diff --git a/.github/classifier.yml b/.github/classifier.yml index a279a7cb8d2..3236507a472 100644 --- a/.github/classifier.yml +++ b/.github/classifier.yml @@ -63,7 +63,7 @@ assignLabel: false }, file-explorer: { - assignees: [ bpasero ], + assignees: [ isidorn ], assignLabel: false }, file-glob: [], diff --git a/.vscode/settings.json b/.vscode/settings.json index 2cb6c48fce4..af8b3803709 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,13 +42,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/README.md b/README.md index b08b3fc38f0..29b3270f1e5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Visual Studio Code - Open Source -[![Build Status](https://dev.azure.com/vscode/VSCode/_apis/build/status/VS%20Code?branchName=master)](https://aka.ms/vscode-builds) + +[![Build Status](https://dev.azure.com/vscode/VSCode/_apis/build/status/VS%20Code?branchName=master)](https://dev.azure.com/vscode/VSCode/_build/latest?definitionId=12) [![Feature Requests](https://img.shields.io/github/issues/Microsoft/vscode/feature-request.svg)](https://github.com/Microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) [![Bugs](https://img.shields.io/github/issues/Microsoft/vscode/bug.svg)](https://github.com/Microsoft/vscode/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Abug) [![Gitter](https://img.shields.io/badge/chat-on%20gitter-yellow.svg)](https://gitter.im/Microsoft/vscode) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index d0d0107677b..1465582fb1d 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.31.2", + "version": "1.32.1", "repo": "https://github.com/Microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", @@ -16,7 +16,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.31.5", + "version": "1.31.6", "repo": "https://github.com/Microsoft/vscode-node-debug2", "metadata": { "id": "36d19e17-7569-4841-a001-947eb18602b2", @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.25", + "version": "0.0.26", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", diff --git a/build/gulpfile.compile.js b/build/gulpfile.compile.js index 521a13a440d..0dd2e5abf19 100644 --- a/build/gulpfile.compile.js +++ b/build/gulpfile.compile.js @@ -6,15 +6,13 @@ '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 = util.task.series(util.rimraf('out-build'), compilation.compileTask('src', 'out-build', true)); -compileClientBuildTask.displayName = 'compile-client-build'; +const compileClientBuildTask = task.define('compile-client-build', task.series(util.rimraf('out-build'), compilation.compileTask('src', 'out-build', true))); // All Build -const compileBuildTask = util.task.parallel(compileClientBuildTask, compileExtensionsBuildTask); -compileBuildTask.displayName = 'compile-build'; - +const compileBuildTask = task.define('compile-build', task.parallel(compileClientBuildTask, compileExtensionsBuildTask)); exports.compileBuildTask = compileBuildTask; \ No newline at end of file diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index f1aa8e77ff1..05c7d4fb8b5 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'); @@ -60,7 +61,7 @@ var BUNDLED_FILE_HEADER = [ const languages = i18n.defaultLanguages.concat([]); // i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages : []); -const extractEditorSrcTask = 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(); @@ -95,13 +96,11 @@ const extractEditorSrcTask = function () { importIgnorePattern: /(^vs\/css!)|(promise-polyfill\/polyfill)/, destRoot: path.join(root, 'out-editor-src') }); -}; -extractEditorSrcTask.displayName = 'extract-editor-src'; +}); -const compileEditorAMDTask = compilation.compileTask('out-editor-src', 'out-editor-build', true); -compileEditorAMDTask.displayName = 'compile-editor-amd'; +const compileEditorAMDTask = task.define('compile-editor-amd', compilation.compileTask('out-editor-src', 'out-editor-build', true)); -const optimizeEditorAMDTask = common.optimizeTask({ +const optimizeEditorAMDTask = task.define('optimize-editor-amd', common.optimizeTask({ src: 'out-editor-build', entryPoints: editorEntryPoints, resources: editorResources, @@ -118,13 +117,11 @@ const optimizeEditorAMDTask = common.optimizeTask({ bundleInfo: true, out: 'out-editor', languages: languages -}); -optimizeEditorAMDTask.displayName = 'optimize-editor-amd'; +})); -const minifyEditorAMDTask = common.minifyTask('out-editor'); -minifyEditorAMDTask.displayName = 'minify-editor-amd'; +const minifyEditorAMDTask = task.define('minify-editor-amd', common.minifyTask('out-editor')); -const createESMSourcesAndResourcesTask = function () { +const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => { standalone.createESMSourcesAndResources2({ srcFolder: './out-editor-src', outFolder: './out-editor-esm', @@ -145,10 +142,9 @@ const createESMSourcesAndResourcesTask = function () { 'vs/nls.mock.ts': 'vs/nls.ts' } }); -}; -createESMSourcesAndResourcesTask.displayName = 'extract-editor-esm'; +}); -const compileEditorESMTask = 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') @@ -162,8 +158,7 @@ const compileEditorESMTask = function () { console.log(result.stdout.toString()); console.log(result.stderr.toString()); } -}; -compileEditorESMTask.displayName = 'compile-editor-esm'; +}); function toExternalDTS(contents) { let lines = contents.split('\n'); @@ -209,7 +204,7 @@ function filterStream(testFunc) { }); } -const finalEditorResourcesTask = function () { +const finalEditorResourcesTask = task.define('final-editor-resources', () => { return es.merge( // other assets es.merge( @@ -286,12 +281,11 @@ const finalEditorResourcesTask = function () { return /\.js\.map$/.test(path); })).pipe(gulp.dest('out-monaco-editor-core/min-maps')) ); -}; -finalEditorResourcesTask.displayName = 'final-editor-resources'; +}); gulp.task('editor-distro', - util.task.series( - util.task.parallel( + task.series( + task.parallel( util.rimraf('out-editor-src'), util.rimraf('out-editor-build'), util.rimraf('out-editor-esm'), @@ -300,13 +294,13 @@ gulp.task('editor-distro', util.rimraf('out-editor-min') ), extractEditorSrcTask, - util.task.parallel( - util.task.series( + task.parallel( + task.series( compileEditorAMDTask, optimizeEditorAMDTask, minifyEditorAMDTask ), - util.task.series( + task.series( createESMSourcesAndResourcesTask, compileEditorESMTask ) @@ -367,12 +361,10 @@ function createTscCompileTask(watch) { }; } -const monacoTypecheckWatchTask = createTscCompileTask(true); -monacoTypecheckWatchTask.displayName = 'monaco-typecheck-watch'; +const monacoTypecheckWatchTask = task.define('monaco-typecheck-watch', createTscCompileTask(true)); exports.monacoTypecheckWatchTask = monacoTypecheckWatchTask; -const monacoTypecheckTask = createTscCompileTask(false); -monacoTypecheckTask.displayName = 'monaco-typecheck'; +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 3a780a22e8d..c72b26cafed 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -12,6 +12,7 @@ const tsb = require('gulp-tsb'); const es = require('event-stream'); const filter = require('gulp-filter'); 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'); @@ -100,18 +101,18 @@ const tasks = compilations.map(function (tsconfigFile) { const srcOpts = { cwd: path.dirname(__dirname), base: srcBase }; - const cleanTask = () => util.primraf(out); + const cleanTask = task.define(`clean-extension-${name}`, util.rimraf(out)); - const compileTask = util.task.series(cleanTask, () => { + 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)); - }); + })); - const watchTask = util.task.series(cleanTask, () => { + 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); @@ -119,20 +120,20 @@ const tasks = compilations.map(function (tsconfigFile) { return watchInput .pipe(util.incremental(pipeline, input)) .pipe(gulp.dest(out)); - }); + })); - const compileBuildTask = util.task.series(cleanTask, () => { + 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)); - }); + })); // Tasks - gulp.task('compile-extension:' + name, compileTask); - gulp.task('watch-extension:' + name, watchTask); + gulp.task(compileTask); + gulp.task(watchTask); return { compileTask: compileTask, @@ -141,16 +142,13 @@ const tasks = compilations.map(function (tsconfigFile) { }; }); -const compileExtensionsTask = util.task.parallel(...tasks.map(t => t.compileTask)); -compileExtensionsTask.displayName = 'compile-extensions'; -gulp.task(compileExtensionsTask.displayName, compileExtensionsTask); +const compileExtensionsTask = task.define('compile-extensions', task.parallel(...tasks.map(t => t.compileTask))); +gulp.task(compileExtensionsTask); exports.compileExtensionsTask = compileExtensionsTask; -const watchExtensionsTask = util.task.parallel(...tasks.map(t => t.watchTask)); -watchExtensionsTask.displayName = 'watch-extensions'; -gulp.task(watchExtensionsTask.displayName, watchExtensionsTask); +const watchExtensionsTask = task.define('watch-extensions', task.parallel(...tasks.map(t => t.watchTask))); +gulp.task(watchExtensionsTask); exports.watchExtensionsTask = watchExtensionsTask; -const compileExtensionsBuildTask = util.task.parallel(...tasks.map(t => t.compileBuildTask)); -compileExtensionsBuildTask.displayName = 'compile-extensions-build'; +const compileExtensionsBuildTask = task.define('compile-extensions-build', task.parallel(...tasks.map(t => t.compileBuildTask))); exports.compileExtensionsBuildTask = compileExtensionsBuildTask; diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 231c5830677..529d85c37aa 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'); @@ -87,8 +88,8 @@ const BUNDLED_FILE_HEADER = [ ' *--------------------------------------------------------*/' ].join('\n'); -const optimizeVSCodeTask = util.task.series( - util.task.parallel( +const optimizeVSCodeTask = task.define('optimize-vscode', task.series( + task.parallel( util.rimraf('out-vscode'), compileBuildTask ), @@ -101,11 +102,10 @@ const optimizeVSCodeTask = util.task.series( out: 'out-vscode', bundleInfo: undefined }) -); -optimizeVSCodeTask.displayName = 'optimize-vscode'; +)); -const optimizeIndexJSTask = util.task.series( +const optimizeIndexJSTask = task.define('optimize-index-js', task.series( optimizeVSCodeTask, () => { const fullpath = path.join(process.cwd(), 'out-vscode/bootstrap-window.js'); @@ -113,18 +113,16 @@ const optimizeIndexJSTask = util.task.series( const newContents = contents.replace('[/*BUILD->INSERT_NODE_MODULES*/]', JSON.stringify(nodeModules)); fs.writeFileSync(fullpath, newContents); } -); -optimizeIndexJSTask.displayName = 'optimize-index-js'; +)); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; -const minifyVSCodeTask = util.task.series( - util.task.parallel( +const minifyVSCodeTask = task.define('minify-vscode', task.series( + task.parallel( util.rimraf('out-vscode-min'), optimizeIndexJSTask ), common.minifyTask('out-vscode', `${sourceMappingURLBase}/core`) -); -minifyVSCodeTask.displayName = 'minify-vscode'; +)); // Package @@ -213,12 +211,11 @@ function getElectron(arch) { }; } -gulp.task('electron', util.task.series(util.rimraf('.build/electron'), getElectron(process.arch))); -gulp.task('electron-ia32', util.task.series(util.rimraf('.build/electron'), getElectron('ia32'))); -gulp.task('electron-x64', util.task.series(util.rimraf('.build/electron'), getElectron('x64'))); -gulp.task('electron-arm', util.task.series(util.rimraf('.build/electron'), getElectron('armv7l'))); -gulp.task('electron-arm64', util.task.series(util.rimraf('.build/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. @@ -457,15 +454,14 @@ BUILD_TARGETS.forEach(buildTarget => { const sourceFolderName = `out-vscode${dashed(minified)}`; const destinationFolderName = `VSCode${dashed(platform)}${dashed(arch)}`; - const vscodeTask = util.task.series( - util.task.parallel( + const vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( + task.parallel( minified ? minifyVSCodeTask : optimizeVSCodeTask, util.rimraf(path.join(buildRoot, destinationFolderName)) ), packageTask(platform, arch, sourceFolderName, destinationFolderName, opts) - ); - vscodeTask.displayName = `vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`; - gulp.task(vscodeTask.displayName, vscodeTask); + )); + gulp.task(vscodeTask); }); }); @@ -490,8 +486,9 @@ 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', - util.task.series( +gulp.task(task.define( + 'vscode-translations-push', + task.series( optimizeVSCodeTask, function () { const pathToMetadata = './out-vscode/nls.metadata.json'; @@ -506,10 +503,11 @@ gulp.task('vscode-translations-push', ).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken)); } ) -); +)); -gulp.task('vscode-translations-export', - util.task.series( +gulp.task(task.define( + 'vscode-translations-export', + task.series( optimizeVSCodeTask, function () { const pathToMetadata = './out-vscode/nls.metadata.json'; @@ -523,7 +521,7 @@ gulp.task('vscode-translations-export', ).pipe(vfs.dest('../vscode-translations-export')); } ) -); +)); gulp.task('vscode-translations-pull', function () { return es.merge([...i18n.defaultLanguages, ...i18n.extraLanguages].map(language => { @@ -574,7 +572,7 @@ gulp.task('upload-vscode-sourcemaps', () => { }); // This task is only run for the MacOS build -const generateVSCodeConfigurationTask = () => { +const generateVSCodeConfigurationTask = task.define('generate-vscode-configuration', () => { return new Promise((resolve, reject) => { const buildDir = process.env['AGENT_BUILDDIRECTORY']; if (!buildDir) { @@ -609,12 +607,12 @@ const generateVSCodeConfigurationTask = () => { reject(err); }); }); -}; -generateVSCodeConfigurationTask.displayName = 'generate-vscode-configuration'; +}); const allConfigDetailsPath = path.join(os.tmpdir(), 'configuration.json'); -gulp.task('upload-vscode-configuration', - util.task.series( +gulp.task(task.define( + 'upload-vscode-configuration', + task.series( generateVSCodeConfigurationTask, () => { if (!shouldSetupSettingsSearch()) { @@ -641,7 +639,7 @@ gulp.task('upload-vscode-configuration', })); } ) -); +)); function shouldSetupSettingsSearch() { const branch = process.env.BUILD_SOURCEBRANCH; diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index b3599cbe39d..fc40616ef09 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'); @@ -241,30 +242,24 @@ BUILD_TARGETS.forEach((buildTarget) => { { const debArch = getDebPackageArch(arch); - const prepareDebTask = util.task.series(util.rimraf(`.build/linux/deb/${debArch}`), prepareDebPackage(arch)); - prepareDebTask.displayName = `vscode-linux-${arch}-prepare-deb`; - // gulp.task(prepareDebTask.displayName, prepareDebTask); - const buildDebTask = util.task.series(prepareDebTask, buildDebPackage(arch)); - buildDebTask.displayName = `vscode-linux-${arch}-build-deb`; - gulp.task(buildDebTask.displayName, buildDebTask); + 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); } { const rpmArch = getRpmPackageArch(arch); - const prepareRpmTask = util.task.series(util.rimraf(`.build/linux/rpm/${rpmArch}`), prepareRpmPackage(arch)); - prepareRpmTask.displayName = `vscode-linux-${arch}-prepare-rpm`; - // gulp.task(prepareRpmTask.displayName, prepareRpmTask); - const buildRpmTask = util.task.series(prepareRpmTask, buildRpmPackage(arch)); - buildRpmTask.displayName = `vscode-linux-${arch}-build-rpm`; - gulp.task(buildRpmTask.displayName, buildRpmTask); + 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); } { - const prepareSnapTask = util.task.series(util.rimraf(`.build/linux/snap/${arch}`), prepareSnapPackage(arch)); - prepareSnapTask.displayName = `vscode-linux-${arch}-prepare-snap`; - gulp.task(prepareSnapTask.displayName, prepareSnapTask); - const buildSnapTask = util.task.series(prepareSnapTask, buildSnapPackage(arch)); - buildSnapTask.displayName = `vscode-linux-${arch}-build-snap`; - gulp.task(buildSnapTask.displayName, buildSnapTask); + 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); } }); diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index b49fcbabcfc..46543316de9 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'); @@ -105,8 +106,8 @@ function buildWin32Setup(arch, target) { } function defineWin32SetupTasks(arch, target) { - const cleanTask = () => util.primraf(setupDir(arch, target)); - gulp.task(`vscode-win32-${arch}-${target}-setup`, util.task.series(cleanTask, 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'); @@ -124,8 +125,8 @@ function archiveWin32Setup(arch) { }; } -gulp.task('vscode-win32-ia32-archive', util.task.series(util.rimraf(zipDir('ia32')), archiveWin32Setup('ia32'))); -gulp.task('vscode-win32-x64-archive', util.task.series(util.rimraf(zipDir('x64')), 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 () => { @@ -141,5 +142,5 @@ function patchInnoUpdater(arch) { }; } -gulp.task('vscode-win32-ia32-inno-updater', util.task.series(copyInnoUpdater('ia32'), patchInnoUpdater('ia32'))); -gulp.task('vscode-win32-x64-inno-updater', util.task.series(copyInnoUpdater('x64'), 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/i18n.resources.json b/build/lib/i18n.resources.json index 6193ef287a3..dc49b431f87 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -38,6 +38,10 @@ "name": "vs/workbench/contrib/codeEditor", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/codeinset", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/comments", "project": "vscode-workbench" @@ -118,6 +122,10 @@ "name": "vs/workbench/contrib/snippets", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/format", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/stats", "project": "vscode-workbench" @@ -210,6 +218,10 @@ "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/standalone.js b/build/lib/standalone.js index cf921e7401c..63e16442d53 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 b7b0fee295f..dfd3c99fe91 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/task.js b/build/lib/task.js new file mode 100644 index 00000000000..4555bbdd068 --- /dev/null +++ b/build/lib/task.js @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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 00000000000..b1a0c903f80 --- /dev/null +++ b/build/lib/task.ts @@ -0,0 +1,125 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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; +} \ No newline at end of file diff --git a/build/lib/util.js b/build/lib/util.js index b6228ad4cf9..17edc75f65f 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -180,76 +180,10 @@ function rimraf(dir) { return cb(err); }); }; - return cb => retry(cb); + retry.taskName = `clean-${path.basename(dir)}`; + return retry; } exports.rimraf = rimraf; -/** - * Like rimraf (with 5 retries), but with a promise instead of a callback. - */ -function primraf(dir) { - const fn = rimraf(dir); - return new Promise((resolve, reject) => { - fn((err) => { - if (err) { - return reject(err); - } - resolve(); - }); - }); -} -exports.primraf = primraf; -var task; -(function (task_1) { - function _isPromise(p) { - if (typeof p.then === 'function') { - return true; - } - return false; - } - async function _execute(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) { - return async () => { - for (let i = 0; i < tasks.length; i++) { - await _execute(tasks[i]); - } - }; - } - task_1.series = series; - function parallel(...tasks) { - return async () => { - await Promise.all(tasks.map(t => _execute(t))); - }; - } - task_1.parallel = parallel; -})(task = exports.task || (exports.task = {})); function getVersion(root) { let version = process.env['BUILD_SOURCEVERSION']; if (!version || !/^[0-9a-f]{40}$/i.test(version)) { diff --git a/build/lib/util.ts b/build/lib/util.ts index 42d3aba2fee..a773b2449da 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -233,86 +233,8 @@ export function rimraf(dir: string): (cb: any) => void { return cb(err); }); }; - - return cb => retry(cb); -} - -/** - * Like rimraf (with 5 retries), but with a promise instead of a callback. - */ -export function primraf(dir: string): Promise { - const fn = rimraf(dir); - return new Promise((resolve, reject) => { - fn((err: any) => { - if (err) { - return reject(err); - } - resolve(); - }); - }); -} - -export type PromiseTask = () => Promise; -export type StreamTask = () => NodeJS.ReadWriteStream; -export type CallbackTask = (cb?: (err?: any) => void) => void; -export type Task = PromiseTask | StreamTask | CallbackTask; - -export namespace task { - - function _isPromise(p: Promise | NodeJS.ReadWriteStream): p is Promise { - if (typeof (p).then === 'function') { - return true; - } - return false; - } - - async function _execute(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 { - return async () => { - for (let i = 0; i < tasks.length; i++) { - await _execute(tasks[i]); - } - }; - } - - export function parallel(...tasks: Task[]): PromiseTask { - return async () => { - await Promise.all(tasks.map(t => _execute(t))); - }; - } + retry.taskName = `clean-${path.basename(dir)}`; + return retry; } export function getVersion(root: string): string | undefined { diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index b0a083e7e9e..5003c35ff82 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/css-language-features/client/src/cssMain.ts b/extensions/css-language-features/client/src/cssMain.ts index 59bd8db7d26..0312498f672 100644 --- a/extensions/css-language-features/client/src/cssMain.ts +++ b/extensions/css-language-features/client/src/cssMain.ts @@ -81,35 +81,39 @@ export function activate(context: ExtensionContext) { documentSelector.forEach(selector => { context.subscriptions.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 - }; - }); - } - return []; + return Promise.all(positions.map(async position => { + 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 + }; + }); + } + return []; + })); } })); }); }); const selectionRangeProvider = { - async provideSelectionRanges(document: TextDocument, position: Position): Promise { + async provideSelectionRanges(document: TextDocument, positions: Position[]): Promise { const textDocument = TextDocumentIdentifier.create(document.uri.toString()); - const rawRanges: Range[] = await client.sendRequest('$/textDocument/selectionRange', { textDocument, position }); + return Promise.all(positions.map(async position => { + const rawRanges: Range[] = await client.sendRequest('$/textDocument/selectionRange', { textDocument, position }); - return rawRanges.map(r => { - const actualRange = new Range(new Position(r.start.line, r.start.character), new Position(r.end.line, r.end.character)); - return { - range: actualRange, - kind: SelectionRangeKind.Declaration - }; - }); + return rawRanges.map(r => { + const actualRange = new Range(new Position(r.start.line, r.start.character), new Position(r.end.line, r.end.character)); + return { + range: actualRange, + kind: SelectionRangeKind.Declaration + }; + }); + })); } }; documentSelector.forEach(selector => { diff --git a/extensions/css-language-features/client/src/customData.ts b/extensions/css-language-features/client/src/customData.ts index b6db79db75d..b812b133ddc 100644 --- a/extensions/css-language-features/client/src/customData.ts +++ b/extensions/css-language-features/client/src/customData.ts @@ -49,7 +49,7 @@ export function getCustomDataPathsFromAllExtensions(): string[] { const contributes = extension.packageJSON && extension.packageJSON.contributes; if (contributes && contributes.css && contributes.css.experimental.customData && Array.isArray(contributes.css.experimental.customData)) { - const relativePaths: string[] = contributes.css.customData; + const relativePaths: string[] = contributes.css.experimental.customData; relativePaths.forEach(rp => { dataPaths.push(path.resolve(extension.extensionPath, rp)); }); diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 4ee8e221f99..f332aff2e00 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -54,7 +54,11 @@ function doWrapping(individualLines: boolean, args: any) { return; } } - const syntax = 'html'; + args = args || {}; + if (!args['language']) { + args['language'] = editor.document.languageId; + } + const syntax = getSyntaxFromArgs(args) || 'html'; const rootNode = parseDocument(editor.document, false); let inPreview = false; diff --git a/extensions/emmet/src/test/wrapWithAbbreviation.test.ts b/extensions/emmet/src/test/wrapWithAbbreviation.test.ts index 9fc924508e2..d5a4a2bce3c 100644 --- a/extensions/emmet/src/test/wrapWithAbbreviation.test.ts +++ b/extensions/emmet/src/test/wrapWithAbbreviation.test.ts @@ -134,19 +134,7 @@ suite('Tests for Wrap with Abbreviations', () => { `; - - return withRandomFileEditor(contents, 'html', (editor, _) => { - editor.selections = [new Selection(2, 0, 2, 0)]; - const promise = wrapWithAbbreviation({ abbreviation: 'li.hello|c' }); - if (!promise) { - assert.equal(1, 2, 'Wrap returned undefined instead of promise.'); - return Promise.resolve(); - } - return promise.then(() => { - assert.equal(editor.document.getText(), expectedContents); - return Promise.resolve(); - }); - }); + return testWrapWithAbbreviation([new Selection(2, 0, 2, 0)], 'li.hello|c', expectedContents, contents); }); test('Wrap with abbreviation entire node when cursor is on opening tag', () => { @@ -162,19 +150,7 @@ suite('Tests for Wrap with Abbreviations', () => { `; - - return withRandomFileEditor(contents, 'html', (editor, _) => { - editor.selections = [new Selection(1, 1, 1, 1)]; - const promise = wrapWithAbbreviation({ abbreviation: 'div' }); - if (!promise) { - assert.equal(1, 2, 'Wrap returned undefined instead of promise.'); - return Promise.resolve(); - } - return promise.then(() => { - assert.equal(editor.document.getText(), expectedContents); - return Promise.resolve(); - }); - }); + return testWrapWithAbbreviation([new Selection(1, 1, 1, 1)], 'div', expectedContents, contents); }); test('Wrap with abbreviation entire node when cursor is on closing tag', () => { @@ -190,19 +166,7 @@ suite('Tests for Wrap with Abbreviations', () => { `; - - return withRandomFileEditor(contents, 'html', (editor, _) => { - editor.selections = [new Selection(3, 1, 3, 1)]; - const promise = wrapWithAbbreviation({ abbreviation: 'div' }); - if (!promise) { - assert.equal(1, 2, 'Wrap returned undefined instead of promise.'); - return Promise.resolve(); - } - return promise.then(() => { - assert.equal(editor.document.getText(), expectedContents); - return Promise.resolve(); - }); - }); + return testWrapWithAbbreviation([new Selection(3, 1, 3, 1)], 'div', expectedContents, contents); }); test('Wrap with multiline abbreviation doesnt add extra spaces', () => { @@ -215,19 +179,7 @@ suite('Tests for Wrap with Abbreviations', () => {
  • hello
  • `; - - return withRandomFileEditor(contents, 'html', (editor, _) => { - editor.selections = [new Selection(1, 2, 1, 2)]; - const promise = wrapWithAbbreviation({ abbreviation: 'ul>li>a' }); - if (!promise) { - assert.equal(1, 2, 'Wrap returned undefined instead of promise.'); - return Promise.resolve(); - } - return promise.then(() => { - assert.equal(editor.document.getText(), expectedContents); - return Promise.resolve(); - }); - }); + return testWrapWithAbbreviation([new Selection(1, 2, 1, 2)], 'ul>li>a', expectedContents, contents); }); test('Wrap individual lines with abbreviation', () => { @@ -245,18 +197,7 @@ suite('Tests for Wrap with Abbreviations', () => { `; - return withRandomFileEditor(contents, 'html', (editor, _) => { - editor.selections = [new Selection(2, 2, 3, 33)]; - const promise = wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello$*' }); - if (!promise) { - assert.equal(1, 2, 'Wrap Individual Lines with Abbreviation returned undefined.'); - return Promise.resolve(); - } - return promise.then(() => { - assert.equal(editor.document.getText(), wrapIndividualLinesExpected); - return Promise.resolve(); - }); - }); + return testWrapIndividualLinesWithAbbreviation([new Selection(2, 2, 3, 33)], 'ul>li.hello$*', wrapIndividualLinesExpected, contents); }); test('Wrap individual lines with abbreviation with extra space selected', () => { @@ -274,18 +215,7 @@ suite('Tests for Wrap with Abbreviations', () => { `; - return withRandomFileEditor(contents, 'html', (editor, _) => { - editor.selections = [new Selection(2, 1, 4, 0)]; - const promise = wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello$*' }); - if (!promise) { - assert.equal(1, 2, 'Wrap Individual Lines with Abbreviation returned undefined.'); - return Promise.resolve(); - } - return promise.then(() => { - assert.equal(editor.document.getText(), wrapIndividualLinesExpected); - return Promise.resolve(); - }); - }); + return testWrapIndividualLinesWithAbbreviation([new Selection(2, 1, 4, 0)], 'ul>li.hello$*', wrapIndividualLinesExpected, contents); }); test('Wrap individual lines with abbreviation with comment filter', () => { @@ -305,18 +235,7 @@ suite('Tests for Wrap with Abbreviations', () => { `; - return withRandomFileEditor(contents, 'html', (editor, _) => { - editor.selections = [new Selection(2, 2, 3, 33)]; - const promise = wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello*|c' }); - if (!promise) { - assert.equal(1, 2, 'Wrap Individual Lines with Abbreviation returned undefined.'); - return Promise.resolve(); - } - return promise.then(() => { - assert.equal(editor.document.getText(), wrapIndividualLinesExpected); - return Promise.resolve(); - }); - }); + return testWrapIndividualLinesWithAbbreviation([new Selection(2, 2, 3, 33)], 'ul>li.hello*|c', wrapIndividualLinesExpected, contents); }); test('Wrap individual lines with abbreviation and trim', () => { @@ -334,19 +253,7 @@ suite('Tests for Wrap with Abbreviations', () => { `; - return withRandomFileEditor(contents, 'html', (editor, _) => { - editor.selections = [new Selection(2, 3, 3, 16)]; - const promise = wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello$*|t' }); - if (!promise) { - assert.equal(1, 2, 'Wrap Individual Lines with Abbreviation returned undefined.'); - return Promise.resolve(); - } - - return promise.then(() => { - assert.equal(editor.document.getText(), wrapIndividualLinesExpected); - return Promise.resolve(); - }); - }); + return testWrapIndividualLinesWithAbbreviation([new Selection(2, 3, 3, 16)], 'ul>li.hello$*|t', wrapIndividualLinesExpected, contents); }); test('Wrap with abbreviation and format set to false', () => { @@ -384,11 +291,35 @@ suite('Tests for Wrap with Abbreviations', () => { return testWrapWithAbbreviation([new Selection(2, 4, 3, 9), new Selection(5, 4, 6, 9)], 'div', wrapMultiLineExpected, htmlContentsForWrapMultiLineTests); }); + + test('Wrap multiline with abbreviation uses className for jsx files', () => { + const wrapMultiLineJsxExpected = ` + +`; + + return testWrapWithAbbreviation([new Selection(2,2,3,33)], '.hello', wrapMultiLineJsxExpected, htmlContentsForWrapTests, 'jsx'); + }); + + test('Wrap individual line with abbreviation uses className for jsx files', () => { + const wrapIndividualLinesJsxExpected = ` + +`; + + return testWrapIndividualLinesWithAbbreviation([new Selection(2,2,3,33)], '.hello$*', wrapIndividualLinesJsxExpected, htmlContentsForWrapTests, 'jsx'); + }); }); -function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string = htmlContentsForWrapTests): Thenable { - return withRandomFileEditor(input, 'html', (editor, _) => { +function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string = htmlContentsForWrapTests, fileExtension: string = 'html'): Thenable { + return withRandomFileEditor(input, fileExtension, (editor, _) => { editor.selections = selections; const promise = wrapWithAbbreviation({ abbreviation }); if (!promise) { @@ -402,3 +333,19 @@ function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, }); }); } + +function testWrapIndividualLinesWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string = htmlContentsForWrapTests, fileExtension: string = 'html'): Thenable { + return withRandomFileEditor(input, fileExtension, (editor, _) => { + editor.selections = selections; + const promise = wrapIndividualLinesWithAbbreviation({ abbreviation }); + if (!promise) { + assert.equal(1, 2, 'Wrap individual lines with Abbreviation returned undefined.'); + return Promise.resolve(); + } + + return promise.then(() => { + assert.equal(editor.document.getText(), expectedContents); + return Promise.resolve(); + }); + }); +} diff --git a/extensions/gulp/src/main.ts b/extensions/gulp/src/main.ts index 1b89a4bdb1a..653e34bd27f 100644 --- a/extensions/gulp/src/main.ts +++ b/extensions/gulp/src/main.ts @@ -122,7 +122,7 @@ class FolderDetector { if (platform === 'win32' && await exists(path.join(rootPath!, 'node_modules', '.bin', 'gulp.cmd'))) { const globalGulp = path.join(process.env.APPDATA ? process.env.APPDATA : '', 'npm', 'gulp.cmd'); if (await exists(globalGulp)) { - gulpCommand = globalGulp; + gulpCommand = '"' + globalGulp + '"'; } else { gulpCommand = path.join('.', 'node_modules', '.bin', 'gulp.cmd'); } diff --git a/extensions/html-language-features/client/src/customData.ts b/extensions/html-language-features/client/src/customData.ts index 2c8c7615a35..d716c0ae347 100644 --- a/extensions/html-language-features/client/src/customData.ts +++ b/extensions/html-language-features/client/src/customData.ts @@ -55,7 +55,7 @@ export function getCustomDataPathsFromAllExtensions(): string[] { contributes.html.experimental.customData && Array.isArray(contributes.html.experimental.customData) ) { - const relativePaths: string[] = contributes.html.customData; + const relativePaths: string[] = contributes.html.experimental.customData; relativePaths.forEach(rp => { dataPaths.push(path.resolve(extension.extensionPath, rp)); }); diff --git a/extensions/html-language-features/client/src/htmlMain.ts b/extensions/html-language-features/client/src/htmlMain.ts index 218e05b3e32..848ed4c6fad 100644 --- a/extensions/html-language-features/client/src/htmlMain.ts +++ b/extensions/html-language-features/client/src/htmlMain.ts @@ -90,18 +90,20 @@ export function activate(context: ExtensionContext) { documentSelector.forEach(selector => { context.subscriptions.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 - }; - }); - } - return []; + return Promise.all(positions.map(async position => { + 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 + }; + }); + } + return []; + })); } })); }); @@ -206,4 +208,4 @@ function readJSONFile(location: string) { export function deactivate(): Promise { return telemetryReporter ? telemetryReporter.dispose() : Promise.resolve(null); -} \ No newline at end of file +} diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index fd84aa1a024..ccc67f2c1cf 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.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/TypeScript-TmLanguage/commit/a42e5cbe14945ccc0493f62b1e2d63eddcdaa6f6", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/06d49b5ea993412a21aad630a17c6e7e7081c30f", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -4396,7 +4396,7 @@ "patterns": [ { "name": "string.regexp.js", - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.js" @@ -4419,7 +4419,7 @@ }, { "name": "string.regexp.js", - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.js.jsx" @@ -4419,7 +4419,7 @@ }, { "name": "string.regexp.js.jsx", - "begin": "(? { 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.map(selectionRange => { + return { + range: client.protocol2CodeConverter.asRange(selectionRange.range), + kind: selectionRange.kind + }; + }); }); } return []; diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 69c21ae84a9..a48161bb43d 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -14,7 +14,7 @@ "dependencies": { "jsonc-parser": "^2.0.2", "request-light": "^0.2.4", - "vscode-json-languageservice": "^3.3.0-next.0", + "vscode-json-languageservice": "^3.3.0-next.2", "vscode-languageserver": "^5.1.0", "vscode-nls": "^4.0.0", "vscode-uri": "^1.0.6" diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 5c3539454e4..a5fc3e6940e 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -427,12 +427,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/yarn.lock b/extensions/json-language-features/server/yarn.lock index 2e1f8085f6d..598acb7c066 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -73,10 +73,10 @@ 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.2: + version "3.3.0-next.2" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.3.0-next.2.tgz#32ac8546a7b80d910b72b0f18ef591d917fa6938" + integrity sha512-ny4vye7kqJfzm31Gvt/zkrNoav2iyck6njmdtugjWslWx1i8ZPSCa1FyPRajnORJpTXu9VC+2oYl2Vmliuywog== dependencies: jsonc-parser "^2.0.2" vscode-languageserver-types "^3.13.0" diff --git a/extensions/json/package.json b/extensions/json/package.json index 0ee9501609f..fd0dd54ff26 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -24,6 +24,7 @@ ".jshintrc", ".jscsrc", ".eslintrc", + ".swcrc", ".webmanifest", ".js.map", ".css.map" @@ -71,4 +72,4 @@ } ] } -} +} \ 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 ede78807943..8a4eea9541d 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 bb0bd24f548..f0d83635dd0 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 \ No newline at end of file +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvY3NwLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2xvYWRpbmcudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvcHJlLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3NldHRpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3N0cmluZ3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0EseURBQWlELGNBQWM7QUFDL0Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsbUNBQTJCLDBCQUEwQixFQUFFO0FBQ3ZELHlDQUFpQyxlQUFlO0FBQ2hEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLDhEQUFzRCwrREFBK0Q7O0FBRXJIO0FBQ0E7OztBQUdBO0FBQ0E7Ozs7Ozs7Ozs7Ozs7O0FDbkVBOzs7Z0dBR2dHOztBQUdoRyxzRkFBeUM7QUFDekMsbUZBQXVDO0FBRXZDOztHQUVHO0FBQ0gsTUFBYSxVQUFVO0lBTXRCO1FBTFEsWUFBTyxHQUFHLEtBQUssQ0FBQztRQUNoQixzQkFBaUIsR0FBRyxLQUFLLENBQUM7UUFLakMsUUFBUSxDQUFDLGdCQUFnQixDQUFDLHlCQUF5QixFQUFFLEdBQUcsRUFBRTtZQUN6RCxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDckIsQ0FBQyxDQUFDLENBQUM7UUFFSCxNQUFNLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUU7WUFDNUMsSUFBSSxLQUFLLElBQUksS0FBSyxDQUFDLElBQUksSUFBSSxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksS0FBSyxzQkFBc0IsRUFBRTtnQkFDdEUsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO2FBQ3BCO1FBQ0YsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDO0lBRU0sU0FBUyxDQUFDLE1BQXFCO1FBQ3JDLElBQUksQ0FBQyxTQUFTLEdBQUcsTUFBTSxDQUFDO1FBQ3hCLElBQUksSUFBSSxDQUFDLGlCQUFpQixFQUFFO1lBQzNCLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztTQUN0QjtJQUNGLENBQUM7SUFFTyxZQUFZO1FBQ25CLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUM7UUFDOUIsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO0lBQ3ZCLENBQUM7SUFFTyxjQUFjO1FBQ3JCLE1BQU0sT0FBTyxHQUFHLG9CQUFVLEVBQUUsQ0FBQztRQUM3QixNQUFNLFFBQVEsR0FBRyxzQkFBVyxFQUFFLENBQUM7UUFFL0IsSUFBSSxJQUFJLENBQUMsT0FBTyxJQUFJLFFBQVEsQ0FBQyx1QkFBdUIsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUU7WUFDeEUsT0FBTztTQUNQO1FBQ0QsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUM7UUFFcEIsTUFBTSxZQUFZLEdBQUcsUUFBUSxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNqRCxZQUFZLENBQUMsU0FBUyxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQztRQUNyRCxZQUFZLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO1FBQ3BELFlBQVksQ0FBQyxZQUFZLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBRWpFLFlBQVksQ0FBQyxZQUFZLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQzVDLFlBQVksQ0FBQyxZQUFZLENBQUMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBQ3RFLFlBQVksQ0FBQyxPQUFPLEdBQUcsR0FBRyxFQUFFO1lBQzNCLElBQUksQ0FBQyxTQUFVLENBQUMsV0FBVyxDQUFDLDZCQUE2QixFQUFFLEVBQUUsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQ3pGLENBQUMsQ0FBQztRQUNGLFFBQVEsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ3pDLENBQUM7Q0FDRDtBQW5ERCxnQ0FtREM7Ozs7Ozs7Ozs7Ozs7OztBQ3pERCxNQUFhLG1CQUFtQjtJQU0vQjtRQUxRLG1CQUFjLEdBQWEsRUFBRSxDQUFDO1FBQzlCLG9CQUFlLEdBQVksS0FBSyxDQUFDO1FBS3hDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxLQUFVLEVBQUUsRUFBRTtZQUN2QyxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7WUFDM0MsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbEMsQ0FBQyxDQUFDO1FBRUYsTUFBTSxDQUFDLGdCQUFnQixDQUFDLGtCQUFrQixFQUFFLEdBQUcsRUFBRTtZQUNoRCxLQUFLLE1BQU0sSUFBSSxJQUFJLFFBQVEsQ0FBQyxzQkFBc0IsQ0FBQyxpQkFBaUIsQ0FBa0MsRUFBRTtnQkFDdkcsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRTtvQkFDeEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxnQkFBZ0IsQ0FBQztpQkFDaEM7YUFDRDtRQUNGLENBQUMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUU7WUFDcEMsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUFFO2dCQUNoQyxPQUFPO2FBQ1A7WUFDRCxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQztZQUM1QixJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUU7Z0JBQ2hCLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLHVCQUF1QixFQUFFLEVBQUUsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO2FBQzFGO1FBQ0YsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDO0lBRU0sU0FBUyxDQUFDLE1BQXFCO1FBQ3JDLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBQ3JCLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRTtZQUN6QixNQUFNLENBQUMsV0FBVyxDQUFDLHVCQUF1QixFQUFFLEVBQUUsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO1NBQ3JGO0lBQ0YsQ0FBQztDQUNEO0FBckNELGtEQXFDQzs7Ozs7Ozs7Ozs7Ozs7QUMzQ0Q7OztnR0FHZ0c7O0FBRWhHLHVFQUFtQztBQUNuQyxtRkFBZ0Q7QUFTaEQsTUFBTSxDQUFDLFVBQVUsR0FBRyxJQUFJLGdCQUFVLEVBQUUsQ0FBQztBQUNyQyxNQUFNLENBQUMsbUJBQW1CLEdBQUcsSUFBSSw2QkFBbUIsRUFBRSxDQUFDOzs7Ozs7Ozs7Ozs7OztBQ2hCdkQ7OztnR0FHZ0c7O0FBWWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsU0FBZ0IsT0FBTyxDQUFDLEdBQVc7SUFDbEMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLElBQUksT0FBTyxFQUFFO1FBQ1osTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QyxJQUFJLElBQUksRUFBRTtZQUNULE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN4QjtLQUNEO0lBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsR0FBRyxFQUFFLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBVkQsMEJBVUM7QUFFRCxTQUFnQixXQUFXO0lBQzFCLElBQUksY0FBYyxFQUFFO1FBQ25CLE9BQU8sY0FBYyxDQUFDO0tBQ3RCO0lBRUQsY0FBYyxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMxQyxJQUFJLGNBQWMsRUFBRTtRQUNuQixPQUFPLGNBQWMsQ0FBQztLQUN0QjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBWEQsa0NBV0M7Ozs7Ozs7Ozs7Ozs7O0FDeENEOzs7Z0dBR2dHOztBQUVoRyxTQUFnQixVQUFVO0lBQ3pCLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxjQUFjLENBQUMsOEJBQThCLENBQUMsQ0FBQztJQUN0RSxJQUFJLEtBQUssRUFBRTtRQUNWLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxZQUFZLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDaEQsSUFBSSxJQUFJLEVBQUU7WUFDVCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDeEI7S0FDRDtJQUNELE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztBQUMzQyxDQUFDO0FBVEQsZ0NBU0MiLCJmaWxlIjoicHJlLmpzIiwic291cmNlc0NvbnRlbnQiOlsiIFx0Ly8gVGhlIG1vZHVsZSBjYWNoZVxuIFx0dmFyIGluc3RhbGxlZE1vZHVsZXMgPSB7fTtcblxuIFx0Ly8gVGhlIHJlcXVpcmUgZnVuY3Rpb25cbiBcdGZ1bmN0aW9uIF9fd2VicGFja19yZXF1aXJlX18obW9kdWxlSWQpIHtcblxuIFx0XHQvLyBDaGVjayBpZiBtb2R1bGUgaXMgaW4gY2FjaGVcbiBcdFx0aWYoaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0pIHtcbiBcdFx0XHRyZXR1cm4gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0uZXhwb3J0cztcbiBcdFx0fVxuIFx0XHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuIFx0XHR2YXIgbW9kdWxlID0gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0gPSB7XG4gXHRcdFx0aTogbW9kdWxlSWQsXG4gXHRcdFx0bDogZmFsc2UsXG4gXHRcdFx0ZXhwb3J0czoge31cbiBcdFx0fTtcblxuIFx0XHQvLyBFeGVjdXRlIHRoZSBtb2R1bGUgZnVuY3Rpb25cbiBcdFx0bW9kdWxlc1ttb2R1bGVJZF0uY2FsbChtb2R1bGUuZXhwb3J0cywgbW9kdWxlLCBtb2R1bGUuZXhwb3J0cywgX193ZWJwYWNrX3JlcXVpcmVfXyk7XG5cbiBcdFx0Ly8gRmxhZyB0aGUgbW9kdWxlIGFzIGxvYWRlZFxuIFx0XHRtb2R1bGUubCA9IHRydWU7XG5cbiBcdFx0Ly8gUmV0dXJuIHRoZSBleHBvcnRzIG9mIHRoZSBtb2R1bGVcbiBcdFx0cmV0dXJuIG1vZHVsZS5leHBvcnRzO1xuIFx0fVxuXG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlcyBvYmplY3QgKF9fd2VicGFja19tb2R1bGVzX18pXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm0gPSBtb2R1bGVzO1xuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZSBjYWNoZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5jID0gaW5zdGFsbGVkTW9kdWxlcztcblxuIFx0Ly8gZGVmaW5lIGdldHRlciBmdW5jdGlvbiBmb3IgaGFybW9ueSBleHBvcnRzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQgPSBmdW5jdGlvbihleHBvcnRzLCBuYW1lLCBnZXR0ZXIpIHtcbiBcdFx0aWYoIV9fd2VicGFja19yZXF1aXJlX18ubyhleHBvcnRzLCBuYW1lKSkge1xuIFx0XHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBuYW1lLCB7XG4gXHRcdFx0XHRjb25maWd1cmFibGU6IGZhbHNlLFxuIFx0XHRcdFx0ZW51bWVyYWJsZTogdHJ1ZSxcbiBcdFx0XHRcdGdldDogZ2V0dGVyXG4gXHRcdFx0fSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGdldERlZmF1bHRFeHBvcnQgZnVuY3Rpb24gZm9yIGNvbXBhdGliaWxpdHkgd2l0aCBub24taGFybW9ueSBtb2R1bGVzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm4gPSBmdW5jdGlvbihtb2R1bGUpIHtcbiBcdFx0dmFyIGdldHRlciA9IG1vZHVsZSAmJiBtb2R1bGUuX19lc01vZHVsZSA/XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0RGVmYXVsdCgpIHsgcmV0dXJuIG1vZHVsZVsnZGVmYXVsdCddOyB9IDpcbiBcdFx0XHRmdW5jdGlvbiBnZXRNb2R1bGVFeHBvcnRzKCkgeyByZXR1cm4gbW9kdWxlOyB9O1xuIFx0XHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQoZ2V0dGVyLCAnYScsIGdldHRlcik7XG4gXHRcdHJldHVybiBnZXR0ZXI7XG4gXHR9O1xuXG4gXHQvLyBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGxcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubyA9IGZ1bmN0aW9uKG9iamVjdCwgcHJvcGVydHkpIHsgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChvYmplY3QsIHByb3BlcnR5KTsgfTtcblxuIFx0Ly8gX193ZWJwYWNrX3B1YmxpY19wYXRoX19cbiBcdF9fd2VicGFja19yZXF1aXJlX18ucCA9IFwiXCI7XG5cblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXyhfX3dlYnBhY2tfcmVxdWlyZV9fLnMgPSBcIi4vcHJldmlldy1zcmMvcHJlLnRzXCIpO1xuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmltcG9ydCB7IE1lc3NhZ2VQb3N0ZXIgfSBmcm9tICcuL21lc3NhZ2luZyc7XG5pbXBvcnQgeyBnZXRTZXR0aW5ncyB9IGZyb20gJy4vc2V0dGluZ3MnO1xuaW1wb3J0IHsgZ2V0U3RyaW5ncyB9IGZyb20gJy4vc3RyaW5ncyc7XG5cbi8qKlxuICogU2hvd3MgYW4gYWxlcnQgd2hlbiB0aGVyZSBpcyBhIGNvbnRlbnQgc2VjdXJpdHkgcG9saWN5IHZpb2xhdGlvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIENzcEFsZXJ0ZXIge1xuXHRwcml2YXRlIGRpZFNob3cgPSBmYWxzZTtcblx0cHJpdmF0ZSBkaWRIYXZlQ3NwV2FybmluZyA9IGZhbHNlO1xuXG5cdHByaXZhdGUgbWVzc2FnaW5nPzogTWVzc2FnZVBvc3RlcjtcblxuXHRjb25zdHJ1Y3RvcigpIHtcblx0XHRkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdzZWN1cml0eXBvbGljeXZpb2xhdGlvbicsICgpID0+IHtcblx0XHRcdHRoaXMub25Dc3BXYXJuaW5nKCk7XG5cdFx0fSk7XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIChldmVudCkgPT4ge1xuXHRcdFx0aWYgKGV2ZW50ICYmIGV2ZW50LmRhdGEgJiYgZXZlbnQuZGF0YS5uYW1lID09PSAndnNjb2RlLWRpZC1ibG9jay1zdmcnKSB7XG5cdFx0XHRcdHRoaXMub25Dc3BXYXJuaW5nKCk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cdH1cblxuXHRwdWJsaWMgc2V0UG9zdGVyKHBvc3RlcjogTWVzc2FnZVBvc3Rlcikge1xuXHRcdHRoaXMubWVzc2FnaW5nID0gcG9zdGVyO1xuXHRcdGlmICh0aGlzLmRpZEhhdmVDc3BXYXJuaW5nKSB7XG5cdFx0XHR0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBvbkNzcFdhcm5pbmcoKSB7XG5cdFx0dGhpcy5kaWRIYXZlQ3NwV2FybmluZyA9IHRydWU7XG5cdFx0dGhpcy5zaG93Q3NwV2FybmluZygpO1xuXHR9XG5cblx0cHJpdmF0ZSBzaG93Q3NwV2FybmluZygpIHtcblx0XHRjb25zdCBzdHJpbmdzID0gZ2V0U3RyaW5ncygpO1xuXHRcdGNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3MoKTtcblxuXHRcdGlmICh0aGlzLmRpZFNob3cgfHwgc2V0dGluZ3MuZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MgfHwgIXRoaXMubWVzc2FnaW5nKSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdHRoaXMuZGlkU2hvdyA9IHRydWU7XG5cblx0XHRjb25zdCBub3RpZmljYXRpb24gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJyk7XG5cdFx0bm90aWZpY2F0aW9uLmlubmVyVGV4dCA9IHN0cmluZ3MuY3NwQWxlcnRNZXNzYWdlVGV4dDtcblx0XHRub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdpZCcsICdjb2RlLWNzcC13YXJuaW5nJyk7XG5cdFx0bm90aWZpY2F0aW9uLnNldEF0dHJpYnV0ZSgndGl0bGUnLCBzdHJpbmdzLmNzcEFsZXJ0TWVzc2FnZVRpdGxlKTtcblxuXHRcdG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnYnV0dG9uJyk7XG5cdFx0bm90aWZpY2F0aW9uLnNldEF0dHJpYnV0ZSgnYXJpYS1sYWJlbCcsIHN0cmluZ3MuY3NwQWxlcnRNZXNzYWdlTGFiZWwpO1xuXHRcdG5vdGlmaWNhdGlvbi5vbmNsaWNrID0gKCkgPT4ge1xuXHRcdFx0dGhpcy5tZXNzYWdpbmchLnBvc3RNZXNzYWdlKCdzaG93UHJldmlld1NlY3VyaXR5U2VsZWN0b3InLCB7IHNvdXJjZTogc2V0dGluZ3Muc291cmNlIH0pO1xuXHRcdH07XG5cdFx0ZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChub3RpZmljYXRpb24pO1xuXHR9XG59XG4iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbmltcG9ydCB7IE1lc3NhZ2VQb3N0ZXIgfSBmcm9tICcuL21lc3NhZ2luZyc7XG5cbmV4cG9ydCBjbGFzcyBTdHlsZUxvYWRpbmdNb25pdG9yIHtcblx0cHJpdmF0ZSB1bmxvYWRlZFN0eWxlczogc3RyaW5nW10gPSBbXTtcblx0cHJpdmF0ZSBmaW5pc2hlZExvYWRpbmc6IGJvb2xlYW4gPSBmYWxzZTtcblxuXHRwcml2YXRlIHBvc3Rlcj86IE1lc3NhZ2VQb3N0ZXI7XG5cblx0Y29uc3RydWN0b3IoKSB7XG5cdFx0Y29uc3Qgb25TdHlsZUxvYWRFcnJvciA9IChldmVudDogYW55KSA9PiB7XG5cdFx0XHRjb25zdCBzb3VyY2UgPSBldmVudC50YXJnZXQuZGF0YXNldC5zb3VyY2U7XG5cdFx0XHR0aGlzLnVubG9hZGVkU3R5bGVzLnB1c2goc291cmNlKTtcblx0XHR9O1xuXG5cdFx0d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7XG5cdFx0XHRmb3IgKGNvbnN0IGxpbmsgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS11c2VyLXN0eWxlJykgYXMgSFRNTENvbGxlY3Rpb25PZjxIVE1MRWxlbWVudD4pIHtcblx0XHRcdFx0aWYgKGxpbmsuZGF0YXNldC5zb3VyY2UpIHtcblx0XHRcdFx0XHRsaW5rLm9uZXJyb3IgPSBvblN0eWxlTG9hZEVycm9yO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fSk7XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbG9hZCcsICgpID0+IHtcblx0XHRcdGlmICghdGhpcy51bmxvYWRlZFN0eWxlcy5sZW5ndGgpIHtcblx0XHRcdFx0cmV0dXJuO1xuXHRcdFx0fVxuXHRcdFx0dGhpcy5maW5pc2hlZExvYWRpbmcgPSB0cnVlO1xuXHRcdFx0aWYgKHRoaXMucG9zdGVyKSB7XG5cdFx0XHRcdHRoaXMucG9zdGVyLnBvc3RNZXNzYWdlKCdwcmV2aWV3U3R5bGVMb2FkRXJyb3InLCB7IHVubG9hZGVkU3R5bGVzOiB0aGlzLnVubG9hZGVkU3R5bGVzIH0pO1xuXHRcdFx0fVxuXHRcdH0pO1xuXHR9XG5cblx0cHVibGljIHNldFBvc3Rlcihwb3N0ZXI6IE1lc3NhZ2VQb3N0ZXIpOiB2b2lkIHtcblx0XHR0aGlzLnBvc3RlciA9IHBvc3Rlcjtcblx0XHRpZiAodGhpcy5maW5pc2hlZExvYWRpbmcpIHtcblx0XHRcdHBvc3Rlci5wb3N0TWVzc2FnZSgncHJldmlld1N0eWxlTG9hZEVycm9yJywgeyB1bmxvYWRlZFN0eWxlczogdGhpcy51bmxvYWRlZFN0eWxlcyB9KTtcblx0XHR9XG5cdH1cbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgQ3NwQWxlcnRlciB9IGZyb20gJy4vY3NwJztcbmltcG9ydCB7IFN0eWxlTG9hZGluZ01vbml0b3IgfSBmcm9tICcuL2xvYWRpbmcnO1xuXG5kZWNsYXJlIGdsb2JhbCB7XG5cdGludGVyZmFjZSBXaW5kb3cge1xuXHRcdGNzcEFsZXJ0ZXI6IENzcEFsZXJ0ZXI7XG5cdFx0c3R5bGVMb2FkaW5nTW9uaXRvcjogU3R5bGVMb2FkaW5nTW9uaXRvcjtcblx0fVxufVxuXG53aW5kb3cuY3NwQWxlcnRlciA9IG5ldyBDc3BBbGVydGVyKCk7XG53aW5kb3cuc3R5bGVMb2FkaW5nTW9uaXRvciA9IG5ldyBTdHlsZUxvYWRpbmdNb25pdG9yKCk7IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmV4cG9ydCBpbnRlcmZhY2UgUHJldmlld1NldHRpbmdzIHtcblx0c291cmNlOiBzdHJpbmc7XG5cdGxpbmU6IG51bWJlcjtcblx0bGluZUNvdW50OiBudW1iZXI7XG5cdHNjcm9sbFByZXZpZXdXaXRoRWRpdG9yPzogYm9vbGVhbjtcblx0c2Nyb2xsRWRpdG9yV2l0aFByZXZpZXc6IGJvb2xlYW47XG5cdGRpc2FibGVTZWN1cml0eVdhcm5pbmdzOiBib29sZWFuO1xuXHRkb3VibGVDbGlja1RvU3dpdGNoVG9FZGl0b3I6IGJvb2xlYW47XG59XG5cbmxldCBjYWNoZWRTZXR0aW5nczogUHJldmlld1NldHRpbmdzIHwgdW5kZWZpbmVkID0gdW5kZWZpbmVkO1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGF0YShrZXk6IHN0cmluZyk6IFByZXZpZXdTZXR0aW5ncyB7XG5cdGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndnNjb2RlLW1hcmtkb3duLXByZXZpZXctZGF0YScpO1xuXHRpZiAoZWxlbWVudCkge1xuXHRcdGNvbnN0IGRhdGEgPSBlbGVtZW50LmdldEF0dHJpYnV0ZShrZXkpO1xuXHRcdGlmIChkYXRhKSB7XG5cdFx0XHRyZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcblx0XHR9XG5cdH1cblxuXHR0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U2V0dGluZ3MoKTogUHJldmlld1NldHRpbmdzIHtcblx0aWYgKGNhY2hlZFNldHRpbmdzKSB7XG5cdFx0cmV0dXJuIGNhY2hlZFNldHRpbmdzO1xuXHR9XG5cblx0Y2FjaGVkU2V0dGluZ3MgPSBnZXREYXRhKCdkYXRhLXNldHRpbmdzJyk7XG5cdGlmIChjYWNoZWRTZXR0aW5ncykge1xuXHRcdHJldHVybiBjYWNoZWRTZXR0aW5ncztcblx0fVxuXG5cdHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U3RyaW5ncygpOiB7IFtrZXk6IHN0cmluZ106IHN0cmluZyB9IHtcblx0Y29uc3Qgc3RvcmUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndnNjb2RlLW1hcmtkb3duLXByZXZpZXctZGF0YScpO1xuXHRpZiAoc3RvcmUpIHtcblx0XHRjb25zdCBkYXRhID0gc3RvcmUuZ2V0QXR0cmlidXRlKCdkYXRhLXN0cmluZ3MnKTtcblx0XHRpZiAoZGF0YSkge1xuXHRcdFx0cmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG5cdFx0fVxuXHR9XG5cdHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc3RyaW5ncycpO1xufVxuIl0sInNvdXJjZVJvb3QiOiIifQ== \ No newline at end of file diff --git a/extensions/markdown-language-features/preview-src/events.ts b/extensions/markdown-language-features/preview-src/events.ts index 40833aca711..81ce5c61640 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 dff07def595..567b915bc53 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 ce81b5ef092..4fe8987f6cd 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 9684d1ec2d9..8a1e8a03fb8 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/test/workspaceSymbolProvider.test.ts b/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts index d19b6611699..e74d3b592bc 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/typescript-basics/cgmanifest.json b/extensions/typescript-basics/cgmanifest.json index 2fbc739c7b5..28949eb2744 100644 --- a/extensions/typescript-basics/cgmanifest.json +++ b/extensions/typescript-basics/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "TypeScript-TmLanguage", "repositoryUrl": "https://github.com/Microsoft/TypeScript-TmLanguage", - "commitHash": "a42e5cbe14945ccc0493f62b1e2d63eddcdaa6f6" + "commitHash": "06d49b5ea993412a21aad630a17c6e7e7081c30f" } }, "license": "MIT", diff --git a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json index b9dda5a5191..6d3db04a01b 100644 --- a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScript.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/TypeScript-TmLanguage/commit/a42e5cbe14945ccc0493f62b1e2d63eddcdaa6f6", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/06d49b5ea993412a21aad630a17c6e7e7081c30f", "name": "TypeScript", "scopeName": "source.ts", "patterns": [ @@ -4445,7 +4445,7 @@ "patterns": [ { "name": "string.regexp.ts", - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.ts" @@ -4468,7 +4468,7 @@ }, { "name": "string.regexp.ts", - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\])+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", + "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/[gimsuy]*(?!\\s*[a-zA-Z0-9_$]))", "beginCaptures": { "1": { "name": "punctuation.definition.string.begin.tsx" @@ -4419,7 +4419,7 @@ }, { "name": "string.regexp.tsx", - "begin": "(?('renameShorthandProperties', true), allowRenameOfImportPath: true, }; } diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 6c237aa538d..fe43c190fe0 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -27,11 +27,57 @@ import { RequestItem, RequestQueue, RequestQueueingType } from './requestQueue'; class TypeScriptServerError extends Error { + public static create( + version: TypeScriptVersion, + response: Proto.Response, + ): TypeScriptServerError { + const parsedResult = TypeScriptServerError.parseErrorText(version, response); + return new TypeScriptServerError(version, response, + parsedResult ? parsedResult.message : undefined, + parsedResult ? parsedResult.stack : undefined); + } + constructor( version: TypeScriptVersion, - public readonly response: Proto.Response, + private readonly response: Proto.Response, + public readonly serverMessage: string | undefined, + public readonly serverStack: string | undefined, ) { - super(`TypeScript Server Error (${version.versionString})\n${TypeScriptServerError.normalizeMessageStack(version, response.message)}`); + super(`TypeScript Server Error (${version.versionString})\n${serverMessage}\n${serverStack}`); + } + + public get serverErrorText() { + return this.response.message; + } + + public get serverCommand() { + return this.response.command; + } + + /** + * Given a `errorText` from a tsserver request indicating failure in handling a request, + * prepares a payload for telemetry-logging. + */ + private static parseErrorText( + version: TypeScriptVersion, + response: Proto.Response, + ) { + const errorText = response.message; + if (errorText) { + const errorPrefix = 'Error processing request. '; + if (errorText.startsWith(errorPrefix)) { + const prefixFreeErrorText = errorText.substr(errorPrefix.length); + const newlineIndex = prefixFreeErrorText.indexOf('\n'); + if (newlineIndex >= 0) { + // Newline expected between message and stack. + return { + message: prefixFreeErrorText.substring(0, newlineIndex), + stack: TypeScriptServerError.normalizeMessageStack(version, prefixFreeErrorText.substring(newlineIndex + 1)) + }; + } + } + } + return undefined; } /** @@ -42,7 +88,7 @@ class TypeScriptServerError extends Error { message: string | undefined, ) { if (!message) { - return message; + return ''; } return message.replace(new RegExp(`${escapeRegExp(version.path)}[/\\\\]tsserver.js:`, 'gi'), 'tsserver.js:'); } @@ -317,7 +363,7 @@ export class TypeScriptServer extends Disposable { // Special case where response itself is successful but there is not any data to return. callback.onSuccess(ServerResponse.NoContent); } else { - callback.onError(new TypeScriptServerError(this._version, response)); + callback.onError(TypeScriptServerError.create(this._version, response)); } } @@ -344,7 +390,6 @@ export class TypeScriptServer extends Disposable { }).catch((err: Error) => { if (err instanceof TypeScriptServerError) { if (!executeInfo.token || !executeInfo.token.isCancellationRequested) { - const properties = this.parseErrorText(err.response.message, err.response.command); /* __GDPR__ "languageServiceErrorResponse" : { "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -356,7 +401,12 @@ export class TypeScriptServer extends Disposable { ] } */ - this._telemetryReporter.logTelemetry('languageServiceErrorResponse', properties); + this._telemetryReporter.logTelemetry('languageServiceErrorResponse', { + command: err.serverCommand, + message: err.serverMessage || '', + stack: err.serverStack || '', + errortext: err.serverErrorText || '', + }); } } @@ -370,30 +420,6 @@ export class TypeScriptServer extends Disposable { return result; } - /** - * Given a `errorText` from a tsserver request indicating failure in handling a request, - * prepares a payload for telemetry-logging. - */ - private parseErrorText(errorText: string | undefined, command: string) { - const properties: ObjectMap = Object.create(null); - properties['command'] = command; - if (errorText) { - properties['errorText'] = errorText; - - const errorPrefix = 'Error processing request. '; - if (errorText.startsWith(errorPrefix)) { - const prefixFreeErrorText = errorText.substr(errorPrefix.length); - const newlineIndex = prefixFreeErrorText.indexOf('\n'); - if (newlineIndex >= 0) { - // Newline expected between message and stack. - properties['message'] = prefixFreeErrorText.substring(0, newlineIndex); - properties['stack'] = prefixFreeErrorText.substring(newlineIndex + 1); - } - } - } - return properties; - } - private sendNextRequests(): void { while (this._pendingResponses.size === 0 && this._requestQueue.length > 0) { const item = this._requestQueue.dequeue(); diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 134942e4696..3af65af48e0 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -166,6 +166,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType this._register(this.pluginManager.onDidUpdateConfig(update => { this.configurePlugin(update.pluginId, update.config); })); + + this._register(this.pluginManager.onDidChangePlugins(() => { + this.restartTsServer(); + })); } public get configuration() { diff --git a/extensions/typescript-language-features/src/utils/plugins.ts b/extensions/typescript-language-features/src/utils/plugins.ts index 3b0cf217b92..71ce2430cb8 100644 --- a/extensions/typescript-language-features/src/utils/plugins.ts +++ b/extensions/typescript-language-features/src/utils/plugins.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import * as arrays from './arrays'; import { Disposable } from './dispose'; -import { memoize } from './memoize'; export interface TypeScriptServerPlugin { readonly path: string; @@ -14,28 +14,45 @@ export interface TypeScriptServerPlugin { readonly languages: ReadonlyArray; } +namespace TypeScriptServerPlugin { + export function equals(a: TypeScriptServerPlugin, b: TypeScriptServerPlugin): boolean { + return a.path === b.path + && a.name === b.name + && a.enableForWorkspaceTypeScriptVersions === b.enableForWorkspaceTypeScriptVersions + && arrays.equals(a.languages, b.languages); + } +} + export class PluginManager extends Disposable { private readonly _pluginConfigurations = new Map(); - @memoize - public get plugins(): ReadonlyArray { - const plugins: TypeScriptServerPlugin[] = []; - for (const extension of vscode.extensions.all) { - const pack = extension.packageJSON; - if (pack.contributes && Array.isArray(pack.contributes.typescriptServerPlugins)) { - for (const plugin of pack.contributes.typescriptServerPlugins) { - plugins.push({ - name: plugin.name, - enableForWorkspaceTypeScriptVersions: !!plugin.enableForWorkspaceTypeScriptVersions, - path: extension.extensionPath, - languages: Array.isArray(plugin.languages) ? plugin.languages : [], - }); - } + private _plugins: Map> | undefined; + + constructor() { + super(); + + vscode.extensions.onDidChange(() => { + if (!this._plugins) { + return; } - } - return plugins; + const newPlugins = this.readPlugins(); + if (!arrays.equals(arrays.flatten(Array.from(this._plugins.values())), arrays.flatten(Array.from(newPlugins.values())), TypeScriptServerPlugin.equals)) { + this._plugins = newPlugins; + this._onDidUpdatePlugins.fire(this); + } + }, undefined, this._disposables); } + public get plugins(): ReadonlyArray { + if (!this._plugins) { + this._plugins = this.readPlugins(); + } + return arrays.flatten(Array.from(this._plugins.values())); + } + + private readonly _onDidUpdatePlugins = this._register(new vscode.EventEmitter()); + public readonly onDidChangePlugins = this._onDidUpdatePlugins.event; + private readonly _onDidUpdateConfig = this._register(new vscode.EventEmitter<{ pluginId: string, config: {} }>()); public readonly onDidUpdateConfig = this._onDidUpdateConfig.event; @@ -47,4 +64,26 @@ export class PluginManager extends Disposable { public configurations(): IterableIterator<[string, {}]> { return this._pluginConfigurations.entries(); } + + private readPlugins() { + const pluginMap = new Map>(); + for (const extension of vscode.extensions.all) { + const pack = extension.packageJSON; + if (pack.contributes && Array.isArray(pack.contributes.typescriptServerPlugins)) { + const plugins: TypeScriptServerPlugin[] = []; + for (const plugin of pack.contributes.typescriptServerPlugins) { + plugins.push({ + name: plugin.name, + enableForWorkspaceTypeScriptVersions: !!plugin.enableForWorkspaceTypeScriptVersions, + path: extension.extensionPath, + languages: Array.isArray(plugin.languages) ? plugin.languages : [], + }); + } + if (plugins.length) { + pluginMap.set(extension.id, plugins); + } + } + } + return pluginMap; + } } \ No newline at end of file diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts index 9983c542be3..6970507fee1 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts @@ -10,6 +10,18 @@ import { createRandomFile } from '../utils'; suite('languages namespace tests', () => { + function positionToString(p: vscode.Position) { + return `[${p.character}/${p.line}]`; + } + + function rangeToString(r: vscode.Range) { + return `[${positionToString(r.start)}/${positionToString(r.end)}]`; + } + + function assertEqualRange(actual: vscode.Range, expected: vscode.Range, message?: string) { + assert.equal(rangeToString(actual), rangeToString(expected), message); + } + test('setTextDocumentLanguage -> close/open event', async function () { const file = await createRandomFile('foo\nbar\nbar'); const doc = await vscode.workspace.openTextDocument(file); @@ -77,6 +89,31 @@ suite('languages namespace tests', () => { assert.ok(found); }); + test('link detector', async function () { + const uri = await createRandomFile('class A { // http://a.com }', undefined, '.java'); + const doc = await vscode.workspace.openTextDocument(uri); + + const target = vscode.Uri.parse('file://foo/bar'); + const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 5)); + + const linkProvider: vscode.DocumentLinkProvider = { + provideDocumentLinks: _doc => { + return [new vscode.DocumentLink(range, target)]; + } + }; + vscode.languages.registerDocumentLinkProvider({ language: 'java', scheme: 'file' }, linkProvider); + + const links = await vscode.commands.executeCommand('vscode.executeLinkProvider', doc.uri); + assert.equal(2, links && links.length); + let [link1, link2] = links!.sort((l1, l2) => l1.range.start.compareTo(l2.range.start)); + + assert.equal(target.toString(), link1.target && link1.target.toString()); + assertEqualRange(range, link1.range); + + assert.equal('http://a.com/', link2.target && link2.target.toString()); + assertEqualRange(new vscode.Range(new vscode.Position(0, 13), new vscode.Position(0, 25)), link2.range); + }); + test('diagnostics & CodeActionProvider', async function () { class D2 extends vscode.Diagnostic { @@ -144,4 +181,5 @@ suite('languages namespace tests', () => { assert.ok(ran); assert.equal(result!.items[0].label, 'foo'); }); + }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts new file mode 100644 index 00000000000..b2ad43d30b0 --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as assert from 'assert'; +import * as vscode from 'vscode'; + + +suite('types', () => { + + test('static properties, es5 compat class', function () { + + + assert.ok(vscode.ThemeIcon.File instanceof vscode.ThemeIcon); + assert.ok(vscode.ThemeIcon.Folder instanceof vscode.ThemeIcon); + assert.ok(vscode.CodeActionKind.Empty instanceof vscode.CodeActionKind); + assert.ok(vscode.CodeActionKind.QuickFix instanceof vscode.CodeActionKind); + assert.ok(vscode.CodeActionKind.Refactor instanceof vscode.CodeActionKind); + assert.ok(vscode.CodeActionKind.RefactorExtract instanceof vscode.CodeActionKind); + assert.ok(vscode.CodeActionKind.RefactorInline instanceof vscode.CodeActionKind); + assert.ok(vscode.CodeActionKind.RefactorRewrite instanceof vscode.CodeActionKind); + assert.ok(vscode.CodeActionKind.Source instanceof vscode.CodeActionKind); + assert.ok(vscode.CodeActionKind.SourceOrganizeImports instanceof vscode.CodeActionKind); + assert.ok(vscode.CodeActionKind.SourceFixAll instanceof vscode.CodeActionKind); + // assert.ok(vscode.QuickInputButtons.Back instanceof vscode.QuickInputButtons); never was an instance + + }); +}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index e5cf2bd12a4..a950e90504b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -568,7 +568,7 @@ suite('workspace-namespace', () => { test('applyEdit should fail when editing renamed from resource', async () => { const resource = await createRandomFile(); - const newResource = vscode.Uri.parse(resource.fsPath + '.1'); + const newResource = vscode.Uri.file(resource.fsPath + '.1'); const edit = new vscode.WorkspaceEdit(); edit.renameFile(resource, newResource); edit.insert(resource, new vscode.Position(0, 0), ''); diff --git a/extensions/vscode-api-tests/src/utils.ts b/extensions/vscode-api-tests/src/utils.ts index 79db77f0d0c..b65e542c583 100644 --- a/extensions/vscode-api-tests/src/utils.ts +++ b/extensions/vscode-api-tests/src/utils.ts @@ -12,9 +12,9 @@ export function rndName() { return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10); } -export function createRandomFile(contents = '', dir: string = os.tmpdir()): Thenable { +export function createRandomFile(contents = '', dir: string = os.tmpdir(), ext = ''): Thenable { return new Promise((resolve, reject) => { - const tmpFile = join(dir, rndName()); + const tmpFile = join(dir, rndName() + ext); fs.writeFile(tmpFile, contents, (error) => { if (error) { return reject(error); diff --git a/gulpfile.js b/gulpfile.js index b41fa640399..1d13cff608c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -10,26 +10,24 @@ 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 -const compileClientTask = util.task.series(util.rimraf('out'), compilation.compileTask('src', 'out', false)); -compileClientTask.displayName = 'compile-client'; -gulp.task(compileClientTask.displayName, compileClientTask); +const compileClientTask = task.define('compile-client', task.series(util.rimraf('out'), compilation.compileTask('src', 'out', false))); +gulp.task(compileClientTask); -const watchClientTask = util.task.series(util.rimraf('out'), compilation.watchTask('out', false)); -watchClientTask.displayName = 'watch-client'; -gulp.task(watchClientTask.displayName, watchClientTask); +const watchClientTask = task.define('watch-client', task.series(util.rimraf('out'), compilation.watchTask('out', false))); +gulp.task(watchClientTask); // All -const compileTask = util.task.parallel(monacoTypecheckTask, compileClientTask, compileExtensionsTask); -compileTask.displayName = 'compile'; -gulp.task(compileTask.displayName, compileTask); +const compileTask = task.define('compile', task.parallel(monacoTypecheckTask, compileClientTask, compileExtensionsTask)); +gulp.task(compileTask); -gulp.task('watch', util.task.parallel(/* monacoTypecheckWatchTask, */ watchClientTask, watchExtensionsTask)); +gulp.task(task.define('watch', task.parallel(/* monacoTypecheckWatchTask, */ watchClientTask, watchExtensionsTask))); // Default gulp.task('default', compileTask); diff --git a/package.json b/package.json index 908f250e6be..00b54329e5c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.32.0", - "distro": "7b752ca3a77088fd367b62bfcce2e437c99030b7", + "distro": "6f5d9db8821add003be71e726b4ef49df83a51b4", "author": { "name": "Microsoft Corporation" }, @@ -28,7 +28,6 @@ }, "dependencies": { "applicationinsights": "1.0.8", - "fast-plist": "0.1.2", "gc-signals": "^0.0.1", "getmac": "1.4.1", "graceful-fs": "4.1.11", @@ -47,13 +46,13 @@ "sudo-prompt": "8.2.0", "v8-inspect-profiler": "^0.0.20", "vscode-chokidar": "1.6.5", - "vscode-debugprotocol": "1.33.0", + "vscode-debugprotocol": "^1.34.0-pre.0", "vscode-nsfw": "1.1.1", "vscode-proxy-agent": "0.3.0", "vscode-ripgrep": "^1.2.5", "vscode-sqlite3": "4.0.7", "vscode-textmate": "^4.0.1", - "vscode-xterm": "3.12.0-beta2", + "vscode-xterm": "3.12.0-beta4", "winreg": "^1.2.4", "yauzl": "^2.9.1", "yazl": "^2.4.3" @@ -82,6 +81,7 @@ "event-stream": "3.3.4", "express": "^4.13.1", "fancy-log": "^1.3.3", + "fast-plist": "0.1.2", "glob": "^5.0.13", "gulp": "^4.0.0", "gulp-atom-electron": "^1.20.0", @@ -133,7 +133,7 @@ "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", "vsce": "1.48.0", - "vscode-nls-dev": "3.2.4", + "vscode-nls-dev": "3.2.5", "webpack": "^4.16.5", "webpack-cli": "^3.1.0", "webpack-stream": "^5.1.1" @@ -150,4 +150,4 @@ "windows-mutex": "0.2.1", "windows-process-tree": "0.2.3" } -} \ No newline at end of file +} diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index fc13a2d99b4..227bd2e456e 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -10,11 +10,11 @@ call .\scripts\test.bat --runGlob **\*.integrationTest.js %* 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% if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index c41965b30ae..80145240039 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,12 +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); - } - if (options && typeof options.canModifyDOM === 'function') { options.canModifyDOM(configuration); } diff --git a/src/main.js b/src/main.js index 064f5da1934..d846bcd0173 100644 --- a/src/main.js +++ b/src/main.js @@ -57,7 +57,7 @@ registerListeners(); */ let nlsConfiguration = undefined; const userDefinedLocale = getUserDefinedLocale(); -userDefinedLocale.then((locale) => { +userDefinedLocale.then(locale => { if (locale && !nlsConfiguration) { nlsConfiguration = getNLSConfiguration(locale); } @@ -98,7 +98,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 +129,7 @@ function onReady() { // See above the comment about the loader and case sensitiviness appLocale = appLocale.toLowerCase(); - getNLSConfiguration(appLocale).then((nlsConfig) => { + getNLSConfiguration(appLocale).then(nlsConfig => { if (!nlsConfig) { nlsConfig = { locale: appLocale, availableLanguages: {} }; } @@ -437,7 +437,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; @@ -535,7 +535,7 @@ function getNLSConfiguration(locale) { if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') { return defaultResult(initialLocale); } - return exists(mainPack).then((fileExists) => { + return exists(mainPack).then(fileExists => { if (!fileExists) { return defaultResult(initialLocale); } @@ -553,7 +553,7 @@ function getNLSConfiguration(locale) { _resolvedLanguagePackCoreLocation: coreLocation, _corruptedFile: corruptedFile }; - return exists(corruptedFile).then((corrupted) => { + return exists(corruptedFile).then(corrupted => { // The nls cache directory is corrupted. let toDelete; if (corrupted) { @@ -562,7 +562,7 @@ function getNLSConfiguration(locale) { toDelete = Promise.resolve(undefined); } return toDelete.then(() => { - return exists(coreLocation).then((fileExists) => { + return exists(coreLocation).then(fileExists => { if (fileExists) { // We don't wait for this. No big harm if we can't touch touch(coreLocation).catch(() => { }); @@ -571,7 +571,7 @@ function getNLSConfiguration(locale) { } return mkdirp(coreLocation).then(() => { return Promise.all([bootstrap.readFile(path.join(__dirname, 'nls.metadata.json')), bootstrap.readFile(mainPack)]); - }).then((values) => { + }).then(values => { const metadata = JSON.parse(values[0]); const packData = JSON.parse(values[1]).contents; const bundles = Object.keys(metadata.bundles); @@ -607,7 +607,7 @@ function getNLSConfiguration(locale) { }).then(() => { perf.mark('nlsGeneration:end'); return result; - }).catch((err) => { + }).catch(err => { console.error('Generating translation files failed.', err); return defaultResult(locale); }); diff --git a/src/tsconfig.json b/src/tsconfig.json index 4b9f4170455..72ae60789f9 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -4,7 +4,13 @@ "removeComments": false, "preserveConstEnums": true, "sourceMap": false, - "outDir": "../out" + "outDir": "../out", + "target": "es6", + "lib": [ + "dom", + "es5", + "es2015.iterable" + ] }, "include": [ "./typings", @@ -13,4 +19,4 @@ "exclude": [ "./typings/require-monaco.d.ts" ] -} \ No newline at end of file +} diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index 3b090e0b718..c8517858e66 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -213,7 +213,7 @@ "./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/browser/contextScopedHistoryWidget.ts", "./vs/platform/clipboard/common/clipboardService.ts", "./vs/platform/clipboard/electron-browser/clipboardService.ts", "./vs/platform/commands/common/commands.ts", @@ -235,7 +235,6 @@ "./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", @@ -284,8 +283,6 @@ "./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", @@ -320,15 +317,11 @@ "./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", @@ -343,10 +336,6 @@ "./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", @@ -381,7 +370,6 @@ "./vs/platform/url/common/urlService.ts", "./vs/platform/url/electron-main/electronUrlListener.ts", "./vs/platform/url/node/urlIpc.ts", - "./vs/platform/browser/contextScopedHistoryWidget.ts", "./vs/platform/windows/common/windows.ts", "./vs/platform/windows/electron-browser/windowService.ts", "./vs/platform/windows/electron-main/windows.ts", @@ -392,18 +380,19 @@ "./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/common/configurationExtensionPoint.ts", - "./vs/workbench/api/common/menusExtensionPoint.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", @@ -414,6 +403,7 @@ "./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", @@ -421,6 +411,8 @@ "./vs/workbench/api/electron-browser/mainThreadStatusBar.ts", "./vs/workbench/api/electron-browser/mainThreadStorage.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/mainThreadWindow.ts", "./vs/workbench/api/electron-browser/mainThreadWorkspace.ts", @@ -435,10 +427,12 @@ "./vs/workbench/api/node/extHostMessageService.ts", "./vs/workbench/api/node/extHostOutputService.ts", "./vs/workbench/api/node/extHostSearch.fileIndex.ts", + "./vs/workbench/api/node/extHostSearch.ts", "./vs/workbench/api/node/extHostStorage.ts", "./vs/workbench/api/node/extHostTypes.ts", "./vs/workbench/api/node/extHostUrls.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/browser/actions.ts", @@ -467,6 +461,8 @@ "./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/quickOpenActions.ts", + "./vs/workbench/browser/parts/quickopen/quickOpenController.ts", "./vs/workbench/browser/parts/quickopen/quickopen.ts", "./vs/workbench/browser/parts/sidebar/sidebarPart.ts", "./vs/workbench/browser/parts/statusbar/statusbar.ts", @@ -475,6 +471,7 @@ "./vs/workbench/browser/parts/views/views.ts", "./vs/workbench/browser/parts/views/viewsViewlet.ts", "./vs/workbench/browser/quickopen.ts", + "./vs/workbench/browser/style.ts", "./vs/workbench/browser/viewlet.ts", "./vs/workbench/browser/workbench.contribution.ts", "./vs/workbench/common/actions.ts", @@ -496,12 +493,12 @@ "./vs/workbench/common/views.ts", "./vs/workbench/contrib/backup/common/backupRestorer.ts", "./vs/workbench/contrib/cli/electron-browser/cli.contribution.ts", + "./vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts", "./vs/workbench/contrib/codeEditor/browser/menuPreventer.ts", "./vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts", "./vs/workbench/contrib/codeEditor/electron-browser/accessibility.ts", "./vs/workbench/contrib/codeEditor/electron-browser/inspectKeybindings.ts", "./vs/workbench/contrib/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts", - "./vs/workbench/contrib/codeEditor/electron-browser/largeFileOptimizations.ts", "./vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts", "./vs/workbench/contrib/codeEditor/electron-browser/simpleEditorOptions.ts", "./vs/workbench/contrib/codeEditor/electron-browser/sleepResumeRepaintMinimap.ts", @@ -512,9 +509,11 @@ "./vs/workbench/contrib/codeEditor/electron-browser/toggleRenderControlCharacter.ts", "./vs/workbench/contrib/codeEditor/electron-browser/toggleRenderWhitespace.ts", "./vs/workbench/contrib/codeEditor/electron-browser/toggleWordWrap.ts", + "./vs/workbench/contrib/codeinset/common/codeInset.ts", "./vs/workbench/contrib/comments/common/commentModel.ts", "./vs/workbench/contrib/comments/electron-browser/commentGlyphWidget.ts", "./vs/workbench/contrib/comments/electron-browser/commentService.ts", + "./vs/workbench/contrib/comments/electron-browser/reactionsAction.ts", "./vs/workbench/contrib/comments/electron-browser/simpleCommentEditor.ts", "./vs/workbench/contrib/debug/browser/debugANSIHandling.ts", "./vs/workbench/contrib/debug/browser/debugActionItems.ts", @@ -547,7 +546,9 @@ "./vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts", "./vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts", "./vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionsList.ts", "./vs/workbench/contrib/extensions/electron-browser/extensionsUtils.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts", "./vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts", "./vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts", "./vs/workbench/contrib/feedback/electron-browser/feedback.contribution.ts", @@ -558,8 +559,10 @@ "./vs/workbench/contrib/files/common/files.ts", "./vs/workbench/contrib/files/electron-browser/explorerService.ts", "./vs/workbench/contrib/files/electron-browser/views/explorerDecorationsProvider.ts", + "./vs/workbench/contrib/format/browser/format.contribution.ts", "./vs/workbench/contrib/localizations/electron-browser/localizations.contribution.ts", "./vs/workbench/contrib/localizations/electron-browser/localizationsActions.ts", + "./vs/workbench/contrib/localizations/electron-browser/minimalTranslations.ts", "./vs/workbench/contrib/logs/common/logConstants.ts", "./vs/workbench/contrib/logs/electron-browser/logs.contribution.ts", "./vs/workbench/contrib/logs/electron-browser/logsActions.ts", @@ -574,6 +577,7 @@ "./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/node/outputAppender.ts", "./vs/workbench/contrib/performance/electron-browser/startupTimings.ts", "./vs/workbench/contrib/preferences/browser/settingsWidgets.ts", "./vs/workbench/contrib/preferences/common/smartSnippetInserter.ts", @@ -641,6 +645,7 @@ "./vs/workbench/contrib/terminal/common/terminalCommands.ts", "./vs/workbench/contrib/terminal/common/terminalMenu.ts", "./vs/workbench/contrib/terminal/common/terminalService.ts", + "./vs/workbench/contrib/terminal/electron-browser/terminalActions.ts", "./vs/workbench/contrib/terminal/electron-browser/terminalConfigHelper.ts", "./vs/workbench/contrib/terminal/electron-browser/terminalInstance.ts", "./vs/workbench/contrib/terminal/electron-browser/terminalLinkHandler.ts", @@ -657,13 +662,14 @@ "./vs/workbench/contrib/terminal/test/node/terminalCommandTracker.test.ts", "./vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts", "./vs/workbench/contrib/themes/electron-browser/themes.contribution.ts", + "./vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts", "./vs/workbench/contrib/url/electron-browser/url.contribution.ts", "./vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts", - "./vs/workbench/electron-browser/resources.ts", "./vs/workbench/electron-browser/window.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/broadcast/electron-browser/broadcastService.ts", "./vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts", "./vs/workbench/services/configuration/common/configuration.ts", "./vs/workbench/services/configuration/common/configurationModels.ts", @@ -702,8 +708,10 @@ "./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/history/browser/history.ts", "./vs/workbench/services/history/common/history.ts", - "./vs/workbench/services/history/electron-browser/history.ts", + "./vs/workbench/services/integrity/common/integrity.ts", + "./vs/workbench/services/integrity/node/integrityServiceImpl.ts", "./vs/workbench/services/keybinding/common/keybindingIO.ts", "./vs/workbench/services/keybinding/common/keyboardMapper.ts", "./vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts", @@ -727,7 +735,10 @@ "./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/search/common/replace.ts", + "./vs/workbench/services/search/common/search.ts", "./vs/workbench/services/search/common/searchHelpers.ts", + "./vs/workbench/services/search/common/searchHistoryService.ts", "./vs/workbench/services/search/node/fileSearch.ts", "./vs/workbench/services/search/node/fileSearchManager.ts", "./vs/workbench/services/search/node/rawSearchService.ts", @@ -737,10 +748,11 @@ "./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/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", @@ -761,9 +773,11 @@ "./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/extHost.api.impl.test.ts", "./vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts", "./vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts", "./vs/workbench/test/electron-browser/api/extHostTypes.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/mainThreadDiagnostics.test.ts", "./vs/workbench/test/electron-browser/api/mainThreadDocumentContentProviders.test.ts", diff --git a/src/typings/fast-plist.d.ts b/src/typings/fast-plist.d.ts deleted file mode 100644 index 537e7c2e8ec..00000000000 --- 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/lib.ie11_safe_es6.d.ts b/src/typings/lib.ie11_safe_es6.d.ts index ef2e2db7da5..43bde6f7c64 100644 --- a/src/typings/lib.ie11_safe_es6.d.ts +++ b/src/typings/lib.ie11_safe_es6.d.ts @@ -59,7 +59,7 @@ interface SetConstructor { declare var Set: SetConstructor; -interface WeakMap { +interface WeakMap { delete(key: K): boolean; get(key: K): V | undefined; has(key: K): boolean; @@ -70,9 +70,9 @@ interface WeakMap { interface WeakMapConstructor { new(): WeakMap; - new (): WeakMap; + new (): WeakMap; // new (entries?: [K, V][]): WeakMap; - readonly prototype: WeakMap; + readonly prototype: WeakMap; } declare var WeakMap: WeakMapConstructor; diff --git a/src/typings/lib.webworker.importscripts.d.ts b/src/typings/lib.webworker.importscripts.d.ts new file mode 100644 index 00000000000..e84f717c9a4 --- /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/vs/base/browser/dnd.ts b/src/vs/base/browser/dnd.ts index 3aca5c59adf..d7e6571b6f9 100644 --- a/src/vs/base/browser/dnd.ts +++ b/src/vs/base/browser/dnd.ts @@ -71,7 +71,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/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 42f43d27322..d6f63b99a62 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 c0835afdba4..070c88e284d 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -358,6 +358,10 @@ export class FindInput extends Widget { this._onCaseSensitiveKeyDown.fire(e); })); + if (this._showOptionButtons) { + this.inputBox.inputElement.style.paddingRight = (this.caseSensitive.width() + this.wholeWords.width() + this.regex.width()) + 'px'; + } + // Arrow-Key support to navigate between options let indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; this.onkeydown(this.domNode, (event: IKeyboardEvent) => { diff --git a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts index 45b646c165f..e9fdc86be15 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 | 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 | undefined, matches?: Matches) { if (this.didEverRender && this.keybinding === keybinding && KeybindingLabel.areSame(this.matches, matches)) { return; } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 7b60e1ab46b..73ca98dabcf 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,7 +54,7 @@ export interface IListViewOptions { readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; - readonly disableAriaRoles?: boolean; + readonly ariaSetProvider?: IAriaSetProvider; } const DefaultOptions = { @@ -64,8 +69,7 @@ const DefaultOptions = { onDragOver() { return false; }, drop() { } }, - horizontalScrolling: false, - disableAriaRoles: false + horizontalScrolling: false }; export class ElementsDragAndDropData implements IDragAndDropData { @@ -144,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[]; @@ -167,7 +174,7 @@ export class ListView implements ISpliceable, IDisposable { private setRowLineHeight: boolean; private supportDynamicHeights: boolean; private horizontalScrolling: boolean; - private disableAriaRoles: boolean; + private ariaSetProvider: IAriaSetProvider; private scrollWidth: number | undefined; private canUseTranslate3d: boolean | undefined = undefined; @@ -198,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'); @@ -219,12 +226,16 @@ 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.disableAriaRoles = getOrDefault(options, o => o.disableAriaRoles, DefaultOptions.disableAriaRoles); + this.ariaSetProvider = options.ariaSetProvider || { getSetSize: (e, i, length) => length, getPosInSet: (_, index) => index + 1 }; this.rowsContainer = document.createElement('div'); this.rowsContainer.className = 'monaco-list-rows'; @@ -533,6 +544,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) { @@ -600,11 +612,9 @@ 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'); - - if (!this.disableAriaRoles) { - 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); } @@ -1083,6 +1093,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 fe2471d11d1..a342692f5c8 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'); @@ -818,7 +814,7 @@ export interface IListOptions extends IListStyles { readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; - readonly disableAriaRoles?: boolean; + readonly ariaSetProvider?: IAriaSetProvider; } export interface IListStyles { @@ -1069,9 +1065,6 @@ export interface IListOptionsUpdate { 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(); @@ -1167,7 +1160,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); @@ -1193,12 +1186,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), @@ -1528,10 +1518,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; } @@ -1572,7 +1558,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/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 0369a9baa6e..7a5cd31d621 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -150,11 +150,9 @@ 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(); - } + if (!event.relatedTarget || !this.container.contains(event.relatedTarget as HTMLElement)) { + this.focusToReturn = undefined; + this.setUnfocusedState(); } })); diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 77ca7e4f06e..0ddce2da9e0 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -428,8 +428,6 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate(modelProvider: () => ITreeModel implements IListRenderer>(); private renderedNodes = new Map, ITreeListTemplateData>(); - private indent: number; + private indent: number = TreeRenderer.DefaultIndent; private disposables: IDisposable[] = []; constructor( @@ -208,7 +215,9 @@ class TreeRenderer implements IListRenderer { templateData.twistie.style.marginLeft = `${node.depth * this.indent}px`; @@ -230,8 +239,6 @@ class TreeRenderer implements IListRenderer(options?: IAsyncDataTreeOpt typeof options.expandOnlyOnTwistieClick !== 'function' ? options.expandOnlyOnTwistieClick : ( e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T) ) - ) + ), + ariaSetProvider: undefined }; } diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index 4a780995790..f5b9b26edd6 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; diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 1a77679c4ff..4438fa3db74 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) { diff --git a/src/vs/base/common/paths.ts b/src/vs/base/common/extpath.ts similarity index 68% rename from src/vs/base/common/paths.ts rename to src/vs/base/common/extpath.ts index 33496d1d7d9..a829cbcf5fe 100644 --- a/src/vs/base/common/paths.ts +++ b/src/vs/base/common/extpath.ts @@ -6,81 +6,12 @@ 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 - */ -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 = /(\\\.\.?\\)|(\\\.\.?)$|^(\.\.?\\)|(\\\\+)|(\/)/; @@ -90,10 +21,19 @@ function _isNormal(path: string, win: boolean): boolean { : !_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 { +/** + * 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 toSlashes(osPath: string) { + return osPath.replace(/[\\/]/g, '/'); +} + +export function normalizeWithSlashes(path: undefined): undefined; +export function normalizeWithSlashes(path: null): null; +export function normalizeWithSlashes(path: string): string; +export function normalizeWithSlashes(path: string | null | undefined): string | null | undefined { if (path === null || path === undefined) { return path; @@ -104,12 +44,11 @@ export function normalize(path: string | null | undefined, toOSPath?: boolean): return '.'; } - const wantsBackslash = !!(isWindows && toOSPath); - if (_isNormal(path, wantsBackslash)) { + if (_isNormal(path, false)) { return path; } - const sep = wantsBackslash ? '\\' : '/'; + const sep = '/'; const root = getRoot(path, sep); // skip the root-portion of the path @@ -154,6 +93,30 @@ function streql(value: string, start: number, end: number, other: string): boole return start + other.length === end && value.indexOf(other, start) === start; } +export function joinWithSlashes(...parts: string[]): string { + let value = ''; + for (let i = 0; i < parts.length; i++) { + let part = parts[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 += posix.sep; + } + } + } + value += part; + } + + return normalizeWithSlashes(value); +} + + +// #region extpath + /** * Computes the _root_ this path, like `getRoot('c:\files') === c:\`, * `getRoot('files:///files/path') === files:///`, @@ -227,33 +190,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`. * @@ -343,7 +279,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 +317,8 @@ 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; } + +// #endregion \ No newline at end of file diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index a26f954ef31..a7569150a48 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 { @@ -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); }; } @@ -396,8 +396,8 @@ 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 typeof path === 'string' && (path === nativePath || strings.endsWith(path, nativePathEnd)) ? pattern : null; } : function (path, basename) { @@ -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'; } /** diff --git a/src/vs/base/common/keyCodes.ts b/src/vs/base/common/keyCodes.ts index c8e7b7c1579..b2d75920a4c 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. @@ -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 @@ -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 30344e4d724..a1df76ab4fb 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]); + + let 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(' '); } } @@ -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 1d2a0db8914..880278b602f 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); - } + let 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] { @@ -110,6 +108,7 @@ export class KeybindingParser { } static parseUserBinding(input: string): [SimpleKeybinding | ScanCodeBinding | null, SimpleKeybinding | ScanCodeBinding | null] { + // TODO@chords: allow users to define N chords if (!input) { return [null, null]; } diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index c9e2a9b1733..efe10e90646 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ 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'; export interface IWorkspaceFolderProvider { getWorkspaceFolder(resource: URI): { uri: URI, name?: string } | null; @@ -39,11 +39,12 @@ export function getPathLabel(resource: URI | string, userHomeProvider?: IUserHom if (isEqual(baseResource.uri, resource, !isLinux)) { 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 +59,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 +82,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 +113,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 +170,7 @@ export function shorten(paths: string[]): string[] { let path = paths[pathIndex]; if (path === '') { - shortenedPaths[pathIndex] = `.${nativeSep}`; + shortenedPaths[pathIndex] = `.${sep}`; continue; } @@ -185,20 +186,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 +210,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 +227,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 +239,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; diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts index dea84057f73..10ab55362b1 100644 --- a/src/vs/base/common/mime.ts +++ b/src/vs/base/common/mime.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. 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); } @@ -234,8 +234,8 @@ export function suggestFilename(langId: string, 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/path.ts b/src/vs/base/common/path.ts new file mode 100644 index 00000000000..53bd495fd8d --- /dev/null +++ b/src/vs/base/common/path.ts @@ -0,0 +1,1689 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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 { isWindows } from 'vs/base/common/platform'; + +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; /* ? */ + +interface IProcess { + cwd(): string; + platform: string; + env: object; +} + +declare let process: IProcess; +const safeProcess: IProcess = (typeof process === 'undefined') ? { + cwd() { return '/'; }, + env: {}, + get platform() { return isWindows ? 'win32' : 'posix'; } +} : process; + +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; +} + +interface IExportedPath extends IPath { + win32: IPath; + posix: IPath; +} + +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 = safeProcess.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 = safeProcess.env['=' + resolvedDevice] || safeProcess.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; + } + + let 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) { + let 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 ''; + } + + let fromOrig = win32.resolve(from); + let 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; + } + } + let 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; + } + } + let toLen = (toEnd - toStart); + + // Compare paths to find the longest common path from root + let 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; + } + let fromCode = from.charCodeAt(fromStart + i); + let 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'); + + let ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) { + return ret; + } + + let 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 +}; + +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 = safeProcess.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) { + let 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; + } + } + let fromEnd = from.length; + let fromLen = (fromEnd - fromStart); + + // Trim any leading backslashes + let toStart = 1; + for (; toStart < to.length; ++toStart) { + if (to.charCodeAt(toStart) !== CHAR_FORWARD_SLASH) { + break; + } + } + let toEnd = to.length; + let toLen = (toEnd - toStart); + + // Compare paths to find the longest common path from root + let 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; + } + let fromCode = from.charCodeAt(fromStart + i); + let 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'); + + let ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) { + return ret; + } + let 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; + +const impl = (safeProcess.platform === 'win32' ? win32 : posix) as IExportedPath; +export = impl; diff --git a/src/vs/base/common/performance.js b/src/vs/base/common/performance.js index c7825a0e310..2bc77108620 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/resources.ts b/src/vs/base/common/resources.ts index 9d236790f5e..410af8dd5ad 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. 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'; @@ -32,22 +33,21 @@ 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); +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 +55,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 +77,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 +95,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 +125,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 +138,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 +147,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 +165,48 @@ 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 { + let 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 || '/'); } export function distinctParents(items: T[], resourceAccessor: (item: T) => URI): T[] { diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index c05da11b423..9dbae8ce1e2 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,32 @@ 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 { + let 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; } diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index fc6db66a8e4..fa19fff26e9 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 7b802137817..5ef2193997f 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/extfs.ts b/src/vs/base/node/extfs.ts index 08d2beab731..866ba70ddc9 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'; diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index e4f745fe762..28089f4aeab 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/processes.ts b/src/vs/base/node/processes.ts index 15de8c293f6..eeaa7c6bc44 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. 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'; @@ -168,7 +168,7 @@ 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) => { diff --git a/src/vs/base/node/stats.ts b/src/vs/base/node/stats.ts index aedbd13349e..4b89e868fe1 100644 --- a/src/vs/base/node/stats.ts +++ b/src/vs/base/node/stats.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { readdir, stat, exists, readFile } from 'fs'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { parse, ParseError } from 'vs/base/common/json'; export interface WorkspaceStatItem { diff --git a/src/vs/base/node/storage.ts b/src/vs/base/node/storage.ts index 4ba88e5ffb1..546396f1959 100644 --- a/src/vs/base/node/storage.ts +++ b/src/vs/base/node/storage.ts @@ -9,7 +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 { basename } from 'vs/base/common/path'; import { copy, renameIgnoreError, unlink } from 'vs/base/node/pfs'; import { fill } from 'vs/base/common/arrays'; 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 cec3c261038..f79bddaa98c 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 95% rename from src/vs/platform/node/zip.ts rename to src/vs/base/node/zip.ts index 5696d1dec62..cb9fbfbf25e 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. */ @@ -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/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 40ddff4c22d..08f2def654f 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -6,7 +6,7 @@ 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'; diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts index ed4b43d2680..74148e4a0fd 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts @@ -639,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(() => { @@ -873,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; } @@ -945,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/quickOpenScorer.ts b/src/vs/base/parts/quickopen/common/quickOpenScorer.ts index c1c1a82673e..ae2f38541cc 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'; @@ -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 }; } @@ -410,7 +410,7 @@ function doScoreItem(label: string, description: string | null, path: string | u 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; 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 1ecc9e83e42..47c51b87902 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 { @@ -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/treeDefaults.ts b/src/vs/base/parts/tree/browser/treeDefaults.ts index 8d30a79e501..f91ca2bcf84 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; } } @@ -553,7 +555,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/test/common/extpath.test.ts b/src/vs/base/test/common/extpath.test.ts new file mode 100644 index 00000000000..f5947d06593 --- /dev/null +++ b/src/vs/base/test/common/extpath.test.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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('normalize', () => { + assert.equal(extpath.normalizeWithSlashes(''), '.'); + assert.equal(extpath.normalizeWithSlashes('.'), '.'); + assert.equal(extpath.normalizeWithSlashes('.'), '.'); + assert.equal(extpath.normalizeWithSlashes('../../far'), '../../far'); + assert.equal(extpath.normalizeWithSlashes('../bar'), '../bar'); + assert.equal(extpath.normalizeWithSlashes('../far'), '../far'); + assert.equal(extpath.normalizeWithSlashes('./'), './'); + assert.equal(extpath.normalizeWithSlashes('./././'), './'); + assert.equal(extpath.normalizeWithSlashes('./ff/./'), 'ff/'); + assert.equal(extpath.normalizeWithSlashes('./foo'), 'foo'); + assert.equal(extpath.normalizeWithSlashes('/'), '/'); + assert.equal(extpath.normalizeWithSlashes('/..'), '/'); + assert.equal(extpath.normalizeWithSlashes('///'), '/'); + assert.equal(extpath.normalizeWithSlashes('//foo'), '/foo'); + assert.equal(extpath.normalizeWithSlashes('//foo//'), '/foo/'); + assert.equal(extpath.normalizeWithSlashes('/foo'), '/foo'); + assert.equal(extpath.normalizeWithSlashes('/foo/bar.test'), '/foo/bar.test'); + assert.equal(extpath.normalizeWithSlashes('\\\\\\'), '/'); + assert.equal(extpath.normalizeWithSlashes('c:/../ff'), 'c:/ff'); + assert.equal(extpath.normalizeWithSlashes('c:\\./'), 'c:/'); + assert.equal(extpath.normalizeWithSlashes('foo/'), 'foo/'); + assert.equal(extpath.normalizeWithSlashes('foo/../../bar'), '../bar'); + assert.equal(extpath.normalizeWithSlashes('foo/./'), 'foo/'); + assert.equal(extpath.normalizeWithSlashes('foo/./bar'), 'foo/bar'); + assert.equal(extpath.normalizeWithSlashes('foo//'), 'foo/'); + assert.equal(extpath.normalizeWithSlashes('foo//'), 'foo/'); + assert.equal(extpath.normalizeWithSlashes('foo//bar'), 'foo/bar'); + assert.equal(extpath.normalizeWithSlashes('foo//bar/far'), 'foo/bar/far'); + assert.equal(extpath.normalizeWithSlashes('foo/bar/../../far'), 'far'); + assert.equal(extpath.normalizeWithSlashes('foo/bar/../far'), 'foo/far'); + assert.equal(extpath.normalizeWithSlashes('foo/far/../../bar'), 'bar'); + assert.equal(extpath.normalizeWithSlashes('foo/far/../../bar'), 'bar'); + assert.equal(extpath.normalizeWithSlashes('foo/xxx/..'), 'foo'); + assert.equal(extpath.normalizeWithSlashes('foo/xxx/../bar'), 'foo/bar'); + assert.equal(extpath.normalizeWithSlashes('foo/xxx/./..'), 'foo'); + assert.equal(extpath.normalizeWithSlashes('foo/xxx/./../bar'), 'foo/bar'); + assert.equal(extpath.normalizeWithSlashes('foo/xxx/./bar'), 'foo/xxx/bar'); + assert.equal(extpath.normalizeWithSlashes('foo\\bar'), 'foo/bar'); + assert.equal(extpath.normalizeWithSlashes(null), null); + assert.equal(extpath.normalizeWithSlashes(undefined), undefined); + + // https://github.com/Microsoft/vscode/issues/7234 + assert.equal(extpath.joinWithSlashes('/home/aeschli/workspaces/vscode/extensions/css', './syntaxes/css.plist'), '/home/aeschli/workspaces/vscode/extensions/css/syntaxes/css.plist'); + }); + + 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('join', () => { + assert.equal(extpath.joinWithSlashes('.', 'bar'), 'bar'); + assert.equal(extpath.joinWithSlashes('../../foo/bar', '../../foo'), '../../foo'); + assert.equal(extpath.joinWithSlashes('../../foo/bar', '../bar/foo'), '../../foo/bar/foo'); + assert.equal(extpath.joinWithSlashes('../foo/bar', '../bar/foo'), '../foo/bar/foo'); + assert.equal(extpath.joinWithSlashes('/', 'bar'), '/bar'); + assert.equal(extpath.joinWithSlashes('//server/far/boo', '../file.txt'), '//server/far/file.txt'); + assert.equal(extpath.joinWithSlashes('/foo/', '/bar'), '/foo/bar'); + assert.equal(extpath.joinWithSlashes('\\\\server\\far\\boo', '../file.txt'), '//server/far/file.txt'); + assert.equal(extpath.joinWithSlashes('\\\\server\\far\\boo', './file.txt'), '//server/far/boo/file.txt'); + assert.equal(extpath.joinWithSlashes('\\\\server\\far\\boo', '.\\file.txt'), '//server/far/boo/file.txt'); + assert.equal(extpath.joinWithSlashes('\\\\server\\far\\boo', 'file.txt'), '//server/far/boo/file.txt'); + assert.equal(extpath.joinWithSlashes('file:///c/users/test', 'test'), 'file:///c/users/test/test'); + assert.equal(extpath.joinWithSlashes('file://localhost/c$/GitDevelopment/express', './settings'), 'file://localhost/c$/GitDevelopment/express/settings'); // unc + assert.equal(extpath.joinWithSlashes('file://localhost/c$/GitDevelopment/express', '.settings'), 'file://localhost/c$/GitDevelopment/express/.settings'); // unc + assert.equal(extpath.joinWithSlashes('foo', '/bar'), 'foo/bar'); + assert.equal(extpath.joinWithSlashes('foo', 'bar'), 'foo/bar'); + assert.equal(extpath.joinWithSlashes('foo', 'bar/'), 'foo/bar/'); + assert.equal(extpath.joinWithSlashes('foo/', '/bar'), 'foo/bar'); + assert.equal(extpath.joinWithSlashes('foo/', '/bar/'), 'foo/bar/'); + assert.equal(extpath.joinWithSlashes('foo/', 'bar'), 'foo/bar'); + assert.equal(extpath.joinWithSlashes('foo/bar', '../bar/foo'), 'foo/bar/foo'); + assert.equal(extpath.joinWithSlashes('foo/bar', './bar/foo'), 'foo/bar/bar/foo'); + assert.equal(extpath.joinWithSlashes('http://localhost/test', '../next'), 'http://localhost/next'); + assert.equal(extpath.joinWithSlashes('http://localhost/test', 'test'), 'http://localhost/test/test'); + }); + + 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 1a72d1bda6e..f8b2b55a452 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/path.test.ts b/src/vs/base/test/common/path.test.ts new file mode 100644 index 00000000000..f16f61a9bea --- /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 MIT License. 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 835e7e5d2ef..00000000000 --- 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 MIT License. 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/resources.test.ts b/src/vs/base/test/common/resources.test.ts index 7bb9721d124..c4fc9cc0f2e 100644 --- a/src/vs/base/test/common/resources.test.ts +++ b/src/vs/base/test/common/resources.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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 { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, isMalformedFileUri, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator } from 'vs/base/common/resources'; import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri'; import { isWindows } from 'vs/base/common/platform'; @@ -42,23 +42,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 +166,102 @@ 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'); + } + }); + 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 +280,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', () => { diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 038c475fd1d..d2fb152cfe5 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 63e4393926b..afb0a9c4b62 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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 { URI } from 'vs/base/common/uri'; import { canceled } from 'vs/base/common/errors'; @@ -49,7 +49,7 @@ export class DeferredPromise { } export function toResource(this: any, path: string) { - return URI.file(paths.join('C:\\', Buffer.from(this.test.fullTitle()).toString('base64'), path)); + return URI.file(extpath.joinWithSlashes('C:\\', 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 59df2e0d36f..05539350074 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 af88880bac9..f72dbbc4c30 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 100de754c29..fd044fac056 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 ee10d64b839..90a645e39bd 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 MIT License. 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'; @@ -722,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); @@ -951,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'); @@ -969,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'); @@ -987,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'); } @@ -1003,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 26c6c17a6a0..2f9d6268e6e 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/storage/storage.test.ts b/src/vs/base/test/node/storage/storage.test.ts index 16f71f8d208..67c918e9e77 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'; diff --git a/src/vs/base/test/node/utils.ts b/src/vs/base/test/node/utils.ts index 9bc3a59a16d..37190dadfd9 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/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 169de560b50..cf7a592fff8 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -311,6 +311,7 @@ export class IssueReporter extends Disposable { ipcRenderer.send('vscode:issuePerformanceInfoRequest'); } this.updatePreviewButtonState(); + this.setSourceOptions(); this.render(); }); @@ -342,7 +343,19 @@ export class IssueReporter extends Disposable { } this.addEventListener('issue-source', 'change', (e: Event) => { - const fileOnExtension = JSON.parse((e.target).value); + const value = (e.target).value; + const problemSourceHelpText = this.getElementById('problem-source-help-text')!; + if (value === '') { + this.issueReporterModel.update({ fileOnExtension: undefined, includeExtensions: false }); + show(problemSourceHelpText); + this.clearSearchResults(); + this.render(); + return; + } else { + hide(problemSourceHelpText); + } + + const fileOnExtension = JSON.parse(value); this.issueReporterModel.update({ fileOnExtension: fileOnExtension, includeExtensions: !fileOnExtension }); this.render(); @@ -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; @@ -662,6 +680,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 { @@ -676,7 +733,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')!; @@ -690,7 +746,6 @@ export class IssueReporter extends Disposable { hide(searchedExtensionsBlock); hide(settingsSearchResultsBlock); hide(problemSource); - hide(problemSourceHelpText); hide(extensionSelector); if (issueType === IssueType.Bug) { @@ -702,7 +757,6 @@ export class IssueReporter extends Disposable { show(extensionSelector); } else { show(extensionsBlock); - show(problemSourceHelpText); } descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} *`; @@ -718,7 +772,6 @@ export class IssueReporter extends Disposable { show(extensionSelector); } else { show(extensionsBlock); - show(problemSourceHelpText); } descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} *`; @@ -782,6 +835,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 72e1a81ff20..b9625ea066f 100644 --- a/src/vs/code/electron-browser/issue/issueReporterModel.ts +++ b/src/vs/code/electron-browser/issue/issueReporterModel.ts @@ -72,12 +72,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 { diff --git a/src/vs/code/electron-browser/issue/issueReporterPage.ts b/src/vs/code/electron-browser/issue/issueReporterPage.ts index 32619366e7f..70d9888849b 100644 --- a/src/vs/code/electron-browser/issue/issueReporterPage.ts +++ b/src/vs/code/electron-browser/issue/issueReporterPage.ts @@ -21,10 +21,9 @@ 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.")) + diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts index 1271ff2866f..edd44ec556f 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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'; diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts index c4fb1355b61..8d714e48d1d 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 931eeab83fd..3655374314c 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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'; diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts index ff502e8d463..c94a47612d3 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/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index ab864aa3432..13232577758 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -5,7 +5,7 @@ - + diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 84c258ca734..a90c87136cd 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -27,7 +27,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, @@ -42,7 +42,6 @@ bootstrapWindow.load([ onNodeCachedData.push(arguments); }; } - }, beforeRequire: function () { perf.mark('willLoadWorkbenchMain'); @@ -56,10 +55,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-main/app.ts b/src/vs/code/electron-main/app.ts index 02729fd6138..d20380c7d4a 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -63,8 +63,9 @@ 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 { joinWithSlashes } from 'vs/base/common/extpath'; import { homedir } from 'os'; +import { 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'; @@ -154,7 +155,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 @@ -395,7 +396,7 @@ export class CodeApplication extends Disposable { recordingStopped = true; // only once - contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => { + contentTracing.stopRecording(joinWithSlashes(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => { if (!timeout) { this.windowsMainService.showMessageBox({ type: 'info', @@ -685,7 +686,7 @@ export class CodeApplication extends Disposable { this._disposeRunner = new RunOnceScheduler(() => this.dispose(), 5000); } - public dispose(): void { + dispose(): void { this._disposeRunner.dispose(); connectionPool.delete(this._authority); this._client.then((connection) => { @@ -693,7 +694,7 @@ export class CodeApplication extends Disposable { }); } - public getClient(): Promise> { + getClient(): Promise> { this._disposeRunner.schedule(); return this._client; } diff --git a/src/vs/code/electron-main/logUploader.ts b/src/vs/code/electron-main/logUploader.ts index ed1a038a81b..c335d0cb77c 100644 --- a/src/vs/code/electron-main/logUploader.ts +++ b/src/vs/code/electron-main/logUploader.ts @@ -6,8 +6,8 @@ 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 { IRequestService } from 'vs/platform/request/node/request'; diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 03d22708966..46eae2f76ad 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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'; @@ -24,7 +24,6 @@ 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'; export interface IWindowCreationOptions { @@ -87,7 +86,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(); @@ -590,7 +588,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); diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 60fed9a17bf..749cd6b4116 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. 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'; @@ -34,8 +34,7 @@ import { normalizeNFC } from 'vs/base/common/normalization'; import { URI } from 'vs/base/common/uri'; import { Queue, timeout } from 'vs/base/common/async'; import { exists } from 'vs/base/node/pfs'; -import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, fsPath } 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'; @@ -511,7 +510,7 @@ 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 lastActiveWindow = authority ? this.getLastActiveWindowForAuthority(authority) : undefined; + const lastActiveWindow = this.getLastActiveWindowForAuthority(authority); if (lastActiveWindow) { usedWindows.push(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd)); } @@ -531,7 +530,7 @@ export class WindowsManager implements IWindowsMainService { newWindow: openFilesInNewWindow, context: openConfig.context, fileUri: fileToCheck && fileToCheck.fileUri, - workspaceResolver: workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveWorkspaceSync(fsPath(workspace.configPath)) : null + localWorkspaceResolver: workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath) : null }); // We found a window to open the files in @@ -762,8 +761,6 @@ export class WindowsManager implements IWindowsMainService { } private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] { - debugger; - let windowsToOpen: IPathToOpen[]; let isCommandLineOrAPICall = false; @@ -981,13 +978,8 @@ export class WindowsManager implements IWindowsMainService { // remove trailing slash - const uriPath = uri.path; - - if (endsWith(uriPath, '/')) { - if (uriPath.length > 2) { - // only remove if the path has some content - uri = uri.with({ path: uriPath.substr(0, uriPath.length - 1) }); - } + if (hasTrailingPathSeparator(uri)) { + uri = removeTrailingPathSeparator(uri); if (!typeHint) { typeHint = 'folder'; } @@ -1051,7 +1043,7 @@ export class WindowsManager implements IWindowsMainService { // Workspace (unless disabled via flag) if (!options.forceOpenWorkspaceAsFile) { - const workspace = this.workspacesMainService.resolveWorkspaceSync(candidate); + const workspace = this.workspacesMainService.resolveLocalWorkspaceSync(URI.file(candidate)); if (workspace) { return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority }; } @@ -1158,7 +1150,7 @@ export class WindowsManager implements IWindowsMainService { } } else { if (workspaceToOpen.configPath.scheme === Schemas.file) { - cliArgs = [fsPath(workspaceToOpen.configPath)]; + cliArgs = [originalFSPath(workspaceToOpen.configPath)]; } else { fileUris = [workspaceToOpen.configPath.toString()]; } @@ -1552,7 +1544,7 @@ export class WindowsManager implements IWindowsMainService { return getLastActiveWindow(WindowsManager.WINDOWS); } - getLastActiveWindowForAuthority(remoteAuthority: string): ICodeWindow | undefined { + getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined { return getLastActiveWindow(WindowsManager.WINDOWS.filter(w => w.remoteAuthority === remoteAuthority)); } @@ -2128,7 +2120,7 @@ class WorkspacesManager { return workspace.scheme === Schemas.file ? dirname(workspace.fsPath) : undefined; } - const resolvedWorkspace = workspace.configPath.scheme === Schemas.file && this.workspacesMainService.resolveWorkspaceSync(workspace.configPath.fsPath); + const resolvedWorkspace = workspace.configPath.scheme === Schemas.file && this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath); if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) { for (const folder of resolvedWorkspace.folders) { if (folder.uri.scheme === Schemas.file) { diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index d98a850e585..9969275a53b 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -9,7 +9,7 @@ import { buildHelpMessage, buildVersionMessage } from 'vs/platform/environment/n 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 * as paths from 'vs/base/common/path'; import * as os from 'os'; import * as fs from 'fs'; import { whenDeleted } from 'vs/base/node/pfs'; diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 98b1c6dfe3f..ce7a72c6dd8 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -6,7 +6,7 @@ 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 * as path from 'vs/base/common/path'; import * as semver from 'semver'; import { sequence } from 'vs/base/common/async'; @@ -39,6 +39,8 @@ import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionId } f 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 { isUIExtension } from 'vs/platform/extensions/node/extensionsUtil'; +import { CancellationToken } from 'vs/base/common/cancellation'; 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,9 @@ export function getIdAndVersion(id: string): [string, string | undefined] { export class Main { constructor( + private readonly remote: boolean, @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService ) { } @@ -112,26 +116,30 @@ export class Main { 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 && 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(() => { + 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); } - return null; }); + } + return null; } const [id, version] = getIdAndVersion(extension); @@ -148,11 +156,17 @@ 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 && 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) { @@ -177,9 +191,7 @@ export class Main { - 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'); } @@ -290,7 +302,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 678b91022ae..9154dbb27b5 100644 --- a/src/vs/code/node/paths.ts +++ b/src/vs/code/node/paths.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. 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/wait.ts b/src/vs/code/node/wait.ts index 6245e052a73..9f9b9aaa41d 100644 --- a/src/vs/code/node/wait.ts +++ b/src/vs/code/node/wait.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; import { writeFile } from 'vs/base/node/pfs'; diff --git a/src/vs/code/node/windowsFinder.ts b/src/vs/code/node/windowsFinder.ts index 99c06e09b4e..aa76820be2f 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'; @@ -25,10 +25,10 @@ export interface IBestWindowOrFolderOptions { fileUri?: URI; userHome?: string; codeSettingsFolder?: string; - workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null; + localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null; } -export function findBestWindowOrFolderForFile({ windows, newWindow, context, fileUri, workspaceResolver }: IBestWindowOrFolderOptions): W | undefined { +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) { @@ -38,15 +38,15 @@ export function findBestWindowOrFolderForFile({ windows 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 for (const window of windows) { const workspace = window.openedWorkspace; if (workspace) { - const resolvedWorkspace = workspaceResolver(workspace); + const resolvedWorkspace = localWorkspaceResolver(workspace); if (resolvedWorkspace) { - // workspace cpuld be resolved: It's in the local file system + // workspace could be resolved: It's in the local file system if (resolvedWorkspace.folders.some(folder => isEqualOrParent(fileUri, folder.uri))) { return window; } @@ -98,7 +98,7 @@ 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; } } diff --git a/src/vs/code/test/electron-main/windowsStateStorage.test.ts b/src/vs/code/test/electron-main/windowsStateStorage.test.ts index 8a82e33e1e4..d87d855a36b 100644 --- a/src/vs/code/test/electron-main/windowsStateStorage.test.ts +++ b/src/vs/code/test/electron-main/windowsStateStorage.test.ts @@ -4,7 +4,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 { restoreWindowsState, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; import { IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows'; diff --git a/src/vs/code/test/node/windowsFinder.test.ts b/src/vs/code/test/node/windowsFinder.test.ts index 79d476c53d0..e18521facc4 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 MIT License. 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'; @@ -24,7 +24,7 @@ function options(custom?: Partial>): I newWindow: 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 }; } diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index 1fb8d7df97d..b6629ad43a7 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -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', + } + } + } } ] }; @@ -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'] + } + } + } } ] }; @@ -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 } }); } @@ -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; } @@ -1767,12 +1802,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/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index 33927be7765..9411568fc79 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/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index 0e9eec2e01c..bb087386420 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -193,7 +193,11 @@ export class ViewController { } } 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); } 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 094c6acfbea..1d1cda0e93e 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 4dee2761704..7f64457f78b 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 706d2e2ba23..da79a9547db 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 6753e028781..0add3031fb5 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/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index b88b781dbb7..f529a258edc 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -371,6 +371,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'], @@ -832,6 +837,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 d8ee1d9226b..7802259669d 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -85,6 +85,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 @@ -894,6 +898,7 @@ export interface InternalEditorMinimapOptions { export interface InternalEditorFindOptions { readonly seedSearchStringFromSelection: boolean; readonly autoFindInSelection: boolean; + readonly addExtraSpaceOnTop: boolean; /** * @internal */ @@ -1323,6 +1328,7 @@ export class InternalEditorOptions { a.seedSearchStringFromSelection === b.seedSearchStringFromSelection && a.autoFindInSelection === b.autoFindInSelection && a.globalFindClipboard === b.globalFindClipboard + && a.addExtraSpaceOnTop === b.addExtraSpaceOnTop ); } @@ -1851,7 +1857,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) }; } @@ -2660,7 +2667,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/cursorMoveCommands.ts b/src/vs/editor/common/controller/cursorMoveCommands.ts index 493611967cd..938d59028a4 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/modes.ts b/src/vs/editor/common/modes.ts index 4bad77f0289..b864e628aa8 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 @@ -667,7 +668,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 @@ -914,7 +915,10 @@ export interface FormattingOptions { */ export interface DocumentFormattingEditProvider { - displayName?: string; + /** + * @internal + */ + readonly extensionId?: ExtensionIdentifier; /** * Provide formatting edits for a whole document. @@ -927,7 +931,11 @@ export interface DocumentFormattingEditProvider { */ export interface DocumentRangeFormattingEditProvider { - displayName?: string; + + /** + * @internal + */ + readonly extensionId?: ExtensionIdentifier; /** * Provide formatting edits for a range in a document. @@ -943,7 +951,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 +983,7 @@ export interface IInplaceReplaceSupportResult { */ export interface ILink { range: IRange; - url?: string; + url?: URI | string; } /** * A provider of links. @@ -1064,7 +1080,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 { @@ -1234,7 +1250,10 @@ export interface NewCommentAction { */ export interface CommentReaction { readonly label?: string; + readonly iconPath?: UriComponents; + readonly count?: number; readonly hasReacted?: boolean; + readonly canEdit?: boolean; } /** diff --git a/src/vs/editor/common/modes/languageSelector.ts b/src/vs/editor/common/modes/languageSelector.ts index b878e2232c7..4cf93dc4d23 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/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index ed3a564f52e..4ef83f744a7 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; @@ -599,7 +600,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 +613,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/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index e6015b5d02b..226893ae849 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -83,7 +83,7 @@ function codeActionsComparator(a: CodeAction, b: CodeAction): number { } registerLanguageCommand('_executeCodeActionProvider', function (accessor, args) { - const { resource, range } = args; + const { resource, range, kind } = args; if (!(resource instanceof URI) || !Range.isIRange(range)) { throw illegalArgument(); } @@ -96,6 +96,6 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args) return getCodeActions( model, model.validateRange(range), - { type: 'manual', filter: { includeSourceActions: true } }, + { type: 'manual', filter: { includeSourceActions: true, kind: kind ? new CodeActionKind(kind) : undefined } }, CancellationToken.None); }); diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 7777aded9f6..76e5dd2404f 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -253,7 +253,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 +317,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 +372,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'] + } + } + } + }] } }); } diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index ff79983d255..5673569e465 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -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); diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts index 34bf98be6b7..3522fc39320 100644 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ b/src/vs/editor/contrib/codeAction/codeActionWidget.ts @@ -16,9 +16,9 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView 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, @@ -69,7 +69,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/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 5cda01790a3..924c840ea3f 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -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 9fcc411fe63..e9f8892a1d2 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -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; } @@ -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/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index 35ece46a3aa..5b4abbb22d5 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -81,7 +81,6 @@ .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 { diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 2c5e17df77b..34b5d0e7f59 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -110,7 +110,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _findInputFocused: IContextKey; private _replaceFocusTracker: dom.IFocusTracker; private _replaceInputFocused: IContextKey; - private _viewZone: FindWidgetViewZone; + private _viewZone?: FindWidgetViewZone; private _viewZoneId?: number; private _resizeSash: Sash; @@ -160,6 +160,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 +208,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 +447,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 +481,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 +525,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 +543,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), @@ -825,7 +853,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } } } else { - this._state.change({ searchScope: undefined }, true); + this._state.change({ searchScope: null }, true); } } })); @@ -835,7 +863,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)) { diff --git a/src/vs/editor/contrib/find/simpleFindWidget.css b/src/vs/editor/contrib/find/simpleFindWidget.css index 18fedb114fc..078eb737c4b 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; } diff --git a/src/vs/editor/contrib/find/simpleFindWidget.ts b/src/vs/editor/contrib/find/simpleFindWidget.ts index d05a477931e..9eb65759b17 100644 --- a/src/vs/editor/contrib/find/simpleFindWidget.ts +++ b/src/vs/editor/contrib/find/simpleFindWidget.ts @@ -204,6 +204,7 @@ 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(); @@ -220,16 +221,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/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index d06e204813e..d85f6bfbc75 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -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))); @@ -519,7 +519,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 +604,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/format/format.ts b/src/vs/editor/contrib/format/format.ts index 401a6fc3fc0..49c2198bf18 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -8,68 +8,162 @@ 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 } + } + */ + telemetryService.publicLog('formatterInfo', { + type: 'range', + language: model.getLanguageIdentifier().language, + count: providers.length, + }); + + 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 } + } + */ + telemetryService.publicLog('formatterInfo', { + type: 'document', + language: model.getLanguageIdentifier().language, + count: docFormattingProviders.length, + }); + + 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 } + } + */ + telemetryService.publicLog('formatterInfo', { + type: 'ontype', + language: model.getLanguageIdentifier().language, + count: providers.length, + }); - // 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 +175,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 +188,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 429ea13fa61..ffc30fdd8a9 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 @@ -281,28 +215,32 @@ 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, + { + tabSize: modelOpts.tabSize, + insertSpaces: modelOpts.insertSpaces + }).then(edits => { - unbind.dispose(); + unbind.dispose(); - if (canceled) { - return; - } + if (canceled) { + return; + } - if (isNonEmptyArray(edits)) { - FormattingEdit.execute(this.editor, edits); - alertFormattingEdits(edits); - } + if (isNonEmptyArray(edits)) { + FormattingEdit.execute(this._editor, edits); + alertFormattingEdits(edits); + } - }, (err) => { - unbind.dispose(); - throw err; - }); + }, (err) => { + unbind.dispose(); + throw err; + }); } public getId(): string { @@ -310,8 +248,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); } } @@ -414,15 +352,10 @@ 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, { tabSize, insertSpaces }, CancellationToken.None); } } @@ -451,15 +384,10 @@ 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, { tabSize, insertSpaces }, CancellationToken.None); } } @@ -485,38 +413,3 @@ CommandsRegistry.registerCommand('editor.action.format', accessor => { return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Selection, { tabSize, insertSpaces }, 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/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index f6feacb8517..2653086426b 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -8,6 +8,7 @@ import * as nls from 'vs/nls'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import * as platform from 'vs/base/common/platform'; import { IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -263,7 +264,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 === platform.AccessibilitySupport.Enabled; + controller.showContentHover(range, HoverStartMode.Immediate, focus); } } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 1d911e306e9..a83521453ea 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -25,7 +25,7 @@ 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 { 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'; @@ -488,7 +488,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { 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})`; + a.innerText = `${basename(resource)}(${startLineNumber}, ${startColumn})`; a.style.cursor = 'pointer'; a.onclick = e => { e.stopPropagation(); diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index 5a75d3db2e5..933fd3f3121 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -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 d224f2254ad..972a60cd4c5 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 { @@ -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/referenceSearch/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/referencesModel.ts index da7677a01d5..4243491a8b4 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 ); } } @@ -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 8866c65be81..2a8388900e8 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesTree.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesTree.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. 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 60500684060..4fd0bbebf59 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -543,7 +543,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 71a9f87fb74..460fcc040c2 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -282,7 +282,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 c69442aaf73..6bd8d884235 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 66d7733573b..63e1579a348 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'; @@ -53,7 +54,7 @@ class SmartSelectController implements IEditorContribution { private readonly _editor: ICodeEditor; - private _state?: SelectionRanges; + private _state?: SelectionRanges[]; private _selectionListener?: IDisposable; private _ignoreSelection: boolean = false; @@ -74,7 +75,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 +86,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 +124,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 +210,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)); } } } - })); - } + } + })); } 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 43b94bd2d05..de3a2ed6a75 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 6c6c1ffd65b..1dcabbd65c9 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/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index 9545c299890..dd7591c79ab 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 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'; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index fc3f3d8f62a..967352f6303 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -63,8 +63,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 +164,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 diff --git a/src/vs/editor/contrib/suggest/wordDistance.ts b/src/vs/editor/contrib/suggest/wordDistance.ts index 1ab1cddad5a..c076d21a0b3 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 036f26483fb..2215346fca3 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -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) { diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index e92924064ef..476a17010df 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -369,7 +369,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { keyboardEvent.altKey, keyboardEvent.metaKey, keyboardEvent.keyCode - ); + ).toChord(); return new USLayoutResolvedKeybinding(keybinding, OS); } @@ -576,7 +576,7 @@ export class SimpleBulkEditService implements IBulkEditService { // } - apply(workspaceEdit: WorkspaceEdit, options: IBulkEditOptions): Promise { + apply(workspaceEdit: WorkspaceEdit, options?: IBulkEditOptions): Promise { let edits = new Map(); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 35793ae2e20..6ad774afecc 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2433,6 +2433,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; } /** @@ -3169,6 +3170,7 @@ declare namespace monaco.editor { export interface InternalEditorFindOptions { readonly seedSearchStringFromSelection: boolean; readonly autoFindInSelection: boolean; + readonly addExtraSpaceOnTop: boolean; } export interface InternalEditorHoverOptions { @@ -4968,7 +4970,7 @@ declare namespace monaco.languages { /** * The highlight kind, default is [text](#DocumentHighlightKind.Text). */ - kind: DocumentHighlightKind; + kind?: DocumentHighlightKind; } /** @@ -5167,7 +5169,6 @@ declare namespace monaco.languages { * the formatting-feature. */ export interface DocumentFormattingEditProvider { - displayName?: string; /** * Provide formatting edits for a whole document. */ @@ -5179,7 +5180,6 @@ declare namespace monaco.languages { * the formatting-feature. */ export interface DocumentRangeFormattingEditProvider { - displayName?: string; /** * Provide formatting edits for a range in a document. * @@ -5211,7 +5211,7 @@ declare namespace monaco.languages { */ export interface ILink { range: IRange; - url?: string; + url?: Uri | string; } /** @@ -5303,7 +5303,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/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index af82955b242..c192571d5b5 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ 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'; @@ -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'; @@ -252,7 +252,6 @@ export class BackupMainService implements IBackupMainService { // Validate Workspaces for (let workspace of rootWorkspaces) { if (!isWorkspaceIdentifier(workspace)) { - console.log('not a workspace identifer'); return []; // wrong format, skip all entries } @@ -267,12 +266,10 @@ export class BackupMainService implements IBackupMainService { if (workspace.configPath.scheme !== Schemas.file || await exists(workspace.configPath.fsPath)) { result.push(workspace); } else { - console.log('target workspace missing'); // If the workspace has backups, but the target workspace is missing, convert backups to empty ones await this.convertToEmptyWindowBackup(backupPath); } } else { - console.log('no backups'); await this.deleteStaleBackup(backupPath); } } 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 077133574f3..feefaef22d0 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -7,7 +7,7 @@ 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, URI } from 'vs/base/common/uri'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index 16c3a764a02..91541847a17 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; } diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index 21a79757768..e3c45c59f28 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 71deee86ec4..6617af20f86 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -42,32 +42,32 @@ export abstract class ContextKeyExpr { return new ContextKeyAndExpr(expr); } - 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)); } if (/^\!\s*/.test(serializedOne)) { @@ -77,7 +77,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') { @@ -96,17 +96,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; } @@ -115,7 +123,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; } } diff --git a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts index 1eb77972c18..a539a7cabf4 100644 --- a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts @@ -13,7 +13,7 @@ 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 } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 9daa60dc2c7..5f4ebaa0c83 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/download/node/downloadIpc.ts b/src/vs/platform/download/node/downloadIpc.ts index 10210cfb5b0..53144c3f1db 100644 --- a/src/vs/platform/download/node/downloadIpc.ts +++ b/src/vs/platform/download/node/downloadIpc.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 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 { Event, Emitter } from 'vs/base/common/event'; diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index deab8063bb3..b84856fb0f7 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -107,7 +107,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { } 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/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 933f56cdc17..48953aa64e7 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 93f117ad29e..c59029b0e3d 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -62,7 +62,7 @@ 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; @@ -109,7 +109,7 @@ export interface IEnvironmentService { backupHome: string; backupWorkspacesPath: string; - workspacesHome: string; + untitledWorkspacesHome: URI; isExtensionDevelopment: boolean; disableExtensions: boolean | string[]; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 054a7fe2c61..7a304566aef 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -61,7 +61,7 @@ export const options: Option[] = [ { 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' }, diff --git a/src/vs/platform/environment/node/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index a69fda7a371..b5db5eba7f0 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -17,7 +17,8 @@ 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.`); + console.log(parseInt(args['max-memory'])); + 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; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 8c33dbaec1e..9427179597d 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -7,7 +7,7 @@ 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'; @@ -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'); } diff --git a/src/vs/platform/environment/test/node/environmentService.test.ts b/src/vs/platform/environment/test/node/environmentService.test.ts index fc1faf3fff7..b465072e7fa 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'; diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index d4f636b463f..c6d2d394344 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'; diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts index 5d98b943253..57b5112f8f4 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/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index ab6982abed3..746461c2e18 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, @@ -44,7 +44,7 @@ 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'; @@ -345,7 +345,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))); } } @@ -472,7 +472,7 @@ 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 => pfs.rimraf(extractPath).finally(() => null) @@ -516,7 +516,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; } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/node/extensionManagementUtil.ts index a837b87c821..56931eaa41b 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementUtil.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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 680419cf182..72cffa02dc2 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/node/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts index 2d494965163..1b9af7cde94 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts @@ -9,7 +9,7 @@ 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 { join } from 'vs/base/common/path'; import { mkdirp } from 'vs/base/node/pfs'; import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { isUUID } from 'vs/base/common/uuid'; diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index cb8566333b3..ee0a9aed604 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -227,3 +227,7 @@ export class ExtensionIdentifier { return id._lower; } } + +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/files/common/files.ts b/src/vs/platform/files/common/files.ts index fce0e320b86..fa87bbd829f 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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'; @@ -385,8 +385,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 +419,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. diff --git a/src/vs/platform/files/test/files.test.ts b/src/vs/platform/files/test/files.test.ts index ff67a7ea552..638839b094e 100644 --- a/src/vs/platform/files/test/files.test.ts +++ b/src/vs/platform/files/test/files.test.ts @@ -5,23 +5,23 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { join, isEqual, isEqualOrParent } from 'vs/base/common/paths'; +import { joinWithSlashes, 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'; suite('Files', () => { function toResource(path) { - return URI.file(join('C:\\', path)); + return URI.file(joinWithSlashes('C:\\', path)); } test('FileChangesEvent', () => { 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: URI.file(joinWithSlashes('C:\\', '/foo/updated.txt')), type: FileChangeType.UPDATED }, + { resource: URI.file(joinWithSlashes('C:\\', '/foo/otherupdated.txt')), type: FileChangeType.UPDATED }, + { resource: URI.file(joinWithSlashes('C:\\', '/added.txt')), type: FileChangeType.ADDED }, + { resource: URI.file(joinWithSlashes('C:\\', '/bar/deleted.txt')), type: FileChangeType.DELETED }, + { resource: URI.file(joinWithSlashes('C:\\', '/bar/folder')), type: FileChangeType.DELETED } ]; let r1 = new FileChangesEvent(changes); diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 4e854d1a1e5..156e85b9f8f 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -14,9 +14,9 @@ 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 { isEqual } from 'vs/base/common/extpath'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { getComparisonKey, isEqual as areResourcesEqual, dirname, fsPath } from 'vs/base/common/resources'; +import { getComparisonKey, 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'; @@ -169,12 +169,12 @@ export class HistoryMainService implements IHistoryMainService { const workspace = mru.workspaces[i]; if (isSingleFolderWorkspaceIdentifier(workspace)) { if (workspace.scheme === Schemas.file) { - app.addRecentDocument(fsPath(workspace)); + app.addRecentDocument(originalFSPath(workspace)); entries++; } } else { if (workspace.configPath.scheme === Schemas.file) { - app.addRecentDocument(fsPath(workspace.configPath)); + app.addRecentDocument(originalFSPath(workspace.configPath)); entries++; } } @@ -185,7 +185,7 @@ export class HistoryMainService implements IHistoryMainService { 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(fsPath(file)); + app.addRecentDocument(originalFSPath(file)); entries++; } } @@ -289,12 +289,12 @@ export class HistoryMainService implements IHistoryMainService { 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); + const title = 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"); diff --git a/src/vs/platform/history/test/electron-main/historyStorage.test.ts b/src/vs/platform/history/test/electron-main/historyStorage.test.ts index 79e354a9471..bf7f3acaaa4 100644 --- a/src/vs/platform/history/test/electron-main/historyStorage.test.ts +++ b/src/vs/platform/history/test/electron-main/historyStorage.test.ts @@ -4,7 +4,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 { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/platform/keybinding/common/baseResolvedKeybinding.ts b/src/vs/platform/keybinding/common/baseResolvedKeybinding.ts new file mode 100644 index 00000000000..cae3bd603da --- /dev/null +++ b/src/vs/platform/keybinding/common/baseResolvedKeybinding.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index 57bf20e8a73..bfa00350ebd 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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'; @@ -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 173456244ac..5df9723f091 100644 --- a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts +++ b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts @@ -22,9 +22,18 @@ 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; + const dispatchParts = resolvedKeybinding.getDispatchParts(); + // TODO@chords: add support for dispatching N chords here + if (dispatchParts.length >= 2) { + this.keypressFirstPart = dispatchParts[0]; + this.keypressChordPart = dispatchParts[1]; + } else if (dispatchParts.length === 1) { + this.keypressFirstPart = dispatchParts[0]; + this.keypressChordPart = null; + } else { + this.keypressFirstPart = null; + this.keypressChordPart = null; + } } else { this.keypressFirstPart = null; this.keypressChordPart = null; diff --git a/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts b/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts index e55a2961797..5a24412035a 100644 --- a/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts +++ b/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts @@ -3,31 +3,17 @@ * Licensed under the MIT License. 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 accb7f54d4c..b79fd79c7f1 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]; } diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index 4cabf4af10a..e9cb10c3f63 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 MIT License. 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 91721a6b0cf..92065a6766b 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[] { diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index 87746ef28af..6322da863b5 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -43,12 +43,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 basename(workspace); } // Workspace: Untitled - if (isEqualOrParent(workspace.configPath, URI.file(workspaceHome))) { + 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 aa77f122c84..7a3a44f41e7 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -19,7 +19,6 @@ import { BrowserWindow } from 'electron'; import { Event } from 'vs/base/common/event'; import { hasArgs } from 'vs/platform/environment/node/argv'; import { coalesce } from 'vs/base/common/arrays'; -import { Schemas } from 'vs/base/common/network'; export const ID = 'launchService'; export const ILaunchService = createDecorator(ID); @@ -273,7 +272,7 @@ export class LaunchService implements ILaunchService { } else if (window.openedWorkspace) { // workspace folders can only be shown for local workspaces const workspaceConfigPath = window.openedWorkspace.configPath; - const resolvedWorkspace = workspaceConfigPath.scheme === Schemas.file && this.workspacesMainService.resolveWorkspaceSync(workspaceConfigPath.fsPath); + const resolvedWorkspace = this.workspacesMainService.resolveLocalWorkspaceSync(workspaceConfigPath); if (resolvedWorkspace) { const rootFolders = resolvedWorkspace.folders; rootFolders.forEach(root => { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index aee424a621b..1f89c13f399 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -707,14 +707,16 @@ export class TreeResourceNavigator2 extends Disposable { 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); } } diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index faa6e4906d2..1deb73c4cab 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -16,7 +16,7 @@ import product from 'vs/platform/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; @@ -105,7 +105,7 @@ class LanguagePacksCache extends Disposable { @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(); } @@ -151,7 +151,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); } } } diff --git a/src/vs/platform/log/node/spdlogService.ts b/src/vs/platform/log/node/spdlogService.ts index 86eba7a7f34..76cb0865e56 100644 --- a/src/vs/platform/log/node/spdlogService.ts +++ b/src/vs/platform/log/node/spdlogService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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/node/package.ts b/src/vs/platform/node/package.ts index 93c32bc7117..d39c5877d6a 100644 --- a/src/vs/platform/node/package.ts +++ b/src/vs/platform/node/package.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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/node/product.ts index 08b8b607e2b..00494b7e39a 100644 --- a/src/vs/platform/node/product.ts +++ b/src/vs/platform/node/product.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 4f6d33febf1..f19d33f8318 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -114,7 +114,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 { diff --git a/src/vs/platform/remote/common/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index 0114939f09b..77f32902f72 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/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index 26f7760e74c..412ea4da01a 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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 a3b5db21684..abb30e484a9 100644 --- a/src/vs/platform/state/test/node/state.test.ts +++ b/src/vs/platform/state/test/node/state.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 extfs from 'vs/base/node/extfs'; import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices'; import { FileStorage } from 'vs/platform/state/node/stateService'; diff --git a/src/vs/platform/storage/node/storageMainService.ts b/src/vs/platform/storage/node/storageMainService.ts index d6227c98e36..858acbf945f 100644 --- a/src/vs/platform/storage/node/storageMainService.ts +++ b/src/vs/platform/storage/node/storageMainService.ts @@ -9,7 +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 { join } from 'vs/base/common/path'; import { mark } from 'vs/base/common/performance'; import { exists, readdir } from 'vs/base/node/pfs'; import { Database } from 'vscode-sqlite3'; diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 79d189e43a6..6b232493c2c 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'; diff --git a/src/vs/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index 109678bddb8..618892479eb 100644 --- a/src/vs/platform/storage/test/node/storageService.test.ts +++ b/src/vs/platform/storage/test/node/storageService.test.ts @@ -8,7 +8,7 @@ import { StorageScope } from 'vs/platform/storage/common/storage'; import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; 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'; diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index b0dcafe6568..fe6540c6291 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -5,7 +5,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { guessMimeTypes } from 'vs/base/common/mime'; -import * as paths from 'vs/base/common/paths'; +import { extname } from 'vs/base/common/path'; 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'; @@ -79,7 +79,7 @@ export interface URIDescriptor { 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) } : {}; + return fsPath ? { mimeType: guessMimeTypes(fsPath).join(', '), scheme: uri.scheme, ext: extname(fsPath), path: hashPath(fsPath) } : {}; } /** 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 ecc735dc7dd..92f60d3c088 100644 --- a/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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'; diff --git a/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts b/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts index 1e92b4d8608..32f092e1c11 100644 --- a/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts +++ b/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts @@ -14,7 +14,7 @@ import { buttonBackground } from 'vs/workbench/contrib/welcome/page/electron-bro import { embeddedEditorBackground } from 'vs/workbench/contrib/welcome/walkThrough/electron-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/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index a1afe880e9a..a85227b9684 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -15,7 +15,7 @@ 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 * as path from 'vs/base/common/path'; import { spawn } from 'child_process'; import { realpath } from 'fs'; diff --git a/src/vs/platform/update/electron-main/updateService.snap.ts b/src/vs/platform/update/electron-main/updateService.snap.ts index bcbb7f22817..6f5040901c8 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'; diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index e94091c31e7..f4384e5a4ac 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 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'; diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index f86580a7d73..7447410922f 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -395,7 +395,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 179df0df102..1a1cffb8839 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -18,20 +18,23 @@ 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 ) { 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); diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index e2488422d95..418d131224e 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'; @@ -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 29aa68c5325..c704c3706e9 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -8,7 +8,14 @@ import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { extname } from 'vs/base/common/paths'; +import { isEqualOrParent, normalizeWithSlashes } from 'vs/base/common/extpath'; +import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; +import { isAbsolute, relative, posix, resolve, extname } from 'vs/base/common/path'; +import { normalizeDriveLetter } from 'vs/base/common/labels'; +import { originalFSPath, 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'; export const IWorkspacesMainService = createDecorator('workspacesMainService'); export const IWorkspacesService = createDecorator('workspacesService'); @@ -88,7 +95,7 @@ export interface IWorkspacesMainService extends IWorkspacesService { createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier; - resolveWorkspaceSync(path: string): IResolvedWorkspace | null; + resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null; isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean; @@ -145,3 +152,115 @@ const WORKSPACE_SUFFIX = '.' + WORKSPACE_EXTENSION; export function hasWorkspaceFileExtension(path: string) { return extname(path) === WORKSPACE_SUFFIX; } + +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 = originalFSPath(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 = normalizeWithSlashes(absoluteFolderPath /* 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(originalFSPath(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; +} \ No newline at end of file diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index e4d66cbac8e..e30bea97d8c 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -3,25 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkspacesMainService, IWorkspaceIdentifier, hasWorkspaceFileExtension, 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, massageFolderPathForWorkspace, rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/common/workspaces'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { join, dirname } from 'path'; +import { join, dirname } from 'vs/base/common/path'; import { mkdirp, writeFile, readFile } 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 { isEqual } from 'vs/base/common/extpath'; 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[]; @@ -31,7 +29,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain _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; } @@ -42,26 +40,29 @@ 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) || hasWorkspaceFileExtension(path); + private isWorkspacePath(uri: URI): boolean { + return this.isInsideWorkspacesHome(uri) || hasWorkspaceFileExtension(uri.path); } private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null { @@ -71,7 +72,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain return { id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath, - folders: toWorkspaceFolders(workspace.folders, resourcesDirname(path)!) + folders: toWorkspaceFolders(workspace.folders, resourcesDirname(path)) }; } catch (error) { this.logService.warn(error.toString()); @@ -98,36 +99,41 @@ 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); + const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders); + const configPath = workspace.configPath.fsPath; - return mkdirp(configParent).then(() => { - return writeFile(workspace.configPath.fsPath, 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); + const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders); + 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.fsPath, 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[] = []): { 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 => { @@ -136,7 +142,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain // File URI if (folderResource.scheme === Schemas.file) { - storedWorkspace = { path: massageFolderPathForWorkspace(fsPath(folderResource), URI.file(untitledWorkspaceConfigFolder), []) }; + storedWorkspace = { path: massageFolderPathForWorkspace(originalFSPath(folderResource), untitledWorkspaceConfigFolder, []) }; } // Any URI @@ -153,14 +159,13 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain }; return { - workspace: this.getWorkspaceIdentifier(URI.file(untitledWorkspaceConfigPath)), - configParent: untitledWorkspaceConfigFolder, + workspace: this.getWorkspaceIdentifier(untitledWorkspaceConfigPath), storedWorkspace }; } getWorkspaceId(configPath: URI): string { - let workspaceConfigPath = configPath.scheme === Schemas.file ? fsPath(configPath) : configPath.toString(); + let workspaceConfigPath = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString(); if (!isLinux) { workspaceConfigPath = workspaceConfigPath.toLowerCase(); // sanitize for platform file system } @@ -176,7 +181,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean { - return workspace.configPath.scheme === Schemas.file && this.isInsideWorkspacesHome(fsPath(workspace.configPath)); + return this.isInsideWorkspacesHome(workspace.configPath); } saveWorkspaceAs(workspace: IWorkspaceIdentifier, targetConfigPath: string): Promise { @@ -185,7 +190,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain throw new Error('Only local workspaces can be saved with this API. Use WorkspaceEditingService.saveWorkspaceAs on the renderer instead.'); } - const configPath = fsPath(workspace.configPath); + const configPath = originalFSPath(workspace.configPath); // Return early if target is same as source if (isEqual(configPath, targetConfigPath, !isLinux)) { @@ -216,7 +221,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } private doDeleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void { - const configPath = fsPath(workspace.configPath); + const configPath = originalFSPath(workspace.configPath); try { // Delete Workspace delSync(dirname(configPath)); @@ -234,10 +239,10 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain getUntitledWorkspacesSync(): IWorkspaceIdentifier[] { let untitledWorkspaces: IWorkspaceIdentifier[] = []; try { - const 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 = this.getWorkspaceIdentifier(URI.file(untitledWorkspacePath)); - if (!this.resolveWorkspaceSync(untitledWorkspacePath)) { + const workspace = this.getWorkspaceIdentifier(untitledWorkspacePath); + if (!this.resolveLocalWorkspaceSync(untitledWorkspacePath)) { this.doDeleteUntitledWorkspaceSync(workspace); } else { untitledWorkspaces.push(workspace); @@ -245,7 +250,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } } 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}).`); } } return untitledWorkspaces; diff --git a/src/vs/platform/workspaces/node/workspaces.ts b/src/vs/platform/workspaces/node/workspaces.ts deleted file mode 100644 index e5e872b9f19..00000000000 --- a/src/vs/platform/workspaces/node/workspaces.ts +++ /dev/null @@ -1,127 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. 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/test/electron-main/workspacesMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts index d902534458c..0c5e7f6f111 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.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 extfs from 'vs/base/node/extfs'; import * as pfs from 'vs/base/node/pfs'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; @@ -21,11 +21,11 @@ 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 +56,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 { @@ -173,20 +173,20 @@ suite('WorkspacesMainService', () => { test('resolveWorkspaceSync', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - assert.ok(service.resolveWorkspaceSync(workspace.configPath.fsPath)); + assert.ok(service.resolveLocalWorkspaceSync(workspace.configPath)); // make it a valid workspace path 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.fsPath); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); assert.equal(2, resolved!.folders.length); assertEqualURI(resolved!.configPath, workspace.configPath); assert.ok(resolved!.id); fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ something: 'something' })); // invalid workspace - const resolvedInvalid = service.resolveWorkspaceSync(workspace.configPath.fsPath); + const resolvedInvalid = service.resolveLocalWorkspaceSync(workspace.configPath); assert.ok(!resolvedInvalid); }); }); @@ -195,7 +195,7 @@ suite('WorkspacesMainService', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] })); - const resolved = service.resolveWorkspaceSync(workspace.configPath.fsPath); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); }); @@ -204,7 +204,7 @@ suite('WorkspacesMainService', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] })); - const resolved = service.resolveWorkspaceSync(workspace.configPath.fsPath); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'other'))); }); }); @@ -213,7 +213,7 @@ suite('WorkspacesMainService', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] })); - const resolved = service.resolveWorkspaceSync(workspace.configPath.fsPath); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); }); @@ -222,7 +222,7 @@ suite('WorkspacesMainService', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { fs.writeFileSync(workspace.configPath.fsPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma - const resolved = service.resolveWorkspaceSync(workspace.configPath.fsPath); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); }); @@ -345,7 +345,7 @@ suite('WorkspacesMainService', () => { 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.fsPath)); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 7fa1945c4b2..978c31dd58c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2014,12 +2014,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 +2110,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. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 7bc245848d0..d0a90d17e21 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -16,6 +16,56 @@ declare module 'vscode' { + //#region Alex - resolvers + + export class ResolvedAuthority { + readonly host: string; + readonly port: number; + debugListenPort?: number; + debugConnectPort?: number; + + constructor(host: string, port: number); + } + + export interface RemoteAuthorityResolver { + resolve(authority: string): ResolvedAuthority | Thenable; + } + + export namespace workspace { + export function registerRemoteAuthorityResolver(authorityPrefix: string, resolver: RemoteAuthorityResolver): Disposable; + } + + //#endregion + + + // #region Joh - code insets + + /** + */ + export class CodeInset { + range: Range; + 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 export class SelectionRangeKind { @@ -52,10 +102,13 @@ declare module 'vscode' { 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 sequences + * of ranges (per position) where a range must [contain](#Range.contains) and subsequent ranges. + * + * todo@joh */ - provideSelectionRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + provideSelectionRanges(document: TextDocument, positions: Position[], token: CancellationToken): ProviderResult; } export namespace languages { @@ -820,6 +873,8 @@ declare module 'vscode' { interface CommentReaction { readonly label?: string; + readonly iconPath?: string | Uri; + count?: number; readonly hasReacted?: boolean; } @@ -1126,18 +1181,6 @@ declare module 'vscode' { } //#endregion - //#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. - */ - isPreferred?: boolean; - } - //#endregion - //#region Tasks export interface TaskPresentationOptions { /** @@ -1146,16 +1189,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/electron-browser/mainThreadConfiguration.ts b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts index 265994c37dc..a219ce3dca3 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts @@ -46,17 +46,17 @@ 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 | null): 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 | null): 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 | null): 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); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadConsole.ts b/src/vs/workbench/api/electron-browser/mainThreadConsole.ts index 0f4e37223aa..be1aa913a83 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 287c7320573..80b8a12bc42 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -141,13 +141,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(); } @@ -259,20 +259,32 @@ 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 { + 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)) { diff --git a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts index 37cd9749381..8847d97283a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts @@ -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/mainThreadDocuments.ts b/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts index 325d34af088..7780708d837 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts @@ -130,14 +130,14 @@ 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; this._modelIsSynced[modelUrl.toString()] = true; diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts index 1a51bf7f504..7801fcbf337 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'; @@ -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 | undefined, + readonly newActiveEditor: string | 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 | 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 ) { @@ -282,7 +282,7 @@ class MainThreadDocumentAndEditorStateComputer { } } - private _getActiveEditorFromPanel(): IEditor { + private _getActiveEditorFromPanel(): IEditor | undefined { let panel = this._panelService.getActivePanel(); if (panel instanceof BaseTextEditor && isCodeEditor(panel.getControl())) { return panel.getControl(); @@ -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 a5f9775cb68..672b53ed6b9 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadEditor.ts @@ -187,7 +187,7 @@ export class MainThreadTextEditor { private _focusTracker: IFocusTracker; private _codeEditorListeners: IDisposable[]; - private _properties: MainThreadTextEditorProperties | null; + private _properties: MainThreadTextEditorProperties; private readonly _onPropertiesChanged: Emitter; constructor( @@ -204,7 +204,6 @@ export class MainThreadTextEditor { this._modelService = modelService; this._codeEditorListeners = []; - this._properties = null; this._onPropertiesChanged = new Emitter(); this._modelListeners = []; @@ -300,7 +299,7 @@ export class MainThreadTextEditor { return !!this._codeEditor; } - public getProperties(): MainThreadTextEditorProperties | null { + public getProperties(): MainThreadTextEditorProperties { return this._properties; } @@ -316,7 +315,7 @@ export class MainThreadTextEditor { const newSelections = selections.map(Selection.liftSelection); this._setProperties( - new MainThreadTextEditorProperties(newSelections, this._properties!.options, this._properties!.visibleRanges), + new MainThreadTextEditorProperties(newSelections, this._properties.options, this._properties.visibleRanges), null ); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts index d35dda26fd8..1f9bff1a2cb 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts @@ -36,7 +36,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { private _documentsAndEditors: MainThreadDocumentsAndEditors; private _toDispose: IDisposable[]; private _textEditorsListenersMap: { [editorId: string]: IDisposable[]; }; - private _editorPositionData: ITextEditorPositionData; + private _editorPositionData: ITextEditorPositionData | null; private _registeredDecorationTypes: { [decorationType: string]: boolean; }; constructor( @@ -145,7 +145,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { options: { preserveFocus: false } }, viewColumnToEditorGroup(this._editorGroupService, position)).then(() => { return; }); } - return undefined; + return Promise.resolve(); } $tryHideEditor(id: string): Promise { @@ -158,7 +158,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } } } - 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 { diff --git a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts b/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts index d7af8caf14a..1b5d15ee21c 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts @@ -47,14 +47,23 @@ export class HeapService implements IHeapService { } trackObject(obj: ObjectIdentifier | undefined | null): void { - if (!obj || typeof obj.$ident !== 'number') { + 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(obj.$ident); - this._activeSignals.set(obj, new this._ctor(obj.$ident)); + this._activeIds.add(ident); + this._activeSignals.set(obj, new this._ctor(ident)); } else { // make sure to load gc-signals, then track and leave @@ -77,8 +86,8 @@ export class HeapService implements IHeapService { } this._ctorInit.then(() => { - this._activeIds.add(obj.$ident); - this._activeSignals.set(obj, new this._ctor(obj.$ident)); + this._activeIds.add(ident); + this._activeSignals.set(obj, new this._ctor(ident)); }); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index f3307626193..73dbc49ae4e 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -11,7 +11,7 @@ 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,6 +20,8 @@ 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 { @@ -104,6 +106,13 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha 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 +120,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,18 +128,26 @@ 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._proxy.$provideCodeLenses(handle, model.uri, token).then(dto => { - if (dto) { dto.forEach(obj => this._heapService.trackObject(obj)); } + 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._proxy.$resolveCodeLens(handle, model.uri, codeLens, token).then(obj => { - this._heapService.trackObject(obj); + if (obj) { + this._heapService.trackObject(obj); + this._heapService.trackObject(obj.command); + } return obj; }); } @@ -152,6 +169,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 { @@ -190,7 +236,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); } }); @@ -200,7 +246,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); } }); @@ -232,30 +278,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); } }); @@ -264,7 +309,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 => { @@ -290,7 +335,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 }); } @@ -338,12 +383,18 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha this._registrations[handle] = modes.LinkProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { provideLinks: (model, token) => { return this._proxy.$provideDocumentLinks(handle, model.uri, token).then(dto => { - if (dto) { dto.forEach(obj => this._heapService.trackObject(obj)); } + if (dto) { + dto.forEach(obj => { + MainThreadLanguageFeatures._reviveLinkDTO(obj); + this._heapService.trackObject(obj); + }); + } return dto; }); }, resolveLink: (link, token) => { return this._proxy.$resolveDocumentLink(handle, link, token).then(obj => { + MainThreadLanguageFeatures._reviveLinkDTO(obj); this._heapService.trackObject(obj); return obj; }); @@ -400,8 +451,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); } }); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts b/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts index 6318bbd9559..7f9f642dafd 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts @@ -38,7 +38,7 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut 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,7 +84,7 @@ 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); @@ -93,7 +93,7 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut return undefined; } - public $dispose(channelId: string): Promise { + public $dispose(channelId: string): Promise | undefined { const channel = this._getChannel(channelId); if (channel) { channel.dispose(); diff --git a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts index 7a7b720a77b..eb965620574 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts @@ -23,7 +23,7 @@ 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'; @@ -36,6 +36,7 @@ import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustom import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ISaveParticipant, ITextFileEditorModel, SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../node/extHost.protocol'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export interface ISaveParticipantParticipant extends ISaveParticipant { // progressMessage: string; @@ -215,7 +216,8 @@ 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 } @@ -235,20 +237,14 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { return new Promise((resolve, reject) => { let source = new CancellationTokenSource(); - let request = getDocumentFormattingEdits(model, { tabSize, insertSpaces }, source.token); + let request = getDocumentFormattingEdits(this._telemetryService, this._editorWorkerService, model, { tabSize, insertSpaces }, 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()) { diff --git a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts index bc4624424f9..e4e99dcf661 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'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadTask.ts b/src/vs/workbench/api/electron-browser/mainThreadTask.ts index 37231f85b42..8637ce0dcad 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTask.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTask.ts @@ -74,7 +74,7 @@ namespace TaskDefinitionDTO { 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 +87,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 +102,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 +117,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 +126,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; } @@ -167,7 +167,7 @@ namespace ProcessExecutionDTO { } namespace ShellExecutionOptionsDTO { - export function from(value: CommandOptions): ShellExecutionOptionsDTO { + export function from(value: CommandOptions): ShellExecutionOptionsDTO | undefined { if (value === undefined || value === null) { return undefined; } @@ -182,7 +182,7 @@ namespace ShellExecutionOptionsDTO { } return result; } - export function to(value: ShellExecutionOptionsDTO): CommandOptions { + export function to(value: ShellExecutionOptionsDTO): CommandOptions | undefined { if (value === undefined || value === null) { return undefined; } @@ -257,7 +257,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 +270,9 @@ namespace TaskSourceDTO { scope = value.scope; } else { scope = TaskScope.Folder; - workspaceFolder = workspace.getWorkspaceFolder(URI.revive(value.scope)); + workspaceFolder = workspace.getWorkspaceFolder(URI.revive(value.scope)) || undefined; } - let result: ExtensionTaskSource = { + const result: ExtensionTaskSource = { kind: TaskSourceKind.Extension, label: value.label, extension: value.extensionId, @@ -291,7 +291,7 @@ namespace TaskHandleDTO { } 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; } @@ -327,16 +327,19 @@ namespace TaskDTO { return result; } - export function to(task: TaskDTO, workspace: IWorkspaceContextService, executeOnly: boolean): ContributedTask { + export function to(task: TaskDTO, workspace: IWorkspaceContextService, executeOnly: boolean): ContributedTask | undefined { if (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); + } } + if (!command) { return undefined; } @@ -371,7 +374,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; } } @@ -392,13 +395,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())); } 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())); } @@ -534,8 +537,8 @@ export class MainThreadTask implements MainThreadTaskShape { }); return new Promise((resolve, reject) => { this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks').then(resolvedVars => { - let result = { - process: undefined as string, + let 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 d340f73f602..3f30d465934 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts @@ -37,7 +37,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape 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 @@ -90,7 +90,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(); } } @@ -164,7 +165,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); } @@ -195,6 +196,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void { + if (terminalInstance.processId === undefined) { + return; + } this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts index 84b444bb19e..2d6da3314f5 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts @@ -45,7 +45,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 +59,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 { diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts index cc68e778185..d1764ce52f4 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts @@ -2,30 +2,33 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { onUnexpectedError } from 'vs/base/common/errors'; import { dispose, 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 { 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, WebviewReviver } from 'vs/workbench/contrib/webview/electron-browser/webviewEditorService'; +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 { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; 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 implements MainThreadWebviewsShape { private static readonly standardSupportedLinkSchemes = ['http', 'https', 'mailto']; @@ -35,7 +38,8 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv 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; @@ -47,14 +51,15 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv @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, + @IPartService private readonly _partService: IPartService, ) { 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)); - lifecycleService.onBeforeShutdown(e => { e.veto(this._onBeforeShutdown()); }, this, this._toDispose); @@ -68,18 +73,18 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv 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 +101,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._partService.getContainer(Parts.EDITOR_PART), + { + useSameOriginForRoot: true, + extensionLocation: URI.revive(extensionLocation) + }, + { + 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 +154,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._webviewsElements.get(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._webviewsElements.get(handle).options = reviveWebviewOptions(options); + } else { + const webview = this.getWebview(handle); + webview.setOptions(reviveWebviewOptions(options)); + } } public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void { @@ -129,68 +180,81 @@ 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); + this._webviewService.revealWebview(webview, targetGroup || this._editorGroupService.getGroup(webview.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._webviewsElements.get(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`); + } + this._revivers.set(viewType, this._webviewService.registerReviver(this.getInternalWebviewId(viewType), { + canRevive: (webview) => { + return !webview.isDisposed() && webview.state; + }, + reviveWebview: (webview): 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 + } + } - public $unregisterSerializer(viewType: string): void { - this._revivers.delete(viewType); - } + return this._proxy.$deserializeWebviewPanel(handle, webview.state.viewType, webview.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webview.group), webview.options) + .then(undefined, error => { + onUnexpectedError(error); - 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 - } + 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 +265,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 +277,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; @@ -240,7 +301,7 @@ 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)), + visible: this._editorService.visibleControls.some(editor => !!editor.input && editor.input.matches(oldActiveWebview)), position: editorGroupToViewColumn(this._editorGroupService, oldActiveWebview.group), }); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 91bd9eb6070..1ac6c84000b 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -12,17 +12,17 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; 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 { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } 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 { 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, IWorkspaceData } from '../node/extHost.protocol'; import { TextSearchComplete } from 'vscode'; -import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape } from '../node/extHost.protocol'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -44,6 +44,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { @ILabelService private readonly _labelService: ILabelService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace); + this._proxy.$initializeWorkspace(this.getWorkspaceData(this._contextService.getWorkspace())); this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose); this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose); } @@ -101,22 +102,28 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } private _onDidChangeWorkspace(): void { - const workspace = this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : this._contextService.getWorkspace(); - this._proxy.$acceptWorkspaceData(workspace ? { + 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 | undefined { + $startFileSearch(includePattern: string, _includeFolder: UriComponents | undefined, excludePatternOrDisregardExcludes: string | false, 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); } const query = this._queryBuilder.file( diff --git a/src/vs/workbench/api/node/apiCommands.ts b/src/vs/workbench/api/node/apiCommands.ts index 02ff1612998..01d1c77337c 100644 --- a/src/vs/workbench/api/node/apiCommands.ts +++ b/src/vs/workbench/api/node/apiCommands.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { tmpdir } from 'os'; -import { posix } from 'path'; +import { join } from 'vs/base/common/path'; import * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import { isMalformedFileUri } from 'vs/base/common/resources'; @@ -65,7 +65,24 @@ export class OpenFolderAPICommand { return executor.executeCommand('_files.windowOpen', { urisToOpen: [{ uri }], forceNewWindow }); } } -CommandsRegistry.registerCommand(OpenFolderAPICommand.ID, adjustHandler(OpenFolderAPICommand.execute)); +CommandsRegistry.registerCommand({ + id: OpenFolderAPICommand.ID, + handler: adjustHandler(OpenFolderAPICommand.execute), + description: { + description: `Open a folder`, + args: [{ + name: 'uri', + schema: { + 'type': 'string' + } + }, { + name: 'forceNewWindow', + schema: { + 'type': 'boolean' + } + }] + } +}); export class DiffAPICommand { public static ID = 'vscode.diff'; @@ -126,11 +143,35 @@ 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()); + const location = join(tmpdir(), generateUuid()); return downloadService.download(resource, location).then(() => URI.file(location)); }); \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 2b6cd0333da..099173ae6db 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'; @@ -58,15 +58,16 @@ import * as extHostTypes from 'vs/workbench/api/node/extHostTypes'; 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 { ExtHostWorkspace, ExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace'; import { IExtensionDescription, 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 { originalFSPath } from 'vs/base/common/resources'; export interface IExtensionApiFactory { - (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; + (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, workspaceProvider: ExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider): typeof vscode; } function proposedApiFunction(extension: IExtensionDescription, fn: T): T { @@ -142,7 +143,7 @@ export function createApiFactory( // Register API-ish commands ExtHostApiCommands.register(extHostCommands); - return function (extension: IExtensionDescription, extensionRegistry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode { + return function (extension: IExtensionDescription, extensionRegistry: ExtensionDescriptionRegistry, workspaceProvider: ExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider): typeof vscode { // Check document selectors for being overly generic. Technically this isn't a problem but // in practice many extensions say they support `fooLang` but need fs-access to do so. Those @@ -219,7 +220,7 @@ 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[]) => { + return extHostCommands.registerCommand(true, id, async (...args: any[]): Promise => { let activeTextEditor = extHostEditors.getActiveTextEditor(); if (!activeTextEditor) { console.warn('Cannot execute ' + id + ' because there is no active text editor.'); @@ -242,9 +243,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()); @@ -263,8 +264,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); } @@ -304,6 +305,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); }, @@ -443,7 +448,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); @@ -474,7 +479,7 @@ 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 { if (typeof nameOrOptions === 'object') { @@ -511,34 +516,34 @@ export function createApiFactory( // namespace: workspace const workspace: typeof vscode.workspace = { get rootPath() { - return extHostWorkspace.getPath(); + return workspaceProvider.getPath(); }, set rootPath(value) { throw errors.readonly(); }, getWorkspaceFolder(resource) { - return extHostWorkspace.getWorkspaceFolder(resource); + return workspaceProvider.getWorkspaceFolder(resource); }, get workspaceFolders() { - return extHostWorkspace.getWorkspaceFolders(); + return workspaceProvider.getWorkspaceFolders(); }, get name() { - return extHostWorkspace.name; + return workspaceProvider.name; }, set name(value) { throw errors.readonly(); }, updateWorkspaceFolders: (index, deleteCount, ...workspaceFoldersToAdd) => { - return extHostWorkspace.updateWorkspaceFolders(extension, index, deleteCount || 0, ...workspaceFoldersToAdd); + return workspaceProvider.updateWorkspaceFolders(extension, index, deleteCount || 0, ...workspaceFoldersToAdd); }, onDidChangeWorkspaceFolders: function (listener, thisArgs?, disposables?) { - return extHostWorkspace.onDidChangeWorkspace(listener, thisArgs, disposables); + return workspaceProvider.onDidChangeWorkspace(listener, thisArgs, disposables); }, asRelativePath: (pathOrUri, includeWorkspace) => { - return extHostWorkspace.getRelativePath(pathOrUri, includeWorkspace); + return workspaceProvider.getRelativePath(pathOrUri, includeWorkspace); }, findFiles: (include, exclude, maxResults?, token?) => { - return extHostWorkspace.findFiles(typeConverters.GlobPattern.from(include), typeConverters.GlobPattern.from(exclude), maxResults, extension.identifier, token); + return workspaceProvider.findFiles(typeConverters.GlobPattern.from(include), typeConverters.GlobPattern.from(exclude), maxResults, extension.identifier, token); }, findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback, callbackOrToken?, token?: vscode.CancellationToken) => { let options: vscode.FindTextInFilesOptions; @@ -553,10 +558,10 @@ export function createApiFactory( token = callbackOrToken; } - return extHostWorkspace.findTextInFiles(query, options || {}, callback, extension.identifier, token); + return workspaceProvider.findTextInFiles(query, options || {}, callback, extension.identifier, token); }, saveAll: (includeUntitled?) => { - return extHostWorkspace.saveAll(includeUntitled); + return workspaceProvider.saveAll(includeUntitled); }, applyEdit(edit: vscode.WorkspaceEdit): Thenable { return extHostEditors.applyWorkspaceEdit(edit); @@ -641,6 +646,9 @@ export function createApiFactory( registerWorkspaceCommentProvider: proposedApiFunction(extension, (provider: vscode.WorkspaceCommentProvider) => { return exthostCommentProviders.registerWorkspaceCommentProvider(extension.identifier, provider); }), + registerRemoteAuthorityResolver: proposedApiFunction(extension, (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { + return extensionService.registerRemoteAuthorityResolver(authorityPrefix, resolver); + }), onDidRenameFile: proposedApiFunction(extension, (listener, thisArg?, disposables?) => { return extHostFileSystemEvent.onDidRenameFile(listener, thisArg, disposables); }), @@ -751,6 +759,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, @@ -793,6 +802,7 @@ export function createApiFactory( QuickInputButtons: extHostTypes.QuickInputButtons, Range: extHostTypes.Range, RelativePattern: extHostTypes.RelativePattern, + ResolvedAuthority: extHostTypes.ResolvedAuthority, Selection: extHostTypes.Selection, SelectionRange: extHostTypes.SelectionRange, SelectionRangeKind: extHostTypes.SelectionRangeKind, @@ -832,18 +842,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; @@ -857,7 +855,7 @@ class Extension implements vscode.Extension { 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; } @@ -877,11 +875,11 @@ class Extension implements vscode.Extension { } } -export function initializeExtensionApi(extensionService: ExtHostExtensionService, apiFactory: IExtensionApiFactory, extensionRegistry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): Promise { - return extensionService.getExtensionPathIndex().then(trie => defineAPI(apiFactory, trie, extensionRegistry, configProvider)); +export function initializeExtensionApi(extensionService: ExtHostExtensionService, apiFactory: IExtensionApiFactory, extensionRegistry: ExtensionDescriptionRegistry, workspaceProvider: ExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider): Promise { + return extensionService.getExtensionPathIndex().then(trie => defineAPI(apiFactory, trie, extensionRegistry, workspaceProvider, configProvider)); } -function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchTree, extensionRegistry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): void { +function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchTree, extensionRegistry: ExtensionDescriptionRegistry, workspaceProvider: ExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider): void { // each extension is meant to get its own api implementation const extApiImpl = new Map(); @@ -899,7 +897,7 @@ function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchT if (ext) { let apiImpl = extApiImpl.get(ExtensionIdentifier.toKey(ext.identifier)); if (!apiImpl) { - apiImpl = factory(ext, extensionRegistry, configProvider); + apiImpl = factory(ext, extensionRegistry, workspaceProvider, configProvider); extApiImpl.set(ExtensionIdentifier.toKey(ext.identifier), apiImpl); } return apiImpl; @@ -910,7 +908,7 @@ function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchT let extensionPathsPretty = ''; extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.identifier.value}\n`); console.warn(`Could not identify extension for 'vscode' require call from ${parent.filename}. These are the extension path mappings: \n${extensionPathsPretty}`); - defaultApiImpl = factory(nullExtensionDescription, extensionRegistry, configProvider); + defaultApiImpl = factory(nullExtensionDescription, extensionRegistry, workspaceProvider, configProvider); } return defaultApiImpl; }; diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 925871aefc4..cfafeed72ac 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'; @@ -45,6 +45,7 @@ import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IRemoteConsoleLog } from 'vs/base/node/console'; +import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -55,18 +56,21 @@ export interface IEnvironment { globalStorageHome: URI; } -export interface IWorkspaceData { +export interface IStaticWorkspaceData { id: string; name: string; - folders: { uri: UriComponents, name: string, index: number }[]; configuration?: UriComponents; } +export interface IWorkspaceData extends IStaticWorkspaceData { + folders: { uri: UriComponents, name: string, index: number }[]; +} + export interface IInitData { commit?: string; parentPid: number; environment: IEnvironment; - workspace?: IWorkspaceData; + workspace?: IStaticWorkspaceData; resolvedExtensions: ExtensionIdentifier[]; hostExtensions: ExtensionIdentifier[]; extensions: IExtensionDescription[]; @@ -111,7 +115,7 @@ export interface CommentProviderFeatures { startDraftLabel?: string; deleteDraftLabel?: string; finishDraftLabel?: string; - reactionGroup?: vscode.CommentReaction[]; + reactionGroup?: modes.CommentReaction[]; } export interface MainThreadCommentsShape extends IDisposable { @@ -295,7 +299,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): void; $emitCodeLensEvent(eventHandle: number, event?: any): void; $registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void; $registerDeclarationSupport(handle: number, selector: ISerializedDocumentFilter[]): void; @@ -305,9 +310,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; @@ -335,12 +340,12 @@ export interface MainThreadMessageServiceShape extends IDisposable { 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 { @@ -464,6 +469,8 @@ export interface MainThreadTelemetryShape extends IDisposable { export type WebviewPanelHandle = string; +export type WebviewInsetHandle = number; + export interface WebviewPanelShowOptions { readonly viewColumn?: EditorViewColumn; readonly preserveFocus?: boolean; @@ -471,13 +478,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): 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; @@ -506,7 +515,7 @@ export interface ExtHostUrlsShape { } export interface MainThreadWorkspaceShape extends IDisposable { - $startFileSearch(includePattern: string | undefined, includeFolder: URI, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise | undefined; + $startFileSearch(includePattern: string | undefined, includeFolder: URI | undefined, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise; $startTextSearch(query: IPatternInfo, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise; $checkExists(includes: string[], token: CancellationToken): Promise; $saveAll(includeUntitled?: boolean): Promise; @@ -652,7 +661,7 @@ export interface ExtHostDiagnosticsShape { } export interface ExtHostDocumentContentProvidersShape { - $provideTextDocumentContent(handle: number, uri: UriComponents): Promise; + $provideTextDocumentContent(handle: number, uri: UriComponents): Promise; } export interface IModelAddedData { @@ -720,6 +729,7 @@ export interface ExtHostTreeViewsShape { } export interface ExtHostWorkspaceShape { + $initializeWorkspace(workspace: IWorkspaceData | null): void; $acceptWorkspaceData(workspace: IWorkspaceData | null): void; $handleTextSearchResult(result: IRawFileMatch2, requestId: number): void; } @@ -880,45 +890,56 @@ export interface CodeActionDto { isPreferred?: boolean; } -export type LinkDto = ObjectIdentifier & modes.ILink; +export interface LinkDto extends ObjectIdentifier { + range: IRange; + url?: string | UriComponents; +} -export type CodeLensDto = ObjectIdentifier & modes.ICodeLensSymbol; +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; + $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; + $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: LinkDto, 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; + $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; @@ -932,7 +953,7 @@ export interface ShellLaunchConfigDto { executable?: string; args?: string[] | string; cwd?: string | URI; - env?: { [key: string]: string }; + env?: { [key: string]: string | null }; } export interface ExtHostTerminalServiceShape { @@ -953,7 +974,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>; @@ -1032,7 +1053,7 @@ export interface ExtHostDebugServiceShape { $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; } diff --git a/src/vs/workbench/api/node/extHostApiCommands.ts b/src/vs/workbench/api/node/extHostApiCommands.ts index bb76263617f..e487f6911c1 100644 --- a/src/vs/workbench/api/node/extHostApiCommands.ts +++ b/src/vs/workbench/api/node/extHostApiCommands.ts @@ -17,7 +17,7 @@ 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/editor/common/editorGroupsService'; -import { isFalsyOrEmpty, isNonEmptyArray } from 'vs/base/common/arrays'; +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', }, ], 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.' }); @@ -298,7 +299,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 +308,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 +317,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 +326,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 +335,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 +344,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 +353,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) @@ -378,7 +379,7 @@ export class ExtHostApiCommands { }); } - 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 +393,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 +421,15 @@ export class ExtHostApiCommands { }); } - private _executeSelectionRangeProvider(resource: URI, position: types.Position): Promise { + private _executeSelectionRangeProvider(resource: URI, positions: types.Position[]): Promise { + let 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,7 +447,7 @@ export class ExtHostApiCommands { }); } - private _executeDocumentSymbolProvider(resource: URI): Promise { + private _executeDocumentSymbolProvider(resource: URI): Promise { const args = { resource }; @@ -479,10 +479,11 @@ export class ExtHostApiCommands { }); } - 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 => { @@ -504,7 +505,7 @@ 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 => { @@ -515,7 +516,7 @@ export class ExtHostApiCommands { } - private _executeFormatDocumentProvider(resource: URI, options: vscode.FormattingOptions): Promise { + private _executeFormatDocumentProvider(resource: URI, options: vscode.FormattingOptions): Promise { const args = { resource, options @@ -524,7 +525,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 +535,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 +546,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/extHostCommands.ts b/src/vs/workbench/api/node/extHostCommands.ts index 8b73f059351..cc707471087 100644 --- a/src/vs/workbench/api/node/extHostCommands.ts +++ b/src/vs/workbench/api/node/extHostCommands.ts @@ -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 { @@ -207,7 +211,7 @@ export class CommandsConverter { this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } - toInternal(command: vscode.Command): CommandDto { + toInternal(command: vscode.Command | undefined): CommandDto | undefined { if (!command) { return undefined; @@ -237,7 +241,7 @@ export class CommandsConverter { return result; } - fromInternal(command: modes.Command): vscode.Command { + fromInternal(command: modes.Command | undefined): vscode.Command | undefined { if (!command) { return undefined; @@ -258,7 +262,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 3e76d66a03e..f1beb859241 100644 --- a/src/vs/workbench/api/node/extHostComments.ts +++ b/src/vs/workbench/api/node/extHostComments.ts @@ -70,7 +70,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); @@ -111,107 +111,68 @@ export class ExtHostComments implements ExtHostCommentsShape { } $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._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._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)); - - if (!data || !data.document) { - throw new Error('Unable to retrieve document from URI'); - } + const document = this._documents.getDocument(URI.revive(uri)); 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._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._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._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._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 document = this._documents.getDocument(URI.revive(uri)); const handlerData = this._documentProviders.get(handle); return asPromise(() => { - return handlerData.provider.provideDocumentComments(data.document, CancellationToken.None); + return handlerData.provider.provideDocumentComments(document, CancellationToken.None); }).then(commentInfo => commentInfo ? convertCommentInfo(handle, handlerData.extensionId, handlerData.provider, commentInfo, this._commandsConverter) : null); } @@ -289,7 +250,13 @@ 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 }; } @@ -297,6 +264,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), @@ -306,6 +274,27 @@ function convertToComment(provider: vscode.DocumentCommentProvider | vscode.Work canDelete: canDelete, command: vscodeComment.command ? commandsConverter.toInternal(vscodeComment.command) : null, 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 convertFromReaction(reaction: modes.CommentReaction): vscode.CommentReaction { + return { + label: reaction.label, + count: reaction.count, + hasReacted: reaction.hasReacted + }; +} \ 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 b65cdf1f2bf..88604967795 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -7,7 +7,7 @@ import { mixin, deepClone } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import * as vscode from 'vscode'; -import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; +import { ExtHostWorkspace, ExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData, IConfigurationInitData } from './extHost.protocol'; import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; @@ -57,8 +57,10 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { } $initializeConfiguration(data: IConfigurationInitData): void { - this._actual = new ExtHostConfigProvider(this._proxy, this._extHostWorkspace, data); - this._barrier.open(); + this._extHostWorkspace.getWorkspaceProvider().then(workspaceProvider => { + this._actual = new ExtHostConfigProvider(this._proxy, workspaceProvider, data); + this._barrier.open(); + }); } $acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData): void { @@ -70,11 +72,11 @@ export class ExtHostConfigProvider { private readonly _onDidChangeConfiguration = new Emitter(); private readonly _proxy: MainThreadConfigurationShape; - private readonly _extHostWorkspace: ExtHostWorkspace; + private readonly _extHostWorkspace: ExtHostWorkspaceProvider; private _configurationScopes: { [key: string]: ConfigurationScope }; private _configuration: Configuration; - constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData) { + constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspaceProvider, data: IConfigurationInitData) { this._proxy = proxy; this._extHostWorkspace = extHostWorkspace; this._configuration = ExtHostConfigProvider.parse(data); diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 31a45f7dd27..504c7b2ec17 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -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'; @@ -16,13 +16,13 @@ import { 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/contrib/debug/node/debugAdapter'; -import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; +import { ExtHostWorkspace, ExtHostWorkspaceProvider } 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/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, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; @@ -359,12 +359,12 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } public async $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): Promise { - const configProvider = await this._configurationService.getConfigProvider(); + const [workspaceProvider, configProvider] = await Promise.all([this._workspaceService.getWorkspaceProvider(), this._configurationService.getConfigProvider()]); if (!this._variableResolver) { - this._variableResolver = new ExtHostVariableResolverService(this._workspaceService, this._editorsService, configProvider); + this._variableResolver = new ExtHostVariableResolverService(workspaceProvider, this._editorsService, configProvider); } let ws: IWorkspaceFolder; - const folder = this.getFolder(folderUri); + const folder = this.getFolder(folderUri, workspaceProvider); if (folder) { ws = { uri: folder.uri, @@ -378,10 +378,11 @@ 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 workspaceProvider = await this._workspaceService.getWorkspaceProvider(); const mythis = this; - const session = this.getSession(sessionDto); + const session = this.getSession(sessionDto, workspaceProvider); return this.getAdapterDescriptor(this.getAdapterFactoryByType(session.type), session).then(x => { const adapter = this.convertToDto(x); @@ -545,7 +546,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this.fireBreakpointChanges(a, r, c); } - public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { + public async $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); let provider = this.getConfigProviderByHandle(configProviderHandle); if (!provider) { return Promise.reject(new Error('no handler found')); @@ -553,10 +555,11 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { if (!provider.provideDebugConfigurations) { return Promise.reject(new Error('handler has no method provideDebugConfigurations')); } - return asPromise(() => provider.provideDebugConfigurations(this.getFolder(folderUri), CancellationToken.None)); + return asPromise(() => provider.provideDebugConfigurations(this.getFolder(folderUri, workspaceProvider), CancellationToken.None)); } - public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): Promise { + public async $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): Promise { + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); let provider = this.getConfigProviderByHandle(configProviderHandle); if (!provider) { return Promise.reject(new Error('no handler found')); @@ -564,11 +567,12 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { if (!provider.resolveDebugConfiguration) { return Promise.reject(new Error('handler has no method resolveDebugConfiguration')); } - return asPromise(() => provider.resolveDebugConfiguration(this.getFolder(folderUri), debugConfiguration, CancellationToken.None)); + return asPromise(() => provider.resolveDebugConfiguration(this.getFolder(folderUri, workspaceProvider), debugConfiguration, CancellationToken.None)); } // TODO@AW legacy - public $legacyDebugAdapterExecutable(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { + public async $legacyDebugAdapterExecutable(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); let provider = this.getConfigProviderByHandle(configProviderHandle); if (!provider) { return Promise.reject(new Error('no handler found')); @@ -576,41 +580,42 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { 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(() => provider.debugAdapterExecutable(this.getFolder(folderUri, workspaceProvider), CancellationToken.None)).then(x => this.convertToDto(x)); } - public $provideDebugAdapter(adapterProviderHandle: number, sessionDto: IDebugSessionDto): Promise { + public async $provideDebugAdapter(adapterProviderHandle: number, sessionDto: IDebugSessionDto): Promise { + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); let 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)); + return this.getAdapterDescriptor(adapterProvider, this.getSession(sessionDto, workspaceProvider)).then(x => this.convertToDto(x)); } - public $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): void { - - this._onDidStartDebugSession.fire(this.getSession(sessionDto)); + public async $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): Promise { + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); + this._onDidStartDebugSession.fire(this.getSession(sessionDto, workspaceProvider)); } - public $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): void { - - const session = this.getSession(sessionDto); + public async $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): Promise { + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); + const session = this.getSession(sessionDto, workspaceProvider); 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 { + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); + this._activeDebugSession = sessionDto ? this.getSession(sessionDto, workspaceProvider) : undefined; this._onDidChangeActiveDebugSession.fire(this._activeDebugSession); } - public $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): void { - + public async $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): Promise { + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); const ee: vscode.DebugSessionCustomEvent = { - session: this.getSession(sessionDto), + session: this.getSession(sessionDto, workspaceProvider), event: event.event, body: event.body }; @@ -775,12 +780,12 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } } - private getSession(dto: IDebugSessionDto): ExtHostDebugSession { + private getSession(dto: IDebugSessionDto, workspaceProvider: ExtHostWorkspaceProvider): ExtHostDebugSession { if (dto) { if (typeof dto === 'string') { return this._debugSessions.get(dto); } else { - const debugSession = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, this.getFolder(dto.folderUri), dto.configuration); + const debugSession = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, this.getFolder(dto.folderUri, workspaceProvider), dto.configuration); this._debugSessions.set(debugSession.id, debugSession); return debugSession; } @@ -788,10 +793,10 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return undefined; } - private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder | undefined { + private getFolder(_folderUri: UriComponents | undefined, workspaceProvider: ExtHostWorkspaceProvider): vscode.WorkspaceFolder | undefined { if (_folderUri) { const folderURI = URI.revive(_folderUri); - return this._workspaceService.resolveWorkspaceFolder(folderURI); + return workspaceProvider.resolveWorkspaceFolder(folderURI); } return undefined; } @@ -852,7 +857,7 @@ export class ExtHostDebugConsole implements vscode.DebugConsole { export class ExtHostVariableResolverService extends AbstractVariableResolverService { - constructor(workspaceService: ExtHostWorkspace, editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider) { + constructor(workspaceService: ExtHostWorkspaceProvider, editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider) { super({ getFolderUri: (folderName: string): URI => { const folders = workspaceService.getWorkspaceFolders(); @@ -876,7 +881,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; diff --git a/src/vs/workbench/api/node/extHostDiagnostics.ts b/src/vs/workbench/api/node/extHostDiagnostics.ts index 3fdf6edf07e..229ea037c4d 100644 --- a/src/vs/workbench/api/node/extHostDiagnostics.ts +++ b/src/vs/workbench/api/node/extHostDiagnostics.ts @@ -60,7 +60,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { // the actual implementation for #set this._checkDisposed(); - let toSync: vscode.Uri[]; + let toSync: vscode.Uri[] = []; let hasChanged = true; if (first instanceof URI) { @@ -81,7 +81,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); @@ -255,7 +255,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 +272,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,6 +286,7 @@ 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); diff --git a/src/vs/workbench/api/node/extHostDocumentContentProviders.ts b/src/vs/workbench/api/node/extHostDocumentContentProviders.ts index 4c7ed33685d..89c7332e1b1 100644 --- a/src/vs/workbench/api/node/extHostDocumentContentProviders.ts +++ b/src/vs/workbench/api/node/extHostDocumentContentProviders.ts @@ -45,7 +45,7 @@ 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) { @@ -54,6 +54,9 @@ export class ExtHostDocumentContentProvider implements ExtHostDocumentContentPro } if (this._documentsAndEditors.getDocument(uri.toString())) { this.$provideTextDocumentContent(handle, uri).then(value => { + if (!value) { + return; + } const document = this._documentsAndEditors.getDocument(uri.toString()); if (!document) { @@ -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 d659a17e676..c05dbb6855d 100644 --- a/src/vs/workbench/api/node/extHostDocumentData.ts +++ b/src/vs/workbench/api/node/extHostDocumentData.ts @@ -14,7 +14,7 @@ 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 | undefined { diff --git a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts index 29c19a7cefc..731a9309798 100644 --- a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts @@ -63,7 +63,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic return undefined; } - 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`); } } diff --git a/src/vs/workbench/api/node/extHostDocuments.ts b/src/vs/workbench/api/node/extHostDocuments.ts index 313dec0438e..dfc39581f84 100644 --- a/src/vs/workbench/api/node/extHostDocuments.ts +++ b/src/vs/workbench/api/node/extHostDocuments.ts @@ -56,7 +56,7 @@ 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; } @@ -67,6 +67,14 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { 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()); @@ -143,7 +151,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 bee6279e748..4048e3a936b 100644 --- a/src/vs/workbench/api/node/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/node/extHostDocumentsAndEditors.ts @@ -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); + } } } @@ -97,7 +101,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha data.selections.map(typeConverters.Selection.to), data.options, data.visibleRanges.map(typeConverters.Range.to), - typeConverters.ViewColumn.to(data.editorPosition) + typeof data.editorPosition === 'number' ? typeConverters.ViewColumn.to(data.editorPosition) : undefined ); this._editors.set(data.id, editor); } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 82228a9ec5e..ce45a06e521 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -4,28 +4,30 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; 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'; import { createApiFactory, initializeExtensionApi, IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl'; -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, 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 { ExtHostWorkspace, ExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace'; import { IExtensionDescription } 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 * as vscode from 'vscode'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IWorkspace } from 'vs/platform/workspace/common/workspace'; class ExtensionMemento implements IExtensionMemento { @@ -80,13 +82,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); @@ -96,7 +98,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); } @@ -107,7 +109,7 @@ 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); } @@ -167,6 +169,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { private _extensionPathIndex: Promise>; private readonly _extensionApiFactory: IExtensionApiFactory; + private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver; }; + private _started: boolean; constructor( @@ -227,6 +231,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(); @@ -239,9 +245,10 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { private async _initialize(): Promise { try { const configProvider = await this._extHostConfiguration.getConfigProvider(); - await initializeExtensionApi(this, this._extensionApiFactory, this._registry, configProvider); + const workspaceProvider = await this._extHostWorkspace.getWorkspaceProvider(); + await initializeExtensionApi(this, this._extensionApiFactory, this._registry, workspaceProvider, configProvider); // Do this when extension service exists, but extensions are not being activated yet. - await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._extHostLogService, this._mainThreadTelemetryProxy); + await connectProxyResolver(workspaceProvider, configProvider, this, this._extHostLogService, this._mainThreadTelemetryProxy); this._barrier.open(); } catch (err) { errors.onUnexpectedError(err); @@ -483,15 +490,15 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { // -- eager activation // Handle "eager" activation extensions - private _handleEagerExtensions(): Promise { + private _handleEagerExtensions(workspaceProvider: ExtHostWorkspaceProvider): Promise { this._activateByEvent('*', true).then(undefined, (err) => { console.error(err); }); - return this._handleWorkspaceContainsEagerExtensions(this._initData.workspace); + return this._handleWorkspaceContainsEagerExtensions(workspaceProvider.workspace); } - private _handleWorkspaceContainsEagerExtensions(workspace: IWorkspaceData): Promise { + private _handleWorkspaceContainsEagerExtensions(workspace: IWorkspace | undefined): Promise { if (!workspace || workspace.folders.length === 0) { return Promise.resolve(undefined); } @@ -503,7 +510,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); @@ -533,7 +540,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) { @@ -565,7 +572,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) { @@ -606,8 +613,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } // 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); } catch (error) { @@ -617,7 +624,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(this._initData.environment.extensionTestsPath, (error, failures) => { if (error) { e(error.toString()); } else { @@ -651,17 +658,48 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this._started = true; return this._barrier.wait() - .then(() => this._handleEagerExtensions()) + .then(() => this._extHostWorkspace.getWorkspaceProvider()) + .then(workspaceProvider => this._handleEagerExtensions(workspaceProvider)) .then(() => this._handleExtensionTests()) .then(() => { this._extHostLogService.info(`eager extensions activated`); }); } + // -- called by extensions + + public registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable { + this._resolvers[authorityPrefix] = resolver; + return toDisposable(() => { + this._resolvers[authorityPrefix] = null; + }); + } + // -- 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._barrier.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 { diff --git a/src/vs/workbench/api/node/extHostFileSystem.ts b/src/vs/workbench/api/node/extHostFileSystem.ts index a1e8edc9204..cb8b9b548f2 100644 --- a/src/vs/workbench/api/node/extHostFileSystem.ts +++ b/src/vs/workbench/api/node/extHostFileSystem.ts @@ -8,7 +8,7 @@ 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'; @@ -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 + let docLink = typeConverter.DocumentLink.to(link); + if (docLink.target) { + result.push(docLink); } } return result; diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 8cbf6ad2d1e..fc0a93e2f08 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -15,7 +15,7 @@ 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, LinkDto, CodeLensDto } 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'; @@ -26,6 +26,9 @@ import { IExtensionDescription } from 'vs/workbench/services/extensions/common/e import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionIdentifier } 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 +42,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); @@ -105,7 +108,7 @@ class CodeLensAdapter { ) { } provideCodeLenses(resource: URI, token: CancellationToken): Promise { - const doc = this._documents.getDocumentData(resource).document; + const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideCodeLenses(doc, token)).then(lenses => { let result: CodeLensDto[] = []; @@ -122,18 +125,18 @@ class CodeLensAdapter { }); } - resolveCodeLens(resource: URI, symbol: CodeLensDto, token: CancellationToken): Promise { + resolveCodeLens(resource: URI, symbol: CodeLensDto, token: CancellationToken): Promise { const lens = this._heapService.get(ObjectIdentifier.of(symbol)); if (!lens) { - return undefined; + return Promise.resolve(undefined); } - let resolve: Promise; + let resolve: Promise; if (typeof this._provider.resolveCodeLens !== 'function' || lens.isResolved) { resolve = Promise.resolve(lens); } else { - resolve = asPromise(() => this._provider.resolveCodeLens(lens, token)); + resolve = asPromise(() => this._provider.resolveCodeLens!(lens, token)); } return resolve.then(newLens => { @@ -144,13 +147,54 @@ class CodeLensAdapter { } } +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.getDocumentData(resource).document; + 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; + }); + } + + resolveCodeInset(symbol: CodeInsetDto, webview: vscode.Webview, token: CancellationToken): Promise { + + const inset = this._heapService.get(ObjectIdentifier.of(symbol)); + if (!inset) { + return Promise.resolve(symbol); + } + + return asPromise(() => this._provider.resolveCodeInset(inset, webview, token)).then(newInset => { + newInset = newInset || inset; + return symbol; + }); + } +} + function convertToLocationLinks(value: vscode.Definition): modes.LocationLink[] { if (Array.isArray(value)) { return (value as (vscode.DefinitionLink | vscode.Location)[]).map(typeConvert.DefinitionLink.from); } else if (value) { return [typeConvert.DefinitionLink.from(value)]; } - return undefined; + return []; } class DefinitionAdapter { @@ -161,7 +205,7 @@ class DefinitionAdapter { ) { } provideDefinition(resource: URI, position: IPosition, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; + const doc = this._documents.getDocument(resource); let pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideDefinition(doc, pos, token)).then(convertToLocationLinks); } @@ -175,7 +219,7 @@ class DeclarationAdapter { ) { } provideDeclaration(resource: URI, position: IPosition, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; + const doc = this._documents.getDocument(resource); let pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideDeclaration(doc, pos, token)).then(convertToLocationLinks); } @@ -189,7 +233,7 @@ class ImplementationAdapter { ) { } provideImplementation(resource: URI, position: IPosition, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; + const doc = this._documents.getDocument(resource); let pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideImplementation(doc, pos, token)).then(convertToLocationLinks); } @@ -203,7 +247,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); } @@ -216,9 +260,9 @@ 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; + const doc = this._documents.getDocument(resource); let pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideHover(doc, pos, token)).then(value => { @@ -244,9 +288,9 @@ 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; + const doc = this._documents.getDocument(resource); let pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideDocumentHighlights(doc, pos, token)).then(value => { @@ -265,8 +309,8 @@ 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; + provideReferences(resource: URI, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); let pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideReferences(doc, pos, context, token)).then(value => { @@ -294,9 +338,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); @@ -317,7 +361,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[] = []; @@ -369,9 +413,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)) { @@ -389,9 +433,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 => { @@ -412,9 +456,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 => { @@ -450,7 +494,7 @@ 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); } } @@ -462,19 +506,19 @@ class NavigateTypeAdapter { }); } - 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 { @@ -499,9 +543,9 @@ 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; + const doc = this._documents.getDocument(resource); let pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideRenameEdits(doc, pos, newName, token)).then(value => { @@ -512,7 +556,7 @@ class RenameAdapter { }, err => { let rejectReason = RenameAdapter._asMessage(err); if (rejectReason) { - return { rejectReason, edits: undefined }; + return { rejectReason, edits: undefined! }; } else { // generic error return Promise.reject(err); @@ -520,17 +564,17 @@ 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; + const doc = this._documents.getDocument(resource); let pos = typeConvert.Position.to(position); return asPromise(() => this._provider.prepareRename(doc, pos, token)).then(rangeOrLocation => { - let range: vscode.Range; + let range: vscode.Range | undefined; let text: string; if (Range.isRange(rangeOrLocation)) { range = rangeOrLocation; @@ -552,14 +596,14 @@ class RenameAdapter { }, err => { let rejectReason = RenameAdapter._asMessage(err); if (rejectReason) { - return { rejectReason, range: undefined, text: undefined }; + return { rejectReason, range: undefined!, text: undefined! }; } else { 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') { @@ -591,7 +635,7 @@ class SuggestAdapter { 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( @@ -643,18 +687,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); @@ -670,7 +714,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; @@ -684,7 +728,7 @@ 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, @@ -740,8 +784,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); @@ -779,8 +823,8 @@ 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)) { @@ -796,18 +840,18 @@ class LinkProviderAdapter { }); } - resolveLink(link: LinkDto, 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 +868,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 +885,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 +905,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 +923,35 @@ class SelectionRangeAdapter { private readonly _provider: vscode.SelectionRangeProvider ) { } - provideSelectionRanges(resource: URI, position: IPosition, token: CancellationToken): Promise { + provideSelectionRanges(resource: URI, pos: 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; + const positions = pos.map(typeConvert.Position.to); + + return asPromise(() => this._provider.provideSelectionRanges(document, positions, token)).then(allProviderRanges => { + if (isFalsyOrEmpty(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 []; + } + + let allResults: modes.SelectionRange[][] = []; + for (let i = 0; i < positions.length; i++) { + const oneResult: modes.SelectionRange[] = []; + allResults.push(oneResult); + + const oneProviderRanges = allProviderRanges[i]; + let last: vscode.Position | vscode.Range = positions[i]; + for (const selectionRange of oneProviderRanges) { + if (!selectionRange.range.contains(last)) { + throw new Error('INVALID selection range, must contain the previous range'); + } + oneResult.push(typeConvert.SelectionRange.from(selectionRange)); + last = selectionRange.range; } - result.push(typeConvert.SelectionRange.from(sel)); - last = sel.range; } - return result; + return allResults; }); } } @@ -901,7 +960,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 +977,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 +985,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,6 +1003,7 @@ 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[] { @@ -953,7 +1014,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return [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, @@ -992,20 +1053,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, extenson: IExtensionDescription) => 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) { + let 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); } ); @@ -1034,7 +1100,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 +1115,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); } @@ -1064,6 +1130,37 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { 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) { + 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.extensionLocation); + return adapter.resolveCodeInset(symbol, webview, token); + }); + } + // --- declaration registerDefinitionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { @@ -1114,7 +1211,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 +1223,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 +1235,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 +1243,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 +1256,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 +1296,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 +1312,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)); } @@ -1245,8 +1342,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,7 +1352,7 @@ 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)); } @@ -1267,11 +1364,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { 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)); } @@ -1307,8 +1404,8 @@ 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 @@ -1350,7 +1447,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { }; } - private static _serializeOnEnterRules(onEnterRules: vscode.OnEnterRule[]): ISerializedOnEnterRule[] { + private static _serializeOnEnterRules(onEnterRules: vscode.OnEnterRule[]): ISerializedOnEnterRule[] | undefined | null { if (typeof onEnterRules === 'undefined') { return undefined; } @@ -1372,7 +1469,7 @@ 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(); diff --git a/src/vs/workbench/api/node/extHostLanguages.ts b/src/vs/workbench/api/node/extHostLanguages.ts index 6449d452e76..4d67385bd67 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 d416c323d2b..87dd7be3ef4 100644 --- a/src/vs/workbench/api/node/extHostLogService.ts +++ b/src/vs/workbench/api/node/extHostLogService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { join } from 'vs/base/common/paths'; +import { joinWithSlashes } from 'vs/base/common/extpath'; 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'; @@ -23,7 +23,7 @@ export class ExtHostLogService extends DelegatedLogService implements ILogServic ) { super(createSpdLogService(ExtensionHostLogFileName, logLevel, logsPath)); this._logsPath = logsPath; - this.logFile = URI.file(join(logsPath, `${ExtensionHostLogFileName}.log`)); + this.logFile = URI.file(joinWithSlashes(logsPath, `${ExtensionHostLogFileName}.log`)); } $setLevel(level: LogLevel): void { @@ -31,6 +31,6 @@ export class ExtHostLogService extends DelegatedLogService implements ILogServic } getLogDirectory(extensionID: ExtensionIdentifier): string { - return join(this._logsPath, extensionID.value); + return joinWithSlashes(this._logsPath, extensionID.value); } } diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index a72e053e94d..e009ffb6dcb 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/contrib/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'; @@ -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 4eb444f677a..c365acee488 100644 --- a/src/vs/workbench/api/node/extHostProgress.ts +++ b/src/vs/workbench/api/node/extHostProgress.ts @@ -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 c01fbad3ef8..c2a196eb685 100644 --- a/src/vs/workbench/api/node/extHostQuickOpen.ts +++ b/src/vs/workbench/api/node/extHostQuickOpen.ts @@ -25,7 +25,7 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { private _commands: ExtHostCommands; private _onDidSelectItem: (handle: number) => void; - private _validateInput: (input: string) => string | Thenable; + private _validateInput?: (input: string) => string | undefined | null | Thenable; private _sessions = new Map(); @@ -72,10 +72,10 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { let 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 +99,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 +137,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,11 +149,11 @@ 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 @@ -164,7 +164,7 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { return undefined; } - return this._workspace.getWorkspaceFolders().filter(folder => folder.uri.toString() === selectedFolder.uri.toString())[0]; + return this._workspace.getWorkspaceProvider().then(workspaceProvider => workspaceProvider.getWorkspaceFolders().filter(folder => folder.uri.toString() === selectedFolder.uri.toString())[0]); }); } diff --git a/src/vs/workbench/api/node/extHostSearch.fileIndex.ts b/src/vs/workbench/api/node/extHostSearch.fileIndex.ts index d0ed46e8e2a..57ccbf500e8 100644 --- a/src/vs/workbench/api/node/extHostSearch.fileIndex.ts +++ b/src/vs/workbench/api/node/extHostSearch.fileIndex.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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 { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -14,7 +14,7 @@ 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 { ICachedSearchStats, IFileIndexProviderStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, ISearchCompleteStats } from 'vs/workbench/services/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'; diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 53ff6f41386..82c7aa8769a 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -8,7 +8,7 @@ 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 { IFileQuery, IFolderQuery, IRawFileQuery, IRawQuery, IRawTextQuery, ISearchCompleteStats, ITextQuery } from 'vs/workbench/services/search/common/search'; import { FileIndexSearchManager } from 'vs/workbench/api/node/extHostSearch.fileIndex'; import { FileSearchManager } from 'vs/workbench/services/search/node/fileSearchManager'; import { SearchService } from 'vs/workbench/services/search/node/rawSearchService'; @@ -35,12 +35,12 @@ export class ExtHostSearch implements ExtHostSearchShape { 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(); @@ -124,6 +124,10 @@ export class ExtHostSearch implements ExtHostSearchShape { }, token); } else { const indexProvider = this._fileIndexProvider.get(handle); + if (!indexProvider) { + throw new Error('unknown provider: ' + handle); + } + return this._fileIndexSearchManager.fileSearch(query, indexProvider, batch => { this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); }, token); @@ -147,7 +151,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 { @@ -163,8 +171,8 @@ export class ExtHostSearch implements ExtHostSearchShape { $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/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index fb83f139385..cdd7cc85edb 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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'; @@ -16,7 +16,7 @@ import { IExtensionDescription } from 'vs/workbench/services/extensions/common/e 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, ExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace'; import * as vscode from 'vscode'; import { TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO, ProcessExecutionOptionsDTO, ProcessExecutionDTO, @@ -222,7 +222,7 @@ namespace TaskDTO { }; return result; } - export function to(value: TaskDTO, workspace: ExtHostWorkspace): types.Task { + export function to(value: TaskDTO, workspace: ExtHostWorkspaceProvider): types.Task { if (value === undefined || value === null) { return undefined; } @@ -299,8 +299,8 @@ 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 function to(value: TaskExecutionDTO, tasks: ExtHostTask, workspaceProvider: ExtHostWorkspaceProvider): vscode.TaskExecution { + return new TaskExecutionImpl(tasks, value.id, TaskDTO.to(value.task, workspaceProvider)); } export function from(value: vscode.TaskExecution): TaskExecutionDTO { return { @@ -341,10 +341,6 @@ export class ExtHostTask implements ExtHostTaskShape { this._taskExecutions = new Map(); } - public get extHostWorkspace(): ExtHostWorkspace { - return this._workspaceService; - } - public registerTaskProvider(extension: IExtensionDescription, provider: vscode.TaskProvider): vscode.Disposable { if (!provider) { return new types.Disposable(() => { }); @@ -363,10 +359,11 @@ export class ExtHostTask implements ExtHostTaskShape { } public fetchTasks(filter?: vscode.TaskFilter): Promise { - return this._proxy.$fetchTasks(TaskFilterDTO.from(filter)).then((values) => { + return this._proxy.$fetchTasks(TaskFilterDTO.from(filter)).then(async (values) => { let result: vscode.Task[] = []; + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); for (let value of values) { - let task = TaskDTO.to(value, this._workspaceService); + let task = TaskDTO.to(value, workspaceProvider); if (task) { result.push(task); } @@ -375,17 +372,18 @@ export class ExtHostTask implements ExtHostTaskShape { }); } - public executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { + public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); let 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)); + return this._proxy.$executeTask(TaskHandleDTO.from(tTask)).then(value => this.getTaskExecution(value, workspaceProvider, task)); } else { let dto = TaskDTO.from(task, extension); if (dto === undefined) { return Promise.reject(new Error('Task is not valid')); } - return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, task)); + return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, workspaceProvider, task)); } } @@ -406,9 +404,10 @@ export class ExtHostTask implements ExtHostTaskShape { return this._onDidExecuteTask.event; } - public $onDidStartTask(execution: TaskExecutionDTO): void { + public async $onDidStartTask(execution: TaskExecutionDTO): Promise { + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); this._onDidExecuteTask.fire({ - execution: this.getTaskExecution(execution) + execution: this.getTaskExecution(execution, workspaceProvider) }); } @@ -416,8 +415,9 @@ 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 workspaceProvider = await this._workspaceService.getWorkspaceProvider(); + const _execution = this.getTaskExecution(execution, workspaceProvider); this._taskExecutions.delete(execution.id); this._onDidTerminateTask.fire({ execution: _execution @@ -428,8 +428,9 @@ 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 workspaceProvider = await this._workspaceService.getWorkspaceProvider(); + const execution = this.getTaskExecution(value.id, workspaceProvider); if (execution) { this._onDidTaskProcessStarted.fire({ execution: execution, @@ -442,8 +443,9 @@ 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 workspaceProvider = await this._workspaceService.getWorkspaceProvider(); + const execution = this.getTaskExecution(value.id, workspaceProvider); if (execution) { this._onDidTaskProcessEnded.fire({ execution: execution, @@ -476,13 +478,14 @@ export class ExtHostTask implements ExtHostTaskShape { 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(); + const workspaceProvider = await this._workspaceService.getWorkspaceProvider(); 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 workspaceFolder = workspaceProvider.resolveWorkspaceFolder(uri); + let resolver = new ExtHostVariableResolverService(workspaceProvider, this._editorService, configProvider); let ws: IWorkspaceFolder = { uri: workspaceFolder.uri, name: workspaceFolder.name, @@ -515,7 +518,7 @@ export class ExtHostTask implements ExtHostTaskShape { return this._handleCounter++; } - private getTaskExecution(execution: TaskExecutionDTO | string, task?: vscode.Task): TaskExecutionImpl { + private getTaskExecution(execution: TaskExecutionDTO | string, workspaceProvider: ExtHostWorkspaceProvider, task?: vscode.Task): TaskExecutionImpl { if (typeof execution === 'string') { return this._taskExecutions.get(execution); } @@ -524,7 +527,7 @@ export class ExtHostTask implements ExtHostTaskShape { if (result) { return result; } - result = new TaskExecutionImpl(this, execution.id, task ? task : TaskDTO.to(execution.task, this._workspaceService)); + result = new TaskExecutionImpl(this, execution.id, task ? task : TaskDTO.to(execution.task, workspaceProvider)); this._taskExecutions.set(execution.id, result); return result; } diff --git a/src/vs/workbench/api/node/extHostTextEditors.ts b/src/vs/workbench/api/node/extHostTextEditors.ts index 7843bd63662..2ebad811a31 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(); } @@ -106,7 +107,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 +128,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 diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index 9d75a07dd02..f5e8ea7aba9 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'; @@ -18,7 +18,6 @@ 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'; type TreeItemHandle = string; @@ -142,6 +141,8 @@ interface TreeNode { children: TreeNode[]; } +type TreeData = { message: boolean, element: T | null | undefined | false }; + class ExtHostTreeView extends Disposable { private static LABEL_HANDLE_PREFIX = '0'; @@ -171,27 +172,45 @@ 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(); this.dataProvider = options.treeDataProvider; 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[] }>(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 { @@ -232,7 +251,7 @@ 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 { @@ -448,7 +467,7 @@ class ExtHostTreeView extends Disposable { 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 childrenNodes = (this.getChildrenNodes(parent) || []); diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index 3cc5c1cb05b..a445750d59d 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -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); + const resUris: { [href: string]: UriComponents } = Object.create(null); + res.uris = resUris; + let 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 } @@ -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.toString()) : 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); } } @@ -877,7 +896,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 +905,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 +922,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 +954,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, @@ -975,7 +994,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 +1008,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 950d8cee119..71dc9a8b3a7 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 { @@ -217,6 +229,7 @@ export class Position { } } +@es5ClassCompat export class Range { static isRange(thing: any): thing is vscode.Range { @@ -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 { @@ -524,6 +557,7 @@ export interface IFileTextEdit { edit: TextEdit; } +@es5ClassCompat export class WorkspaceEdit implements vscode.WorkspaceEdit { private _edits = new Array(); @@ -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,7 +1084,17 @@ 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'); +@es5ClassCompat export class SelectionRangeKind { private static readonly _sep = '.'; @@ -1060,6 +1114,7 @@ export class SelectionRangeKind { } } +@es5ClassCompat export class SelectionRange { kind: SelectionRangeKind; @@ -1072,6 +1127,7 @@ export class SelectionRange { } +@es5ClassCompat export class CodeLens { range: Range; @@ -1088,6 +1144,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 +1188,7 @@ export class MarkdownString { } } +@es5ClassCompat export class ParameterInformation { label: string | [number, number]; @@ -1129,6 +1200,7 @@ export class ParameterInformation { } } +@es5ClassCompat export class SignatureInformation { label: string; @@ -1142,6 +1214,7 @@ export class SignatureInformation { } } +@es5ClassCompat export class SignatureHelp { signatures: SignatureInformation[]; @@ -1166,8 +1239,8 @@ export enum CompletionTriggerKind { } export interface CompletionContext { - triggerKind: CompletionTriggerKind; - triggerCharacter: string; + readonly triggerKind: CompletionTriggerKind; + readonly triggerCharacter?: string; } export enum CompletionItemKind { @@ -1198,19 +1271,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 +1309,7 @@ export class CompletionItem implements vscode.CompletionItem { } } +@es5ClassCompat export class CompletionList { isIncomplete?: boolean; @@ -1314,7 +1389,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 +1399,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 +1418,7 @@ export class DocumentLink { } } +@es5ClassCompat export class Color { readonly red: number; readonly green: number; @@ -1358,6 +1435,7 @@ export class Color { export type IColorFormat = string | { opaque: string, transparent: string }; +@es5ClassCompat export class ColorInformation { range: Range; @@ -1375,6 +1453,7 @@ export class ColorInformation { } } +@es5ClassCompat export class ColorPresentation { label: string; textEdit?: TextEdit; @@ -1416,6 +1495,7 @@ export enum TaskPanelKind { New = 3 } +@es5ClassCompat export class TaskGroup implements vscode.TaskGroup { private _id: string; @@ -1458,6 +1538,7 @@ export class TaskGroup implements vscode.TaskGroup { } } +@es5ClassCompat export class ProcessExecution implements vscode.ProcessExecution { private _process: string; @@ -1530,6 +1611,7 @@ export class ProcessExecution implements vscode.ProcessExecution { } } +@es5ClassCompat export class ShellExecution implements vscode.ShellExecution { private _commandLine: string; @@ -1626,6 +1708,7 @@ export enum TaskScope { Workspace = 2 } +@es5ClassCompat export class Task implements vscode.Task { private static ProcessType: string = 'process'; @@ -1858,6 +1941,7 @@ export enum ProgressLocation { Notification = 15 } +@es5ClassCompat export class TreeItem { label?: string | vscode.TreeItemLabel; @@ -1885,18 +1969,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 +2001,7 @@ export enum ConfigurationTarget { WorkspaceFolder = 3 } +@es5ClassCompat export class RelativePattern implements IRelativePattern { base: string; baseFolder?: URI; @@ -1938,12 +2028,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 +2061,7 @@ export class Breakpoint { } } +@es5ClassCompat export class SourceBreakpoint extends Breakpoint { readonly location: Location; @@ -1986,6 +2074,7 @@ export class SourceBreakpoint extends Breakpoint { } } +@es5ClassCompat export class FunctionBreakpoint extends Breakpoint { readonly functionName: string; @@ -1998,6 +2087,7 @@ export class FunctionBreakpoint extends Breakpoint { } } +@es5ClassCompat export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { readonly command: string; readonly args: string[]; @@ -2010,6 +2100,7 @@ export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { } } +@es5ClassCompat export class DebugAdapterServer implements vscode.DebugAdapterServer { readonly port: number; readonly host?: string; @@ -2021,6 +2112,7 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer { } /* +@es5ClassCompat export class DebugAdapterImplementation implements vscode.DebugAdapterImplementation { readonly implementation: any; @@ -2048,6 +2140,7 @@ export enum FileChangeType { Deleted = 3, } +@es5ClassCompat export class FileSystemError extends Error { static FileExists(messageOrUri?: string | URI): FileSystemError { @@ -2090,6 +2183,7 @@ export class FileSystemError extends Error { //#region folding api +@es5ClassCompat export class FoldingRange { start: number; @@ -2125,6 +2219,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 0906dd92aad..df6b86f3e92 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'; 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 ) { @@ -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 1a16b50202b..a0fec19ef9c 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -3,28 +3,28 @@ * Licensed under the MIT License. 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/contrib/search/common/queryBuilder'; import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import * as vscode from 'vscode'; -import { ExtHostWorkspaceShape, IMainContext, IWorkspaceData, MainContext, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol'; +import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, IMainContext, MainContext } from './extHost.protocol'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { Barrier } from 'vs/base/common/async'; function isFolderEqual(folderA: URI, folderB: URI): boolean { return isEqual(folderA, folderB, !isLinux); @@ -68,7 +68,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); @@ -127,7 +127,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,20 +135,62 @@ 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 { - private readonly _onDidChangeWorkspace = new Emitter(); - private readonly _proxy: MainThreadWorkspaceShape; + private readonly _mainContext: IMainContext; + private readonly _logService: ILogService; + private readonly _requestIdProvider: Counter; + private readonly _barrier: Barrier; + private _actual: ExtHostWorkspaceProvider | null; - private _confirmedWorkspace: ExtHostWorkspaceImpl; + constructor( + mainContext: IMainContext, + logService: ILogService, + requestIdProvider: Counter + ) { + this._mainContext = mainContext; + this._logService = logService; + this._requestIdProvider = requestIdProvider; + this._barrier = new Barrier(); + this._actual = null; + } + + public getWorkspaceProvider(): Promise { + return this._barrier.wait().then(_ => this._actual!); + } + + $initializeWorkspace(data: IWorkspaceData): void { + this._actual = new ExtHostWorkspaceProvider(this._mainContext, data, this._logService, this._requestIdProvider); + this._barrier.open(); + } + + $acceptWorkspaceData(workspace: IWorkspaceData): void { + if (this._actual) { + this._actual.$acceptWorkspaceData(workspace); + } + } + + $handleTextSearchResult(result: IRawFileMatch2, requestId: number): void { + if (this._actual) { + this._actual.$handleTextSearchResult(result, requestId); + } + } + +} +export class ExtHostWorkspaceProvider { + + private readonly _onDidChangeWorkspace = new Emitter(); + + private _confirmedWorkspace?: ExtHostWorkspaceImpl; private _unconfirmedWorkspace?: ExtHostWorkspaceImpl; - private _messageService: MainThreadMessageServiceShape; + private readonly _proxy: MainThreadWorkspaceShape; + private readonly _messageService: MainThreadMessageServiceShape; readonly onDidChangeWorkspace: Event = this._onDidChangeWorkspace.event; @@ -162,12 +204,12 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { ) { this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace); this._messageService = mainContext.getProxy(MainContext.MainThreadMessageService); - this._confirmedWorkspace = ExtHostWorkspaceImpl.toExtHostWorkspace(data).workspace; + this._confirmedWorkspace = ExtHostWorkspaceImpl.toExtHostWorkspace(data).workspace || undefined; } // --- workspace --- - get workspace(): Workspace { + get workspace(): Workspace | undefined { return this._actualWorkspace; } @@ -175,7 +217,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { return this._actualWorkspace ? this._actualWorkspace.name : undefined; } - private get _actualWorkspace(): ExtHostWorkspaceImpl { + private get _actualWorkspace(): ExtHostWorkspaceImpl | undefined { return this._unconfirmedWorkspace || this._confirmedWorkspace; } @@ -215,7 +257,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]; @@ -250,7 +292,7 @@ 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; } @@ -283,19 +325,22 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { getRelativePath(pathOrUri: string | vscode.Uri, includeWorkspace?: boolean): string | undefined { + let resource: URI | undefined; let path: string | undefined; 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 +348,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 +369,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 +379,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 @@ -362,7 +407,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { } } - let excludePatternOrDisregardExcludes: string | false | undefined; + let excludePatternOrDisregardExcludes: string | false = false; if (exclude === null) { excludePatternOrDisregardExcludes = false; } else if (exclude) { diff --git a/src/vs/workbench/api/shared/tasks.ts b/src/vs/workbench/api/shared/tasks.ts index 0a1d3b6535b..e260f14f1f5 100644 --- a/src/vs/workbench/api/shared/tasks.ts +++ b/src/vs/workbench/api/shared/tasks.ts @@ -79,16 +79,16 @@ export interface TaskHandleDTO { export interface TaskDTO { _id: string; - name: string; - execution: ProcessExecutionDTO | ShellExecutionDTO; + name?: string; + execution?: ProcessExecutionDTO | ShellExecutionDTO; 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 { diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 082e8abdf2d..be6ecd99e80 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -85,7 +85,7 @@ class ToggleCenteredLayout extends Action { run(): Promise { this.partService.centerEditorLayout(!this.partService.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; @@ -231,7 +231,7 @@ export class ToggleEditorVisibilityAction extends Action { const hideEditor = this.partService.isVisible(Parts.EDITOR_PART); this.partService.setEditorHidden(hideEditor); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -258,7 +258,7 @@ export class ToggleSidebarVisibilityAction extends Action { const hideSidebar = this.partService.isVisible(Parts.SIDEBAR_PART); this.partService.setSideBarHidden(hideSidebar); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -362,7 +362,7 @@ class ToggleZenMode extends Action { run(): Promise { this.partService.toggleZenMode(); - return Promise.resolve(null); + return Promise.resolve(); } } diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 94a19170266..d47aa860d21 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 diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index 5e80d459df0..a7a523100ab 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -68,9 +68,9 @@ abstract class BaseNavigationAction extends Action { 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 { diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index a8847e2cbb7..4520821cde9 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -15,7 +15,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic 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'; @@ -161,7 +160,7 @@ export class SaveWorkspaceAsAction extends Action { 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) + defaultUri: this.dialogService.defaultWorkspacePath() }); } } diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index 95c6eba1bcb..c9843ad60c7 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'; @@ -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/dnd.ts b/src/vs/workbench/browser/dnd.ts index 739f53af72a..03658b5cfe3 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { basename, normalize } from 'vs/base/common/paths'; +import { normalize } from 'vs/base/common/path'; +import { basename, basenameOrAuthority } from 'vs/base/common/resources'; import { IFileService } from 'vs/platform/files/common/files'; import { IWindowsService, IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; @@ -26,7 +27,6 @@ import { coalesce } from 'vs/base/common/arrays'; import { ServicesAccessor, IInstantiationService } 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'; @@ -368,11 +368,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) diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index e38e0ce1137..97f0a3e9d6a 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -386,10 +386,10 @@ class ResourceLabelWidget extends IconLabel { } if (this.label) { - const configuredLangId = getConfiguredLangId(this.modelService, this.label.resource); + const configuredLangId = this.label.resource ? getConfiguredLangId(this.modelService, this.label.resource) : null; if (this.lastKnownConfiguredLangId !== configuredLangId) { clearIconCache = true; - this.lastKnownConfiguredLangId = configuredLangId; + this.lastKnownConfiguredLangId = configuredLangId || undefined; } } @@ -428,7 +428,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 +444,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/media/part.css b/src/vs/workbench/browser/media/part.css index 32c3fe76eae..60351cbaf21 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -3,6 +3,14 @@ * Licensed under the MIT License. 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 00000000000..ffdf30c293f --- /dev/null +++ b/src/vs/workbench/browser/media/style.css @@ -0,0 +1,222 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* Font Families (with CJK support) */ + +.monaco-workbench.mac { font-family: -apple-system, BlinkMacSystemFont, sans-serif; } +.monaco-workbench.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; } +.monaco-workbench.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; } +.monaco-workbench.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; } +.monaco-workbench.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; } + +.monaco-workbench.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; } +.monaco-workbench.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; } +.monaco-workbench.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; } +.monaco-workbench.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Meiryo", sans-serif; } +.monaco-workbench.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; } + +.monaco-workbench.linux { font-family: "Ubuntu", "Droid Sans", sans-serif; } +.monaco-workbench.linux:lang(zh-Hans) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } +.monaco-workbench.linux:lang(zh-Hant) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; } +.monaco-workbench.linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } +.monaco-workbench.linux:lang(ko) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } + +.monaco-workbench.mac { --monaco-monospace-font: Monaco, Menlo, Consolas, "Inconsolata", "Courier New", monospace; } +.monaco-workbench.windows { --monaco-monospace-font: Monaco, Menlo, Consolas, "Inconsolata", "Courier New", monospace; } +.monaco-workbench.linux { --monaco-monospace-font: Monaco, Menlo, Consolas, "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-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; + } +} + +.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 .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/panel.ts b/src/vs/workbench/browser/panel.ts index 4cd74b0ebbe..eee8dbfc218 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -89,13 +89,13 @@ export abstract class TogglePanelAction extends Action { 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 { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 2144bb90f7e..9a800cf87af 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -62,7 +62,7 @@ export class ViewletActivityAction extends ActivityAction { if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.activity.id) { this.logAction('hide'); this.partService.setSideBarHidden(true); - return Promise.resolve(null); + return Promise.resolve(); } this.logAction('show'); @@ -97,7 +97,7 @@ export class ToggleViewletAction extends Action { // Hide sidebar if selected viewlet already visible if (sideBarVisible && activeViewlet && activeViewlet.getId() === this._viewlet.id) { this.partService.setSideBarHidden(true); - return Promise.resolve(null); + return Promise.resolve(); } return this.viewletService.openViewlet(this._viewlet.id, true); @@ -211,9 +211,9 @@ class SwitchSideBarViewAction extends Action { 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]; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 1f55bd25236..b8b8e289b18 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -6,7 +6,7 @@ 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 { Event, 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'; @@ -50,6 +50,16 @@ export class ActivitybarPart extends Part implements ISerializableView { private static readonly ACTION_HEIGHT = 50; private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets'; + element: HTMLElement; + + readonly minimumWidth: number = 50; + readonly maximumWidth: number = 50; + readonly minimumHeight: number = 0; + readonly maximumHeight: number = Number.POSITIVE_INFINITY; + + private _onDidChange = this._register(new Emitter<{ width: number; height: number; }>()); + get onDidChange(): Event<{ width: number, height: number }> { return this._onDidChange.event; } + private dimension: Dimension; private globalActionBar: ActionBar; @@ -59,15 +69,6 @@ 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, diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index db313c4c8a9..4869bb20391 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -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]; @@ -507,7 +507,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, order: number | undefined, pinned: boolean, visible: boolean): ICompositeBarModelItem { const options = this.options; return { id, name, pinned, order, visible, @@ -521,7 +521,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; diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 93a7671370b..54c9a40efa7 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); @@ -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); @@ -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 8064787e489..30f54594cf3 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -54,8 +54,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; @@ -84,7 +84,7 @@ 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 ) { @@ -140,7 +140,7 @@ export abstract class CompositePart extends Part { composite.focus(); } - this._onDidCompositeOpen.fire({ composite, focus }); + this.onDidCompositeOpen.fire({ composite, focus }); return composite; } @@ -152,7 +152,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; @@ -375,7 +375,7 @@ export abstract class CompositePart extends Part { // Empty Actions this.toolBar.setActions([])(); - this._onDidCompositeClose.fire(composite); + this.onDidCompositeClose.fire(composite); return composite; } @@ -415,7 +415,7 @@ export abstract class CompositePart extends Part { }, updateStyles: () => { - titleLabel.style.color = $this.getColor($this.titleForegroundColor); + titleLabel.style.color = $this.titleForegroundColor ? $this.getColor($this.titleForegroundColor) : null; } }; } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 917a426bdbd..947618b4b32 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -196,7 +196,7 @@ export class BreadcrumbsControl { this.domNode.remove(); } - layout(dim: dom.Dimension): void { + layout(dim: dom.Dimension | undefined): void { this._widget.layout(dim); } @@ -260,9 +260,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)) { @@ -313,7 +316,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 +339,7 @@ export class BreadcrumbsControl { return; } if (!editorViewState) { - editorViewState = editor.saveViewState(); + editorViewState = editor.saveViewState() || undefined; } const { symbol } = data.target; editor.revealRangeInCenter(symbol.range, ScrollType.Smooth); @@ -538,7 +541,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({ @@ -554,7 +561,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({ @@ -567,6 +578,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); } }); @@ -580,6 +594,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); } }); @@ -591,8 +608,12 @@ 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); + const widget = breadcrumbs.getWidget(groups.activeGroup.id); + if (!widget) { + return; + } + widget.setFocused(undefined); + widget.setSelection(undefined); groups.activeGroup.activeControl.focus(); } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 5edc694dd85..70dbea9d198 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -117,7 +117,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 b22e048f38e..667bb7cb68e 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from 'vs/base/common/event'; 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 { joinWithSlashes } from 'vs/base/common/extpath'; import { basename, dirname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/breadcrumbscontrol'; @@ -299,7 +299,7 @@ class FileFilter implements ITreeFilter { continue; } let patternAbs = pattern.indexOf('**/') !== 0 - ? join(folder.uri.path, pattern) + ? joinWithSlashes(folder.uri.path, pattern) : pattern; adjustedConfig[patternAbs] = excludesConfig[pattern]; diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index b262c8aeef8..5c1e6ef0f87 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -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 } @@ -165,7 +165,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) { @@ -193,7 +193,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); @@ -261,7 +261,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/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index d2c83f8e88d..05ed83a7bca 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -428,7 +428,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 +441,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 +491,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 +579,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 } { if (context) { return { editor: context.editor, group: editorGroupService.getGroup(context.groupId) }; } @@ -593,7 +593,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 +629,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 => { @@ -704,7 +704,7 @@ export class CloseEditorsInOtherGroupsAction extends Action { 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); + return Promise.resolve(); } return g.closeAllEditors(); @@ -732,7 +732,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(); } } @@ -763,7 +763,7 @@ export class BaseMoveGroupAction extends Action { 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 @@ -930,7 +930,7 @@ export abstract class BaseNavigateEditorAction extends Action { return group.openEditor(editor); } - protected abstract navigate(): IEditorIdentifier; + protected abstract navigate(): IEditorIdentifier | undefined; } export class OpenNextEditor extends BaseNavigateEditorAction { @@ -947,7 +947,7 @@ 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; @@ -982,7 +982,7 @@ 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; @@ -1105,7 +1105,7 @@ export class NavigateForwardAction extends Action { run(): Promise { this.historyService.forward(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1121,7 +1121,7 @@ export class NavigateBackwardsAction extends Action { run(): Promise { this.historyService.back(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1137,7 +1137,7 @@ export class NavigateToLastEditLocationAction extends Action { run(): Promise { this.historyService.openLastEditLocation(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1153,7 +1153,7 @@ export class NavigateLastAction extends Action { run(): Promise { this.historyService.last(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1296,7 +1296,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 +1314,7 @@ export class OpenNextRecentlyUsedEditorAction extends Action { run(): Promise { this.historyService.forward(true); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1330,7 +1330,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 9a22dbc334b..25e420329d5 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, IActiveEditor } 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'; @@ -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: IActiveEditor, accessor: ServicesAccessor): void { const group = control.group; let index = group.getIndexOfEditor(control.input); switch (args.to) { @@ -141,7 +158,7 @@ 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: IActiveEditor, accessor: ServicesAccessor): void { const editorGroupService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); @@ -422,7 +439,7 @@ export function splitEditor(editorGroupService: IEditorGroupsService, direction: 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 { @@ -642,7 +659,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; } @@ -662,7 +679,7 @@ function resolveCommandsContext(editorGroupService: IEditorGroupsService, contex // 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' ? group.getEditor(context.editorIndex) : undefined; let control = group ? group.activeControl : undefined; // Fallback to active group as needed @@ -675,7 +692,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; diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index a2c347895ff..3196a73caae 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -14,6 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti 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 { IActiveEditor } from 'vs/workbench/services/editor/common/editorService'; export interface IOpenEditorResult { readonly control: BaseEditor; @@ -52,8 +53,8 @@ export class EditorControl extends Disposable { this.editorOperation = this._register(new LongRunningOperation(progressService)); } - get activeControl() { - return this._activeControl; + get activeControl(): IActiveEditor | null { + return this._activeControl as IActiveEditor | null; } openEditor(editor: EditorInput, options?: EditorOptions): Promise { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 4abd300bcd0..1562104e83b 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -21,7 +21,6 @@ import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleCon 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 +32,7 @@ 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 { joinWithSlashes } from 'vs/base/common/extpath'; 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,6 +44,7 @@ import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemAc import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { isErrorWithActions, IErrorWithActions } from 'vs/base/common/errorsWithActions'; import { URI } from 'vs/base/common/uri'; +import { IActiveEditor } from 'vs/workbench/services/editor/common/editorService'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -108,7 +108,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>(); @@ -652,15 +651,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group.count; } - get activeControl(): BaseEditor { - return this.editorControl ? this.editorControl.activeControl : undefined; + get activeControl(): IActiveEditor | undefined { + return this.editorControl ? this.editorControl.activeControl || undefined : undefined; } - get activeEditor(): EditorInput { + get activeEditor(): EditorInput | null { return this._group.activeEditor; } - get previewEditor(): EditorInput { + get previewEditor(): EditorInput | null { return this._group.previewEditor; } @@ -680,7 +679,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); } @@ -818,7 +817,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; @@ -858,7 +857,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { let result: IEditor; // 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 @@ -1020,20 +1019,18 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // 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 @@ -1461,7 +1458,7 @@ registerThemingParticipant((theme, collector, environment) => { const letterpress = `resources/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('${URI.file(joinWithSlashes(environment.appRoot, letterpress)).toString()}') } `); diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 2c802fcbe74..4bcee05cf65 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -86,6 +86,13 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.state'; private static readonly EDITOR_PART_CENTERED_VIEW_STORAGE_KEY = 'editorpart.centeredview'; + element: HTMLElement; + + private _onDidChange = this._register(new Emitter<{ width: number; height: number; }>()); + get onDidChange(): Event<{ width: number, height: number }> { return this._onDidChange.event; } + + readonly priority: LayoutPriority = LayoutPriority.High; + //#region Events private readonly _onDidLayout: Emitter = this._register(new Emitter()); @@ -135,13 +142,6 @@ 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, diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index d0e97f06e7e..6d36ad6d9e7 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -7,7 +7,7 @@ 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'; @@ -64,7 +64,7 @@ class SideBySideEditorEncodingSupport implements IEncodingSupport { } } -function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport { +function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | null { // Untitled Editor if (input instanceof UntitledEditorInput) { @@ -179,7 +179,7 @@ class State { this._metadata = null; } - update(update: StateDelta): StateChange { + update(update: StateDelta): StateChange | null { const e = new StateChange(); let somethingChanged = false; @@ -282,9 +282,9 @@ export class EditorStatus implements IStatusbarItem { private metadataElement: HTMLElement; 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, @@ -379,7 +379,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); @@ -458,9 +460,9 @@ export class EditorStatus implements IStatusbarItem { } } - private getSelectionLabel(info: IEditorSelectionStatus): string { + private getSelectionLabel(info: IEditorSelectionStatus): string | undefined { if (!info || !info.selections) { - return null; + return undefined; } if (info.selections.length === 1) { @@ -479,7 +481,7 @@ export class EditorStatus implements IStatusbarItem { return strings.format(nlsMultiSelection, info.selections.length); } - return null; + return undefined; } private onModeClick(): void { @@ -499,7 +501,7 @@ export class EditorStatus implements IStatusbarItem { if (!this.screenReaderNotification) { this.screenReaderNotification = this.notificationService.prompt( Severity.Info, - nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code?"), + nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code? (Certain features like folding, minimap or word wrap are disabled when using a screen reader)"), [{ label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"), run: () => { @@ -544,7 +546,7 @@ export class EditorStatus implements IStatusbarItem { private updateStatusBar(): void { const activeControl = this.editorService.activeControl; - const activeCodeEditor = activeControl ? getCodeEditor(activeControl.getControl()) : undefined; + const activeCodeEditor = activeControl ? getCodeEditor(activeControl.getControl()) || undefined : undefined; // Update all states this.onScreenReaderModeChange(activeCodeEditor); @@ -582,11 +584,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; + } } } })); @@ -626,8 +630,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) { @@ -635,15 +639,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(); @@ -660,11 +664,11 @@ 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(); + update.metadata = editor.getMetadata() || undefined; } this.updateState(update); @@ -672,7 +676,7 @@ 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 @@ -701,7 +705,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 @@ -715,7 +719,7 @@ 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); }); } @@ -738,8 +742,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(); @@ -756,11 +760,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]; @@ -794,11 +798,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; } @@ -807,7 +811,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 { @@ -856,19 +860,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 @@ -882,7 +886,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]); @@ -908,8 +912,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) { @@ -990,9 +994,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) => { @@ -1006,15 +1010,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 @@ -1024,11 +1028,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); @@ -1077,7 +1077,7 @@ class ChangeIndentationAction extends Action { return { id: a.id, label: a.label, - detail: (language === LANGUAGE_DEFAULT) ? null : a.alias, + detail: (language === LANGUAGE_DEFAULT) ? undefined : a.alias, run: () => { activeTextEditorWidget.focus(); a.run(); @@ -1159,7 +1159,10 @@ export class ChangeEncodingAction extends Action { } let activeControl = this.editorService.activeControl; - let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeControl.input); + if (!activeControl) { + return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + } + let encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeControl.input); if (!encodingSupport) { return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); } @@ -1203,8 +1206,8 @@ export class ChangeEncodingAction extends Action { const configuredEncoding = this.textResourceConfigurationService.getValue(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) diff --git a/src/vs/workbench/browser/parts/editor/editorWidgets.ts b/src/vs/workbench/browser/parts/editor/editorWidgets.ts index dfa93b7c447..0b2a66006e6 100644 --- a/src/vs/workbench/browser/parts/editor/editorWidgets.ts +++ b/src/vs/workbench/browser/parts/editor/editorWidgets.ts @@ -14,12 +14,12 @@ 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 { 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 { @@ -107,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(); @@ -138,8 +139,12 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit return false; // we need a model } - if (model.uri.scheme !== Schemas.file || !hasWorkspaceFileExtension(model.uri.fsPath)) { - 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) { diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css index 1190f855b69..85e4d64ebb1 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css @@ -54,14 +54,14 @@ background-image: none; } -.windows > .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before { +.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 67be1c251c1..4e1e729e2b7 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -244,7 +244,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, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); if (isGroupActive) { this.editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND); } else { @@ -256,7 +256,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 a0cc90b5735..73446c7b5cb 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; } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index a9802ce7ea3..951d1b81b3c 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -62,7 +62,7 @@ export class TabsTitleControl extends TitleControl { private tabDisposeables: IDisposable[] = []; private dimension: Dimension; - private layoutScheduled: IDisposable; + private layoutScheduled?: IDisposable; private blockRevealActiveTab: boolean; constructor( @@ -203,7 +203,7 @@ export class TabsTitleControl extends TitleControl { // Return if transfer is unsupported if (!this.isSupportedDropTransfer(e)) { - e.dataTransfer.dropEffect = 'none'; + e.dataTransfer!.dropEffect = 'none'; return; } @@ -214,7 +214,7 @@ export class TabsTitleControl extends TitleControl { 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; } } @@ -222,7 +222,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); @@ -298,7 +298,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 @@ -562,7 +562,7 @@ 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 @@ -577,7 +577,7 @@ export class TabsTitleControl extends TitleControl { const editor = this.group.getEditor(index); 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 }); @@ -599,7 +599,7 @@ export class TabsTitleControl extends TitleControl { // Return if transfer is unsupported if (!this.isSupportedDropTransfer(e)) { - e.dataTransfer.dropEffect = 'none'; + e.dataTransfer!.dropEffect = 'none'; return; } @@ -610,7 +610,7 @@ export class TabsTitleControl extends TitleControl { 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; } } @@ -618,7 +618,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); @@ -661,7 +661,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) } @@ -670,7 +670,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; @@ -698,9 +699,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: editor.getDescription(verbosity) || undefined, + title: editor.getTitle(Verbosity.LONG) || undefined })); // Shorten labels as needed @@ -752,7 +753,7 @@ export class TabsTitleControl extends TitleControl { if (useLongDescriptions) { mapDescriptionToDuplicates.clear(); duplicateTitles.forEach(label => { - label.description = label.editor.getDescription(Verbosity.LONG); + label.description = label.editor.getDescription(Verbosity.LONG) || undefined; getOrSet(mapDescriptionToDuplicates, label.description, []).push(label); }); } @@ -780,7 +781,7 @@ export class TabsTitleControl extends TitleControl { }); } - private getLabelConfigFlags(value: string) { + private getLabelConfigFlags(value: string | undefined) { switch (value) { case 'short': return { verbosity: Verbosity.SHORT, shortenDuplicates: false }; @@ -925,7 +926,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) { @@ -1032,7 +1033,7 @@ export class TabsTitleControl extends TitleControl { } } - 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; @@ -1193,12 +1194,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/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 7e0e7e52a27..7c8160aa080 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -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 a4d541d0101..0a2b6fb57c7 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -50,7 +50,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[] = []; @@ -154,18 +154,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 = createActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService) || null; } return actionItem; @@ -217,7 +217,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; @@ -253,11 +253,11 @@ 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); } @@ -312,14 +312,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; diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 450b6ad07ec..adb1d3b18d5 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -31,7 +31,7 @@ export class ClosePanelAction extends Action { run(): Promise { this.partService.setPanelHidden(true); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -50,7 +50,7 @@ export class TogglePanelAction extends Action { run(): Promise { this.partService.setPanelHidden(this.partService.isVisible(Parts.PANEL_PART)); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -73,7 +73,7 @@ class FocusPanelAction extends Action { // Show panel if (!this.partService.isVisible(Parts.PANEL_PART)) { this.partService.setPanelHidden(false); - return Promise.resolve(null); + return Promise.resolve(); } // Focus into active panel @@ -82,7 +82,7 @@ class FocusPanelAction extends Action { panel.focus(); } - return Promise.resolve(null); + return Promise.resolve(); } } @@ -120,7 +120,7 @@ export class TogglePanelPositionAction extends Action { const position = this.partService.getPanelPosition(); this.partService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM); - return Promise.resolve(null); + return Promise.resolve(); } dispose(): void { @@ -162,7 +162,7 @@ export class ToggleMaximizedPanelAction extends Action { } this.partService.toggleMaximizedPanel(); - return Promise.resolve(null); + return Promise.resolve(); } dispose(): void { @@ -184,7 +184,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 +202,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(); } } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index f7e420b76a7..d6bd16824cc 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -41,7 +41,7 @@ export const PanelFocusContext = new RawContextKey('panelFocus', false) interface ICachedPanel { id: string; pinned: boolean; - order: number; + order?: number; visible: boolean; } @@ -54,6 +54,18 @@ export class PanelPart extends CompositePart implements IPanelService, IS _serviceBrand: any; + element: HTMLElement; + + 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; + + private _onDidChange = this._register(new Emitter<{ width: number; height: number; }>()); + get onDidChange(): Event<{ width: number, height: number }> { return this._onDidChange.event; } + private activePanelContextKey: IContextKey; private panelFocusContextKey: IContextKey; private blockOpeningPanel: boolean; @@ -61,17 +73,6 @@ export class PanelPart extends CompositePart implements IPanelService, IS private compositeActions: { [compositeId: string]: { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null); 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, @@ -99,7 +100,7 @@ export class PanelPart extends CompositePart implements IPanelService, IS Registry.as(PanelExtensions.Panels).getDefaultPanelId(), 'panel', 'panel', - null, + undefined, id, { hasTitle: true } ); @@ -194,11 +195,11 @@ 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 })); + return Event.map(this.onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); } get onDidPanelClose(): Event { - return this._onDidCompositeClose.event; + return this.onDidCompositeClose.event; } updateStyles(): void { @@ -209,10 +210,12 @@ 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 } @@ -227,20 +230,20 @@ export class PanelPart extends CompositePart implements IPanelService, IS } } - 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 +260,7 @@ export class PanelPart extends CompositePart implements IPanelService, IS ]; } - getActivePanel(): IPanel { + getActivePanel(): IPanel | null { return this.getActiveComposite(); } @@ -301,9 +304,9 @@ export class PanelPart extends CompositePart implements IPanelService, IS if (this.partService.getPanelPosition() === Position.RIGHT) { // Take into account the 1px border when layouting - this.dimension = new Dimension(width - 1, height); + this.dimension = new Dimension(width - 1, height!); } else { - this.dimension = new Dimension(width, height); + this.dimension = new Dimension(width, height!); } const sizes = super.layout(this.dimension.width, this.dimension.height); @@ -412,7 +415,7 @@ export class PanelPart extends CompositePart implements IPanelService, IS return cachedPanels; } - private _cachedPanelsValue: string; + private _cachedPanelsValue: string | null; private get cachedPanelsValue(): string { if (!this._cachedPanelsValue) { this._cachedPanelsValue = this.getStoredCachedPanelsValue(); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index f0458b7bbff..2e9ef883959 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -1162,7 +1162,7 @@ export class QuickInputService extends Component implements IQuickInputService { } validation.then(result => { if (value === validationValue) { - input.validationMessage = result; + input.validationMessage = result || undefined; } }); }), @@ -1418,6 +1418,6 @@ export class BackAction extends Action { public run(): Promise { this.quickInputService.back(); - return Promise.resolve(null); + return Promise.resolve(); } } diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 9b4f8f54995..3eb74651206 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -78,12 +78,12 @@ export class QuickOpenController extends Component implements IQuickOpenService 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, @@ -165,7 +165,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); @@ -206,7 +206,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 @@ -270,7 +270,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 +300,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 +482,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, resolvedHandler.getGroupLabel() || undefined); } }); }); } - 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 +529,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() }), resolvedHandler.getAriaLabel() || undefined); return Promise.resolve(undefined); } @@ -547,9 +550,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() }), resolvedHandler.getAriaLabel() || undefined); } 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() }), resolvedHandler.getAriaLabel() || undefined); } } }); @@ -570,15 +573,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; } }); @@ -655,7 +659,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 +674,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 +694,7 @@ class EditorHistoryHandler { return false; } - e.setHighlights(itemScore.labelMatch, itemScore.descriptionMatch); + e.setHighlights(itemScore.labelMatch || [], itemScore.descriptionMatch); return true; }) @@ -707,8 +711,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 +725,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 +766,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { return this.dirty ? 'dirty' : ''; } - getLabel(): string { + getLabel(): string | null { return this.label; } @@ -776,12 +780,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 +810,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 +850,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() }; diff --git a/src/vs/workbench/browser/parts/quickopen/quickopen.ts b/src/vs/workbench/browser/parts/quickopen/quickopen.ts index 1c00cf57074..2a176b5726f 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickopen.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickopen.ts @@ -19,12 +19,24 @@ export const defaultQuickOpenContext = ContextKeyExpr.and(inQuickOpenContext, Co 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 88b4876943a..83e373830a0 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -41,23 +41,24 @@ export class SidebarPart extends CompositePart implements ISerializable static readonly activeViewletSettingsKey = 'workbench.sidebar.activeviewletid'; + element: HTMLElement; + + 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; + + private _onDidChange = this._register(new Emitter<{ width: number; height: number; }>()); + get onDidChange(): Event<{ width: number, height: number }> { return this._onDidChange.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, @@ -116,11 +117,11 @@ export class SidebarPart extends CompositePart implements ISerializable get onDidViewletDeregister(): Event { return this._onDidViewletDeregister.event; } get onDidViewletOpen(): Event { - return Event.map(this._onDidCompositeOpen.event, compositeEvent => compositeEvent.composite); + return Event.map(this.onDidCompositeOpen.event, compositeEvent => compositeEvent.composite); } get onDidViewletClose(): Event { - return this._onDidCompositeClose.event as Event; + return this.onDidCompositeClose.event as Event; } create(parent: HTMLElement): void { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 9fed8f11b46..c9df37291f6 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -27,11 +27,10 @@ import { Color } from 'vs/base/common/color'; import { addClass, EventHelper, createStyleSheet, addDisposableListener, Dimension } 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 { Event, Emitter } from 'vs/base/common/event'; import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; import { Parts } from 'vs/workbench/services/part/common/partService'; - export class StatusbarPart extends Part implements IStatusbarService, ISerializableView { _serviceBrand: any; @@ -39,17 +38,16 @@ export class StatusbarPart extends Part implements IStatusbarService, ISerializa private static readonly ALIGNMENT_PROP = 'statusbar-entry-alignment'; element: HTMLElement; + + readonly minimumWidth: number = 0; + readonly maximumWidth: number = Number.POSITIVE_INFINITY; + readonly minimumHeight: number = 22; + readonly maximumHeight: number = 22; + + private _onDidChange = this._register(new Emitter<{ width: number; height: number; }>()); + get onDidChange(): Event<{ width: number, height: number }> { return this._onDidChange.event; } + 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( diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 38794f793dd..7ea3b3d5ee2 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,12 +69,11 @@ 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%; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 4b3eb7bdb87..16482690836 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'; @@ -35,11 +35,10 @@ 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 { RunOnceScheduler } from 'vs/base/common/async'; export class TitlebarPart extends Part implements ITitleService, ISerializableView { - _serviceBrand: any; - 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]"); private static readonly NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]"); @@ -47,6 +46,17 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi private static readonly TITLE_SEPARATOR = isMacintosh ? ' — ' : ' - '; // macOS uses special - separator element: HTMLElement; + + 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)); } + + private _onDidChange = this._register(new Emitter<{ width: number; height: number; }>()); + get onDidChange(): Event<{ width: number, height: number }> { return this._onDidChange.event; } + + _serviceBrand: any; + private title: HTMLElement; private dragRegion: HTMLElement; private windowControls: HTMLElement; @@ -64,13 +74,7 @@ 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, @@ -98,10 +102,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 +120,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')) { @@ -160,13 +164,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 @@ -232,7 +236,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi this.properties.isAdmin = isAdmin; this.properties.isPure = isPure; - this.doUpdateTitle(); + this.titleUpdater.schedule(); } } @@ -258,7 +262,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) { @@ -343,7 +347,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 @@ -514,7 +518,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 +527,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)); } } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 960d8631a52..0a142dcfd16 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -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,6 @@ 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'; export class CustomTreeViewPanel extends ViewletPanel { @@ -87,8 +86,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 { @@ -508,7 +507,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 +582,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 +676,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 = 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 +757,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 { diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts new file mode 100644 index 00000000000..8db78ac9953 --- /dev/null +++ b/src/vs/workbench/browser/style.ts @@ -0,0 +1,137 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 3798dbde4fd..12f78547309 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -154,7 +154,7 @@ configurationRegistry.registerConfiguration({ 'workbench.settings.openDefaultKeybindings': { 'type': 'boolean', 'description': nls.localize('openDefaultKeybindings', "Controls whether opening keybinding settings also opens an editor showing all default keybindings."), - 'default': true + 'default': false }, 'workbench.sideBar.location': { 'type': 'string', diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index f805576e516..fa82000a8cb 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, diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index a9113661957..027f4d17d21 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -184,13 +184,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 { @@ -678,7 +678,7 @@ export class EditorOptions implements IEditorOptions { /** * Helper to create EditorOptions inline. */ - static create(settings: IEditorOptions): EditorOptions { + static create(settings: IEditorOptions): EditorOptions | null { const options = new EditorOptions(); options.preserveFocus = settings.preserveFocus; @@ -688,6 +688,7 @@ export class EditorOptions implements IEditorOptions { options.pinned = settings.pinned; options.index = settings.index; options.inactive = settings.inactive; + options.ignoreError = settings.ignoreError; return options; } @@ -731,6 +732,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; } /** @@ -796,6 +803,10 @@ export class TextEditorOptions extends EditorOptions { textEditorOptions.inactive = true; } + if (options.ignoreError) { + textEditorOptions.ignoreError = true; + } + if (typeof options.index === 'number') { textEditorOptions.index = options.index; } diff --git a/src/vs/workbench/common/editor/binaryEditorModel.ts b/src/vs/workbench/common/editor/binaryEditorModel.ts index 57f46840097..bb60951b8c1 100644 --- a/src/vs/workbench/common/editor/binaryEditorModel.ts +++ b/src/vs/workbench/common/editor/binaryEditorModel.ts @@ -16,7 +16,7 @@ export class BinaryEditorModel extends EditorModel { private name: string; private resource: URI; private size: number; - private etag: string; + private etag?: string; private mime: string; constructor( @@ -70,7 +70,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/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index af7ff8be8bb..c5f5e8bd159 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -678,7 +678,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 d61b126f22f..bc873064c44 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -19,14 +19,14 @@ export class ResourceEditorInput extends EditorInput { static readonly ID: string = 'workbench.editors.resourceEditorInput'; - private modelReference: Promise>; + private modelReference: Promise> | null; private resource: URI; private name: string; - private description: string; + private description: string | null; constructor( name: string, - description: string, + description: string | null, resource: URI, @ITextModelService private readonly textModelResolverService: ITextModelService, @IHashService private readonly hashService: IHashService @@ -57,7 +57,7 @@ export class ResourceEditorInput extends EditorInput { } } - getDescription(): string { + getDescription(): string | null { return this.description; } diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 85daece3136..e1800711ea6 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -7,8 +7,8 @@ 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'; @@ -72,22 +72,22 @@ 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 | null { @@ -126,7 +126,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return this.getName(); } - let title: string; + let title: string | undefined; switch (verbosity) { case Verbosity.SHORT: title = this.shortTitle; diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index 54817b0a6b4..70cfc8c48e0 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -38,7 +38,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin constructor( private modeId: string, private resource: URI, - private hasAssociatedFilePath: boolean, + private _hasAssociatedFilePath: boolean, private initialValue: string, private preferredEncoding: string, @IModeService modeService: IModeService, @@ -56,6 +56,10 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin 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 @@ -145,7 +149,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin 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) { @@ -182,7 +186,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin // 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.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') { this.setDirty(false); } diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index c726f2ce067..b8cf29b7272 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -4,11 +4,16 @@ *--------------------------------------------------------------------------------------------*/ 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'; export class ResourceContextKey extends Disposable implements IContextKey { @@ -58,9 +63,9 @@ export class ResourceContextKey extends Disposable implements IContextKey { 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._filenameKey.set(value && basename(value)); this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value.fsPath) : null); - this._extensionKey.set(value && paths.extname(value.fsPath)); + this._extensionKey.set(value && extname(value)); this._hasResource.set(!!value); this._isFileSystemResource.set(value && this._fileService.canHandleResource(value)); } @@ -95,3 +100,106 @@ 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 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 = 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/contrib/cli/electron-browser/cli.contribution.ts b/src/vs/workbench/contrib/cli/electron-browser/cli.contribution.ts index 677b621801e..b0ac6293594 100644 --- a/src/vs/workbench/contrib/cli/electron-browser/cli.contribution.ts +++ b/src/vs/workbench/contrib/cli/electron-browser/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'; diff --git a/src/vs/workbench/contrib/codeEditor/electron-browser/largeFileOptimizations.ts b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts similarity index 98% rename from src/vs/workbench/contrib/codeEditor/electron-browser/largeFileOptimizations.ts rename to src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts index b56e4cef309..17369ff0b6c 100644 --- a/src/vs/workbench/contrib/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/contrib/codeEditor/electron-browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts index 2f9faa53af6..4ca75ddb3b4 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts @@ -6,7 +6,7 @@ import '../browser/menuPreventer'; import './accessibility'; import './inspectKeybindings'; -import './largeFileOptimizations'; +import '../browser/largeFileOptimizations'; import './selectionClipboard'; import './sleepResumeRepaintMinimap'; import './textMate/inspectTMScopes'; 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 00000000000..cb37c59d8f9 --- /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 MIT License. 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 00000000000..bce7c3f7d4f --- /dev/null +++ b/src/vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution.ts @@ -0,0 +1,352 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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; + private _modelChangeCounter: number; + private _currentResolveCodeInsetSymbolsPromise: CancelablePromise; + 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 { + if (this._pendingWebviews.has(symbolId)) { + this._pendingWebviews.get(symbolId)(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, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): 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.getModel()) { + 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(void 0); + } + 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 00000000000..113a5a1fbb4 --- /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 MIT License. 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 00000000000..60637e38989 --- /dev/null +++ b/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts @@ -0,0 +1,176 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. 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): void { + while (this._decorationIds.length) { + helper.removeDecoration(this._decorationIds.pop()); + } + if (viewZoneChangeAccessor) { + viewZoneChangeAccessor.removeZone(this._viewZoneId); + this._viewZone = undefined; + } + if (this._webview) { + this._webview.dispose(); + } + } + + public isValid(): boolean { + return 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) { + helper.removeDecoration(this._decorationIds.pop()); + } + 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 { + 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 => { + 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]); + this._viewZone.afterLineNumber = range.endLineNumber; + viewZoneChangeAccessor.layoutZone(this._viewZoneId); + } + } +} diff --git a/src/vs/workbench/contrib/comments/electron-browser/commentNode.ts b/src/vs/workbench/contrib/comments/electron-browser/commentNode.ts index 6c61815bdb2..47862408321 100644 --- a/src/vs/workbench/contrib/comments/electron-browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentNode.ts @@ -32,6 +32,9 @@ 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'; const UPDATE_COMMENT_LABEL = nls.localize('label.updateComment', "Update comment"); const UPDATE_IN_PROGRESS_LABEL = nls.localize('label.updatingComment', "Updating comment..."); @@ -96,7 +99,7 @@ export class CommentNode extends Disposable { this._body.appendChild(this._md); if (this.comment.commentReactions && this.comment.commentReactions.length) { - this.createReactions(this._commentDetailsContainer); + this.createReactionsContainer(this._commentDetailsContainer); } this._domNode.setAttribute('aria-label', `${comment.userName}, ${comment.body.value}`); @@ -120,6 +123,13 @@ export class CommentNode extends Disposable { } const actions: Action[] = []; + + let reactionGroup = this.commentService.getReactionGroup(this.owner); + if (reactionGroup && reactionGroup.length) { + let toggleReactionAction = this.createReactionPicker(); + actions.push(toggleReactionAction); + } + if (this.comment.canEdit) { this._editAction = this.createEditAction(commentDetailsContainer); actions.push(this._editAction); @@ -134,53 +144,120 @@ export class CommentNode extends Disposable { const actionsContainer = dom.append(header, dom.$('.comment-actions.hidden')); this.toolbar = new ToolBar(actionsContainer, this.contextMenuService, { - actionItemProvider: action => this.actionItemProvider(action as Action), + 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(actionsContainer); - - let reactionActions: Action[] = []; - 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.toolbar.setActions(actions, [])(); this._toDispose.push(this.toolbar); } } actionItemProvider(action: Action) { let options = {}; - if (action.id === 'comment.delete' || action.id === 'comment.edit') { + if (action.id === 'comment.delete' || action.id === 'comment.edit' || action.id === ToggleReactionsAction.ID) { options = { label: false, icon: true }; } else { options = { label: true, icon: true }; } - let item = new ActionItem({}, action, options); - return item; + if (action.id === ReactionAction.ID) { + let item = new ReactionActionItem(action); + return item; + } else { + let item = new ActionItem({}, action, options); + return item; + } } - private createReactions(commentDetailsContainer: HTMLElement): void { + 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._actionsContainer = dom.append(commentDetailsContainer, dom.$('div.comment-reactions')); - this._reactionsActionBar = new ActionBar(this._actionsContainer, {}); + this._reactionsActionBar = new ActionBar(this._actionsContainer, { + 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); - let reactionActions = this.comment.commentReactions!.map(reaction => { - return new Action(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted ? 'active' : '', true, async () => { + this.comment.commentReactions!.map(reaction => { + let action = new ReactionAction(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted && reaction.canEdit ? 'active' : '', reaction.canEdit, async () => { try { if (reaction.hasReacted) { await this.commentService.deleteReaction(this.owner, this.resource, this.comment, reaction); @@ -201,10 +278,16 @@ export class CommentNode extends Disposable { } this.notificationService.error(error); } - }); + }, reaction.iconPath, reaction.count); + + this._reactionsActionBar.push(action, { label: true, icon: true }); }); - reactionActions.forEach(action => this._reactionsActionBar!.push(action, { label: true, icon: true })); + let reactionGroup = this.commentService.getReactionGroup(this.owner); + if (reactionGroup && reactionGroup.length) { + let toggleReactionAction = this.createReactionPicker(); + this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); + } } private createCommentEditor(): void { @@ -383,7 +466,7 @@ export class CommentNode extends Disposable { } if (this.comment.commentReactions && this.comment.commentReactions.length) { - this.createReactions(this._commentDetailsContainer); + this.createReactionsContainer(this._commentDetailsContainer); } } diff --git a/src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts index c288cd08cc9..e7fbb504a5e 100644 --- a/src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts @@ -293,6 +293,10 @@ export class ReviewZoneWidget extends ZoneWidget { } } + 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*/ }); } @@ -354,6 +358,7 @@ export class ReviewZoneWidget extends ZoneWidget { } } else { this.dispose(); + return; } } diff --git a/src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts index 467d81e927d..e53dd62b1e7 100644 --- a/src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts @@ -748,13 +748,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: none;}`); } }); diff --git a/src/vs/workbench/contrib/comments/electron-browser/media/panel.css b/src/vs/workbench/contrib/comments/electron-browser/media/panel.css index 43f2d98ff6b..1633bd41c7e 100644 --- a/src/vs/workbench/contrib/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 00000000000..b51dfbbd9b8 --- /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 00000000000..2c7fa126dd9 --- /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 00000000000..8696b8f2bab --- /dev/null +++ b/src/vs/workbench/contrib/comments/electron-browser/media/reaction.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/contrib/comments/electron-browser/media/review.css b/src/vs/workbench/contrib/comments/electron-browser/media/review.css index 1339e933862..9694788d3c7 100644 --- a/src/vs/workbench/contrib/comments/electron-browser/media/review.css +++ b/src/vs/workbench/contrib/comments/electron-browser/media/review.css @@ -115,10 +115,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 +144,74 @@ } .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; +} + +.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.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 00000000000..0b8bc2e8bf9 --- /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 MIT License. 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/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index bf9c754e095..56d7c8c70ba 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -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)); @@ -267,7 +267,7 @@ export class StepOverAction extends AbstractDebugAction { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.next() : Promise.resolve(null); + return thread ? thread.next() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -288,7 +288,7 @@ export class StepIntoAction extends AbstractDebugAction { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.stepIn() : Promise.resolve(null); + return thread ? thread.stepIn() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -309,7 +309,7 @@ export class StepOutAction extends AbstractDebugAction { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.stepOut() : Promise.resolve(null); + return thread ? thread.stepOut() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -369,7 +369,7 @@ export class ContinueAction extends AbstractDebugAction { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.continue() : Promise.resolve(null); + return thread ? thread.continue() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -395,7 +395,7 @@ export class PauseAction extends AbstractDebugAction { } } - return thread ? thread.pause() : Promise.resolve(null); + return thread ? thread.pause() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -416,7 +416,7 @@ export class TerminateThreadAction extends AbstractDebugAction { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.terminate() : Promise.resolve(null); + return thread ? thread.terminate() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -567,7 +567,7 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction { public run(): Promise { this.debugService.addFunctionBreakpoint(); - return Promise.resolve(null); + return Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -589,12 +589,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); } } @@ -628,7 +628,7 @@ export class EditWatchExpressionAction extends AbstractDebugAction { public run(expression: Expression): Promise { this.debugService.getViewModel().setSelectedExpression(expression); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -661,7 +661,7 @@ export class RemoveWatchExpressionAction extends AbstractDebugAction { public run(expression: Expression): Promise { this.debugService.removeWatchExpressions(expression.getId()); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -676,7 +676,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { public run(): Promise { this.debugService.removeWatchExpressions(); - return Promise.resolve(null); + return Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -727,7 +727,7 @@ export class FocusReplAction extends Action { public run(): Promise { this.panelService.openPanel(REPL_ID, true); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -769,13 +769,13 @@ export class StepBackAction extends AbstractDebugAction { 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); } } @@ -792,13 +792,13 @@ export class ReverseContinueAction extends AbstractDebugAction { 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); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 25a792b2ebf..4a4660744e7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -49,7 +49,7 @@ class ToggleBreakpointAction extends EditorAction { return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber }], 'debugEditorActions.toggleBreakpointAction'); } - return Promise.resolve(null); + return Promise.resolve(); } } @@ -217,7 +217,7 @@ class ShowDebugHoverAction extends EditorAction { const position = editor.getPosition(); 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); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts index 9e0908183b9..20b921632d2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts @@ -233,7 +233,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { private updateBreakpoints(modelData: IDebugEditorModelData, newBreakpoints: IBreakpoint[]): void { const desiredDecorations = this.createBreakpointDecorations(modelData.model, newBreakpoints); - let breakpointDecorationIds: string[]; + let breakpointDecorationIds: string[] | undefined; try { this.ignoreDecorationsChangedEvent = true; breakpointDecorationIds = modelData.model.deltaDecorations(modelData.breakpointDecorations.map(bpd => bpd.decorationId), desiredDecorations); @@ -267,7 +267,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) { diff --git a/src/vs/workbench/contrib/debug/browser/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts index 49bb7903aca..1b6f41c628f 100644 --- a/src/vs/workbench/contrib/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/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 8a85289d1d4..0eb47853e0f 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -6,7 +6,7 @@ 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'; @@ -154,7 +154,7 @@ 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; @@ -317,17 +317,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; } } else { // on unix try to tildify absolute paths - path = normalize(path, true); + path = normalize(path); if (!isWindows) { path = tildify(path, this._environmentService.userHome); } diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 7a5e0f3cfa2..516d47268bf 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -139,15 +139,15 @@ .monaco-workbench .monaco-list-row .expression { overflow: hidden; text-overflow: ellipsis; - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } -.mac > .monaco-workbench .monaco-list-row .expression { +.monaco-workbench.mac .monaco-list-row .expression { font-size: 11px; } -.windows > .monaco-workbench .monaco-list-row .expression, -.linux > .monaco-workbench .monaco-list-row .expression { +.monaco-workbench.windows .monaco-list-row .expression, +.monaco-workbench.linux .monaco-list-row .expression { font-size: 13px; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 9d27545f44b..8eea2828a7d 100644 --- a/src/vs/workbench/contrib/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/contrib/debug/browser/media/exceptionWidget.css b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css index 5ddc57325e8..14e674738d5 100644 --- a/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css @@ -19,7 +19,7 @@ .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 { @@ -31,13 +31,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/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 9662f5852ee..afb6ca59a78 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -12,7 +12,7 @@ } .repl .surveyor { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); position: absolute; display: inline-block; width : auto; @@ -36,13 +36,13 @@ 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 { +.monaco-workbench.mac .repl .repl-tree .input.expression, +.monaco-workbench.mac .repl .repl-tree .output.expression { font-size: 12px; } @@ -65,10 +65,10 @@ 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 { +.monaco-workbench.windows .repl .repl-tree .monaco-list-row .input.expression, +.monaco-workbench.windows .repl .repl-tree .monaco-list-row .output.expression, +.monaco-workbench.linux .repl .repl-tree .monaco-list-row .input.expression, +.monaco-workbench.linux .repl .repl-tree .monaco-list-row .output.expression { font-size: 14px; } @@ -106,7 +106,7 @@ line-height: 18px; } -.linux > .monaco-workbench .repl .repl-input-wrapper:before { +.monaco-workbench.linux .repl .repl-input-wrapper:before { font-size: 9px; } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 7a34a6e28f2..d1d5e2ba2fe 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -244,7 +244,7 @@ export interface IThread extends ITreeElement { /** * Information about the exception if an 'exception' stopped event raised and DA supports the 'exceptionInfo' request, otherwise null. */ - readonly exceptionInfo: Promise; + readonly exceptionInfo: Promise; /** * Gets the callstack if it has already been received from the debug @@ -321,7 +321,7 @@ export interface IBaseBreakpoint extends IEnablement { readonly hitCondition: string; readonly logMessage: string; readonly verified: boolean; - readonly idFromAdapter: number; + readonly idFromAdapter: number | undefined; } export interface IBreakpoint extends IBaseBreakpoint { @@ -330,7 +330,7 @@ export interface IBreakpoint extends IBaseBreakpoint { readonly endLineNumber?: number; readonly column: number; readonly endColumn?: number; - readonly message: string; + readonly message?: string; readonly adapterData: any; } @@ -346,7 +346,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; } @@ -394,7 +394,7 @@ export interface IDebugModel extends ITreeElement { onDidChangeBreakpoints: Event; onDidChangeCallStack: Event; - onDidChangeWatchExpressions: Event; + onDidChangeWatchExpressions: Event; } /** @@ -586,7 +586,7 @@ export interface IConfigurationManager { getLaunches(): ReadonlyArray; - getLaunch(workspaceUri: uri): ILaunch | undefined; + getLaunch(workspaceUri: uri | undefined): ILaunch | undefined; /** * Allows to register on change of selected debug configuration. @@ -784,7 +784,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): Promise; /** * Restarts a session or creates a new one if there is no active session. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 35172eda586..9f2dfc88716 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -20,7 +20,7 @@ import { } 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'; @@ -95,7 +95,7 @@ export class ExpressionContainer implements IExpressionContainer { public valueChanged: boolean; private _value: string; - protected children: Promise; + protected children?: Promise; constructor( protected session: IDebugSession, @@ -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); } @@ -259,7 +259,7 @@ export class Variable extends ExpressionContainer implements IExpression { namedVariables: number, indexedVariables: number, public presentationHint: DebugProtocol.VariablePresentationHint, - public type: string | null = null, + public type: string | undefined = undefined, public available = true, startOfVariables = 0 ) { @@ -308,7 +308,7 @@ export class Scope extends ExpressionContainer implements IScope { export class StackFrame implements IStackFrame { - private scopes: Promise; + private scopes: Promise | null; constructor( public thread: IThread, @@ -354,7 +354,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 +367,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; }); } @@ -564,7 +564,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; } @@ -617,7 +617,7 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { 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 +634,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; } @@ -743,9 +743,9 @@ export class DebugModel implements IDebugModel { private toDispose: lifecycle.IDisposable[]; private schedulers = new Map(); private breakpointsSessionId: string; - private readonly _onDidChangeBreakpoints: Emitter; + private readonly _onDidChangeBreakpoints: Emitter; private readonly _onDidChangeCallStack: Emitter; - private readonly _onDidChangeWatchExpressions: Emitter; + private readonly _onDidChangeWatchExpressions: Emitter; constructor( private breakpoints: Breakpoint[], @@ -797,7 +797,7 @@ export class DebugModel implements IDebugModel { return this._onDidChangeCallStack.event; } - get onDidChangeWatchExpressions(): Event { + get onDidChangeWatchExpressions(): Event { return this._onDidChangeWatchExpressions.event; } @@ -830,7 +830,7 @@ export class DebugModel implements IDebugModel { }, 420)); } - this.schedulers.get(thread.getId()).schedule(); + this.schedulers.get(thread.getId())!.schedule(); this._onDidChangeCallStack.fire(); }); } @@ -879,7 +879,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); } diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index 34b2e1ed4b3..a6a1215b778 100644 --- a/src/vs/workbench/contrib/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/contrib/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts index 0cfc1e8b6b3..8efc84301ea 100644 --- a/src/vs/workbench/contrib/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -5,7 +5,7 @@ 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/contrib/debug/common/debug'; import { IRange } from 'vs/editor/common/core/range'; @@ -52,7 +52,7 @@ export class Source { 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 @@ -104,7 +104,7 @@ export class Source { 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/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 7bd7849ceb1..c067fc8618a 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -6,7 +6,7 @@ import { equalsIgnoreCase } from 'vs/base/common/strings'; 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; @@ -90,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 diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index d78e15f9884..c4556ec3eca 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -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/contrib/debug/electron-browser/callStackView.ts b/src/vs/workbench/contrib/debug/electron-browser/callStackView.ts index 0d300602afa..2810e1deb2b 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/callStackView.ts @@ -540,7 +540,7 @@ 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); diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts index 4fda9f76074..6d6fefb3d0a 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts @@ -337,7 +337,7 @@ export class ConfigurationManager implements IConfigurationManager { return this.launches; } - public getLaunch(workspaceUri: uri): ILaunch { + public getLaunch(workspaceUri: uri | undefined): ILaunch { if (!uri.isUri(workspaceUri)) { return undefined; } @@ -412,7 +412,7 @@ export class ConfigurationManager implements IConfigurationManager { } 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; diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/electron-browser/debugEditorContribution.ts index caeb140c6ae..c03c95bef16 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugEditorContribution.ts @@ -298,7 +298,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { } }); } else { - let overrides: IConfigurationOverrides; + let overrides: IConfigurationOverrides | undefined; if (model) { overrides = { resource: model.uri, @@ -537,7 +537,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { "debug/addLaunchConfiguration" : {} */ this.telemetryService.publicLog('debug/addLaunchConfiguration'); - let configurationsArrayPosition: Position; + let configurationsArrayPosition: Position | undefined; const model = this.editor.getModel(); let depthInArray = 0; let lastProperty: string; diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugService.ts b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts index 4828acaa6eb..38de53bd470 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts @@ -34,7 +34,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur 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/contrib/tasks/common/tasks'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -255,7 +255,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, unresolvedConfig?: IConfig, ): Promise { this.startInitializingState(); // make sure to save all files and that the configuration is up to date @@ -263,7 +263,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; } @@ -296,7 +297,7 @@ export class DebugService implements IDebugService { 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) { @@ -346,7 +347,7 @@ export class DebugService implements IDebugService { private createSession(launch: ILaunch, config: IConfig, unresolvedConfig: IConfig, 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 { @@ -581,7 +582,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); @@ -934,7 +935,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 +946,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 +957,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 +968,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); diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts b/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts index 6410fd8e966..5757eb26e15 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts @@ -436,7 +436,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; diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index 09939841def..aa7e83a4b7f 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -8,7 +8,8 @@ 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 extpath from 'vs/base/common/extpath'; import * as strings from 'vs/base/common/strings'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; @@ -316,7 +317,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { // verify executables if (this.adapterExecutable.command) { - if (paths.isAbsolute(this.adapterExecutable.command)) { + if (path.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))); } @@ -440,7 +441,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { const result: IDebuggerContribution = Object.create(null); if (contribution.runtime) { if (contribution.runtime.indexOf('./') === 0) { // TODO - result.runtime = paths.join(extensionFolderPath, contribution.runtime); + result.runtime = extpath.joinWithSlashes(extensionFolderPath, contribution.runtime); } else { result.runtime = contribution.runtime; } @@ -449,8 +450,8 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { result.runtimeArgs = contribution.runtimeArgs; } if (contribution.program) { - if (!paths.isAbsolute(contribution.program)) { - result.program = paths.join(extensionFolderPath, contribution.program); + if (!path.isAbsolute(contribution.program)) { + result.program = extpath.joinWithSlashes(extensionFolderPath, contribution.program); } else { result.program = contribution.program; } diff --git a/src/vs/workbench/contrib/debug/node/debugger.ts b/src/vs/workbench/contrib/debug/node/debugger.ts index 93e37f35d61..e97d34b077a 100644 --- a/src/vs/workbench/contrib/debug/node/debugger.ts +++ b/src/vs/workbench/contrib/debug/node/debugger.ts @@ -46,13 +46,13 @@ export class Debugger implements IDebugger { public merge(otherDebuggerContribution: IDebuggerContribution, extensionDescription: IExtensionDescription): void { - // remember all extensions that are merged for this debugger + // remember all extensions that have been merged for this debugger this.mergedExtensionDescriptions.push(extensionDescription); - // merge debugger contributions + // merge new debugger contribution into existing contributions. objects.mixin(this.debuggerContribution, otherDebuggerContribution, extensionDescription.isBuiltin); - // remember the extension that has the "main" debugger contribution + // remember the extension that is considered the "main" debugger contribution if (isDebuggerMainContribution(otherDebuggerContribution)) { this.mainExtensionDescription = extensionDescription; } @@ -146,9 +146,9 @@ export class Debugger implements IDebugger { private inExtHost(): boolean { const debugConfigs = this.configurationService.getValue('debug'); - return debugConfigs.extensionHostDebugAdapter + return !!debugConfigs.extensionHostDebugAdapter || this.configurationManager.needsToRunInExtHost(this.type) - || (this.mainExtensionDescription && this.mainExtensionDescription.extensionLocation.scheme !== 'file'); + || (!!this.mainExtensionDescription && this.mainExtensionDescription.extensionLocation.scheme !== 'file'); } get label(): string { diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 31c0332eb54..343fddd46b8 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -305,6 +305,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 @@ -391,17 +393,21 @@ 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)} ; `; + command += `cd ${quote(args.cwd)} && `; } if (args.env) { command += 'env'; 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/contrib/debug/test/browser/baseDebugView.test.ts b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts index d4549a65671..149b78269a8 100644 --- a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts @@ -55,10 +55,10 @@ suite('Debug - Base Debug View', () => { test('render variable', () => { const session = new MockSession(); const thread = new Thread(session, 'mockthread', 1); - const stackFrame = new StackFrame(thread, 1, null, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: undefined, endColumn: undefined }, 0); + const stackFrame = new StackFrame(thread, 1, null!, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: undefined!, endColumn: undefined! }, 0); const scope = new Scope(stackFrame, 1, 'local', 1, false, 10, 10); - let variable = new Variable(session, scope, 2, 'foo', 'bar.foo', undefined, 0, 0, {}, 'string'); + let variable = new Variable(session, scope, 2, 'foo', 'bar.foo', undefined!, 0, 0, {}, 'string'); let expression = $('.'); let name = $('.'); let value = $('.'); diff --git a/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts index c164d37d9db..5f554b70daf 100644 --- a/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts @@ -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/contrib/debug/test/common/debugSource.test.ts b/src/vs/workbench/contrib/debug/test/common/debugSource.test.ts index 4fcfdc46c00..e30be1612d7 100644 --- a/src/vs/workbench/contrib/debug/test/common/debugSource.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugSource.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI as uri } from 'vs/base/common/uri'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; -import { normalize } from 'vs/base/common/paths'; +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/contrib/debug/test/node/debugger.test.ts b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts index 38c87d51175..08ec6867a17 100644 --- a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts +++ b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -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 { IDebugAdapterExecutable, IConfigurationManager, IConfig, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { Debugger } from 'vs/workbench/contrib/debug/node/debugger'; @@ -144,7 +144,7 @@ suite('Debug - Debugger', () => { const ae = ExecutableDebugAdapter.platformAdapterExecutable([extensionDescriptor0], 'mock'); - assert.equal(ae!.command, paths.join(extensionFolderPath, debuggerContribution.program)); + assert.equal(ae!.command, extpath.joinWithSlashes(extensionFolderPath, debuggerContribution.program)); assert.deepEqual(ae!.args, debuggerContribution.args); }); diff --git a/src/vs/workbench/contrib/execution/electron-browser/execution.contribution.ts b/src/vs/workbench/contrib/execution/electron-browser/execution.contribution.ts index f23572852da..b7993790f8c 100644 --- a/src/vs/workbench/contrib/execution/electron-browser/execution.contribution.ts +++ b/src/vs/workbench/contrib/execution/electron-browser/execution.contribution.ts @@ -8,7 +8,7 @@ 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/contrib/execution/common/execution'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; diff --git a/src/vs/workbench/contrib/execution/electron-browser/terminalService.ts b/src/vs/workbench/contrib/execution/electron-browser/terminalService.ts index 498a5f9b578..0462e10ec86 100644 --- a/src/vs/workbench/contrib/execution/electron-browser/terminalService.ts +++ b/src/vs/workbench/contrib/execution/electron-browser/terminalService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 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'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts index 0e03b0890ce..753676567a1 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts @@ -28,7 +28,7 @@ import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension 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/contrib/extensions/electron-browser/extensionsActions'; +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'; @@ -52,6 +52,7 @@ 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'; function renderBody(body: string): string { const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-core-resource://'); @@ -125,7 +126,7 @@ class NavBar { _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); } @@ -199,7 +200,9 @@ export class ExtensionEditor extends BaseEditor { @IPartService private readonly partService: IPartService, @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,136 +281,139 @@ 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'; - } - - 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, - 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'; + } + + 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, + 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 { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index 36b76f71fcd..ed8e3872d2e 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -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/contrib/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts index 32adbff953d..326a4a84450 100644 --- a/src/vs/workbench/contrib/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 * as extpath from 'vs/base/common/extpath'; import { forEach } from 'vs/base/common/collections'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; @@ -45,6 +45,7 @@ import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/wo import { CancellationToken } from 'vs/base/common/cancellation'; import { getKeywordsForExtension } from 'vs/workbench/contrib/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"); @@ -333,7 +334,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 { - const extensionsJsonUri = workspaceFolder.toResource(paths.join('.vscode', 'extensions.json')); + const extensionsJsonUri = workspaceFolder.toResource(extpath.joinWithSlashes('.vscode', 'extensions.json')); return Promise.resolve(this.fileService.resolveFile(extensionsJsonUri) .then(() => this.fileService.resolveContent(extensionsJsonUri)) @@ -586,7 +587,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; @@ -903,8 +904,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, extpath.joinWithSlashes('/usr/local/bin', exeName))); + promises.push(findExecutable(exeName, extpath.joinWithSlashes(homeDir, exeName))); } }); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index ee26c75549d..2ffcb335cb3 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -6,9 +6,9 @@ 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 * as extpath from 'vs/base/common/extpath'; 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'; @@ -29,7 +29,7 @@ import { IWindowService, IWindowsService } from 'vs/platform/windows/common/wind import { IExtensionService, IExtensionDescription } 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'; @@ -49,12 +49,13 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import product from 'vs/platform/node/product'; -import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +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 { 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'; function toExtensionDescription(local: ILocalExtension): IExtensionDescription { return { @@ -145,7 +146,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(); @@ -172,24 +175,63 @@ 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 => { + 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; } } @@ -318,7 +360,7 @@ export class CombinedInstallAction extends ExtensionAction { return this.uninstallAction.run(); } - return Promise.resolve(null); + return Promise.resolve(); } dispose(): void { @@ -450,7 +492,7 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { if (this._actionItem) { this._actionItem.showMenu(actionGroups, disposeActionsOnHide); } - return Promise.resolve(null); + return Promise.resolve(); } dispose(): void { @@ -506,7 +548,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); @@ -516,8 +559,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) @@ -535,8 +592,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 { @@ -630,7 +690,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(); } } @@ -775,7 +835,7 @@ export abstract class ExtensionEditorDropDownAction extends ExtensionDropDownAct } else { return super.run({ actionGroups: [this.actions], disposeActionsOnHide: false }); } - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1063,6 +1123,150 @@ export class ReloadAction extends ExtensionAction { } } +export class SetColorThemeAction extends ExtensionAction { + + static getColorThemes(colorThemes: IColorTheme[], extension: IExtension): IColorTheme[] { + return colorThemes.filter(c => 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; @@ -1390,7 +1594,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 { @@ -1420,7 +1624,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 { @@ -1840,7 +2044,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(extpath.joinWithSlashes('.vscode', 'extensions.json'))); case WorkbenchState.WORKSPACE: return this.openWorkspaceConfigurationFile(this.contextService.getWorkspace().configuration!); } @@ -1885,7 +2089,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(extpath.joinWithSlashes('.vscode', 'extensions.json'))); } return null; }); @@ -1938,7 +2142,7 @@ export class AddToWorkspaceFolderRecommendationsAction extends AbstractConfigure if (!workspaceFolder) { return Promise.resolve(); } - const configurationFile = workspaceFolder.toResource(paths.join('.vscode', 'extensions.json')); + const configurationFile = workspaceFolder.toResource(extpath.joinWithSlashes('.vscode', 'extensions.json')); return this.getWorkspaceFolderExtensionsConfigContent(configurationFile).then(content => { const extensionIdLowerCase = extensionId.id.toLowerCase(); if (shouldRecommend) { @@ -2144,7 +2348,7 @@ export class StatusLabelAction extends Action implements IExtensionContainer { } run(): Promise { - return Promise.resolve(null); + return Promise.resolve(); } } @@ -2169,7 +2373,7 @@ export class MaliciousStatusLabelAction extends ExtensionAction { } run(): Promise { - return Promise.resolve(null); + return Promise.resolve(); } } @@ -2316,14 +2520,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 => { + return Promise.resolve(this.fileService.resolveFile(extensionsHome)).then(file => { let itemToShow: string; if (file.children && file.children.length > 0) { itemToShow = file.children[0].resource.fsPath; } else { - itemToShow = paths.normalize(extensionsHome, true); + itemToShow = extensionsHome.fsPath; } return this.windowsService.showItemInFolder(itemToShow); @@ -2356,7 +2560,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 => this.extensionsWorkbenchService.install(vsix))) diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts index 9e1fd882810..cd9c936d72e 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts @@ -11,7 +11,7 @@ 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/contrib/extensions/electron-browser/runtimeExtensionsEditor'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts index 849f8161da7..4d6d70c410e 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts @@ -43,6 +43,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { getKeywordsForExtension } from 'vs/workbench/contrib/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'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -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); } @@ -175,24 +177,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 +222,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 +232,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 +242,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 +272,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 +286,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 +301,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 +316,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)); } @@ -385,7 +389,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; @@ -426,11 +430,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)); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts index 8cf034ebdb5..7e81d54db35 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts @@ -136,7 +136,7 @@ export class RatingsWidget extends ExtensionWidget { } } this.container.title = this.extension.ratingCount === 1 ? localize('ratedBySingleUser', "Rated by 1 user") - : this.extension.ratingCount > 1 ? localize('ratedByUsers', "Rated by {0} users", this.extension.ratingCount) : localize('noRating', "No rating"); + : typeof this.extension.ratingCount === 'number' && this.extension.ratingCount > 1 ? localize('ratedByUsers', "Rated by {0} users", this.extension.ratingCount) : localize('noRating', "No rating"); } } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css index d2bea0aecb4..3bf716270cc 100644 --- a/src/vs/workbench/contrib/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/contrib/extensions/electron-browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css index a5446fb5d46..88cd76efe63 100644 --- a/src/vs/workbench/contrib/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/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index b1c5250d2a3..c192a29b6fd 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -40,7 +40,7 @@ import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/cont 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'; @@ -62,12 +62,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 +90,7 @@ interface IRuntimeExtension { description: IExtensionDescription; marketplaceInfo: IExtension; status: IExtensionsStatus; - profileInfo: IExtensionProfileInformation; + profileInfo?: IExtensionProfileInformation; unresponsiveProfile?: IExtensionHostProfile; } @@ -214,7 +214,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) }; } @@ -402,7 +402,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) { @@ -501,7 +501,7 @@ export class ReportExtensionIssueAction extends Action { 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/`; @@ -579,7 +579,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', @@ -601,7 +601,7 @@ export class StartExtensionHostProfileAction extends Action { run(): Promise { this._extensionHostProfileService.startProfiling(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -618,7 +618,7 @@ export class StopExtensionHostProfileAction extends Action { run(): Promise { this._extensionHostProfileService.stopProfiling(); - return Promise.resolve(null); + return Promise.resolve(); } } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 00a577f03d5..711e7e8a624 100644 --- a/src/vs/workbench/contrib/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'; diff --git a/src/vs/workbench/contrib/feedback/electron-browser/feedback.ts b/src/vs/workbench/contrib/feedback/electron-browser/feedback.ts index 5d3f0f1f67b..11051ef1722 100644 --- a/src/vs/workbench/contrib/feedback/electron-browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/electron-browser/feedback.ts @@ -11,7 +11,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import product from 'vs/platform/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'; diff --git a/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css b/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css index 041ae4a8776..259665cc894 100644 --- a/src/vs/workbench/contrib/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/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 6d840584a3b..8d1c0fbc798 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -6,7 +6,8 @@ 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/contrib/files/common/files'; import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -181,7 +182,7 @@ export class TextFileEditor extends BaseTextEditor { } // 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, () => { diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 73428f84eda..6645b5ab3ab 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -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/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 5d141dac753..3b7916fe08f 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -5,8 +5,8 @@ import { localize } from 'vs/nls'; import { memoize } from 'vs/base/common/decorators'; -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 { URI } from 'vs/base/common/uri'; import { EncodingMode, ConfirmResult, EditorInput, IFileEditorInput, ITextEditorModel, Verbosity, IRevertOptions } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; @@ -122,7 +122,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 +130,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 +182,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { case Verbosity.SHORT: title = this.shortTitle; break; + default: case Verbosity.MEDIUM: title = this.mediumTitle; break; diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 0bbd5075ca8..4ddf7073459 100644 --- a/src/vs/workbench/contrib/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/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index a007c4d655b..fd9d942093e 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -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,7 +47,7 @@ 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; findClosest(resource: URI): ExplorerItem | null; diff --git a/src/vs/workbench/contrib/files/electron-browser/explorerService.ts b/src/vs/workbench/contrib/files/electron-browser/explorerService.ts index a4e4689adb7..800b5175456 100644 --- a/src/vs/workbench/contrib/files/electron-browser/explorerService.ts +++ b/src/vs/workbench/contrib/files/electron-browser/explorerService.ts @@ -12,7 +12,7 @@ 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'; @@ -217,7 +217,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 +227,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); @@ -281,9 +281,6 @@ export class ExplorerService implements IExplorerService { // Find parent const parent = dirname(change.resource); - if (!parent) { - continue; - } // Continue if parent was already determined as to be ignored if (ignoredPaths[parent.toString()]) { diff --git a/src/vs/workbench/contrib/files/electron-browser/fileActions.ts b/src/vs/workbench/contrib/files/electron-browser/fileActions.ts index 1d61e4c585a..56aa8b7bda5 100644 --- a/src/vs/workbench/contrib/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/electron-browser/fileActions.ts @@ -7,7 +7,7 @@ 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'; @@ -17,7 +17,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; 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 { toResource, IUntitledResourceInput, ITextEditor } from 'vs/workbench/common/editor'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/electron-browser/explorerViewlet'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; @@ -40,13 +40,11 @@ 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/contrib/files/common/explorerModel'; import { onUnexpectedError } from 'vs/base/common/errors'; import { sequence } from 'vs/base/common/async'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -118,7 +116,7 @@ export class NewFileAction extends BaseErrorReportingAction { 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]; } @@ -174,7 +172,7 @@ export class NewFolderAction extends BaseErrorReportingAction { 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]; } @@ -246,7 +244,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); } @@ -352,7 +350,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?"); @@ -470,16 +468,16 @@ 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); @@ -505,7 +503,7 @@ export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste break; } - name = incrementFileName(name, fileToPaste.isDirectory); + name = incrementFileName(name, !!fileToPaste.isDirectory); candidate = resources.joinPath(targetFolder.resource, name); } @@ -709,7 +707,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; }); } @@ -869,24 +867,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 }); - if (fileResource && (fileResource.scheme === Schemas.file || fileResource.scheme === REMOTE_HOST_SCHEME)) { - this.windowService.openWindow([{ uri: fileResource, typeHint: 'file' }], { forceNewWindow: true, forceOpenWorkspaceAsFile: true }); + if (fileResource) { + 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); @@ -912,7 +915,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)); } @@ -974,7 +977,7 @@ export class CompareWithClipboardAction extends Action { } public run(): Promise { - const resource: URI = toResource(this.editorService.activeEditor, { supportSideBySide: true }); + const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true }); if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { if (!this.registrationDisposal) { const provider = this.instantiationService.createInstance(ClipboardContentProvider); @@ -1014,7 +1017,7 @@ class ClipboardContentProvider implements ITextModelContentProvider { } interface IExplorerContext { - stat: ExplorerItem; + stat?: ExplorerItem; selection: ExplorerItem[]; } @@ -1026,7 +1029,7 @@ 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. @@ -1036,7 +1039,7 @@ function openExplorerAndRunAction(accessor: ServicesAccessor, constructor: ICons 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); } diff --git a/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts b/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts index 77564393606..9a9e757e255 100644 --- a/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as paths from 'vs/base/common/paths'; import { URI } from 'vs/base/common/uri'; import { toResource, IEditorCommandsContext } from 'vs/workbench/common/editor'; import { IWindowsService, IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; @@ -39,6 +38,7 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/ 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'; // Commands @@ -245,7 +245,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))); }); } @@ -300,7 +300,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); @@ -357,9 +357,9 @@ CommandsRegistry.registerCommand({ 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.fsPath))); } else if (workspaceContextService.getWorkspace().folders.length) { - windowsService.showItemInFolder(paths.normalize(workspaceContextService.getWorkspace().folders[0].uri.fsPath, true)); + windowsService.showItemInFolder(workspaceContextService.getWorkspace().folders[0].uri.fsPath); } else { notificationService.info(nls.localize('openFileToReveal', "Open a file first to reveal")); } diff --git a/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts index be0be8bf599..222927311e1 100644 --- a/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-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'; @@ -33,7 +34,6 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; 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 { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExplorerService } from 'vs/workbench/contrib/files/electron-browser/explorerService'; @@ -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/contrib/files/electron-browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/electron-browser/media/explorerviewlet.css index e993c16c5ff..a0c0f8de7f4 100644 --- a/src/vs/workbench/contrib/files/electron-browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/electron-browser/media/explorerviewlet.css @@ -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/contrib/files/electron-browser/saveErrorHandler.ts b/src/vs/workbench/contrib/files/electron-browser/saveErrorHandler.ts index c6ebe282cac..0232222e3ba 100644 --- a/src/vs/workbench/contrib/files/electron-browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/electron-browser/saveErrorHandler.ts @@ -5,7 +5,7 @@ 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'; @@ -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); }); } diff --git a/src/vs/workbench/contrib/files/electron-browser/views/explorerView.ts b/src/vs/workbench/contrib/files/electron-browser/views/explorerView.ts index bff9758fa30..75a80567887 100644 --- a/src/vs/workbench/contrib/files/electron-browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/electron-browser/views/explorerView.ts @@ -308,8 +308,8 @@ export class ExplorerView extends ViewletPanel { 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.folderContext.set((isSingleFolder && !stat) || !!stat && stat.isDirectory); + this.readonlyContext.set(!!stat && stat.isReadonly); this.rootContext.set(!stat || (stat && stat.isRoot)); })); diff --git a/src/vs/workbench/contrib/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/electron-browser/views/explorerViewer.ts index 95ee0aa8e66..8c253e50a35 100644 --- a/src/vs/workbench/contrib/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/electron-browser/views/explorerViewer.ts @@ -26,9 +26,8 @@ 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 * 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'; @@ -313,7 +312,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.getChild(name))) { + // review (isidor): is path.normalize necessary? path.relative already returns an os path return false; // hidden through pattern } @@ -472,7 +472,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; } @@ -746,7 +746,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } const folders = this.contextService.getWorkspace().folders; - let targetIndex: number; + let targetIndex: number | undefined; const workspaceCreationData: IWorkspaceFolderCreationData[] = []; const rootsToMove: IWorkspaceFolderCreationData[] = []; diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 45881b72ff9..7bfd5e4a1c0 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/paths'; +import { joinWithSlashes } from 'vs/base/common/extpath'; 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'; @@ -18,7 +18,7 @@ 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)); + return URI.file(joinWithSlashes('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path)); } class ServiceAccessor { diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index d43e6793f1c..48b7d41df80 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/paths'; +import { joinWithSlashes } from 'vs/base/common/extpath'; 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'; @@ -17,7 +17,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor 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)); + return URI.file(joinWithSlashes('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path)); } class ServiceAccessor { @@ -47,7 +47,7 @@ suite('Files - FileEditorTracker', () => { return accessor.textFileService.models.loadOrCreate(resource).then((model: TextFileEditorModel) => { 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 +55,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/contrib/files/test/electron-browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts index 255aa26b5c3..a72554dd6ee 100644 --- a/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts @@ -6,7 +6,7 @@ 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 { joinWithSlashes } from 'vs/base/common/extpath'; import { validateFileName } from 'vs/workbench/contrib/files/electron-browser/fileActions'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; @@ -16,9 +16,9 @@ function createStat(path: string, name: string, isFolder: boolean, hasChildren: function toResource(path) { if (isWindows) { - return URI.file(join('C:\\', path)); + return URI.file(joinWithSlashes('C:\\', path)); } else { - return URI.file(join('/home/john', path)); + return URI.file(joinWithSlashes('/home/john', path)); } } @@ -253,19 +253,19 @@ suite('Files - View Model', () => { test('Merge Local with Disk', function () { const d = new Date().toUTCString(); - const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), undefined, true, false, false, 'to', Date.now(), d); - const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), undefined, true, false, false, 'to', Date.now(), new Date(0).toUTCString()); + const merge1 = new ExplorerItem(URI.file(joinWithSlashes('C:\\', '/path/to')), undefined, true, false, false, 'to', Date.now(), d); + const merge2 = new ExplorerItem(URI.file(joinWithSlashes('C:\\', '/path/to')), undefined, true, false, false, 'to', Date.now(), new Date(0).toUTCString()); // Merge Properties ExplorerItem.mergeLocalWithDisk(merge2, merge1); assert.strictEqual(merge1.mtime, merge2.mtime); // Merge Child when isDirectoryResolved=false is a no-op - merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), undefined, true, false, false, 'foo.html', Date.now(), d)); + merge2.addChild(new ExplorerItem(URI.file(joinWithSlashes('C:\\', '/path/to/foo.html')), undefined, true, false, false, 'foo.html', Date.now(), d)); ExplorerItem.mergeLocalWithDisk(merge2, merge1); // Merge Child with isDirectoryResolved=true - const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), undefined, true, false, false, 'foo.html', Date.now(), d); + const child = new ExplorerItem(URI.file(joinWithSlashes('C:\\', '/path/to/foo.html')), undefined, true, false, false, 'foo.html', Date.now(), d); merge2.removeChild(child); merge2.addChild(child); (merge2)._isDirectoryResolved = true; 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 00000000000..dca58e7cbf7 --- /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 MIT License. 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/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/electron-browser/localizations.contribution.ts index 5f18522d574..63a0a58bfcf 100644 --- a/src/vs/workbench/contrib/localizations/electron-browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/electron-browser/localizations.contribution.ts @@ -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 { joinWithSlashes } from 'vs/base/common/extpath'; 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/contrib/extensions/common/extensions'; -import { minimumTranslatedStrings } from 'vs/platform/node/minimalTranslations'; +import { minimumTranslatedStrings } from 'vs/workbench/contrib/localizations/electron-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'; @@ -82,7 +82,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo [{ label: updateAndRestart ? localize('yes', "Yes") : localize('restart now', "Restart Now"), run: () => { - const file = URI.file(join(this.environmentService.appSettingsHome, 'locale.json')); + const file = URI.file(joinWithSlashes(this.environmentService.appSettingsHome, 'locale.json')); const updatePromise = updateAndRestart ? this.jsonEditingService.write(file, { key: 'locale', value: locale }, true) : Promise.resolve(undefined); updatePromise.then(() => this.windowsService.relaunch({}), e => this.notificationService.error(e)); } diff --git a/src/vs/workbench/contrib/localizations/electron-browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/electron-browser/localizationsActions.ts index 42123a2b12a..3def8a46348 100644 --- a/src/vs/workbench/contrib/localizations/electron-browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/electron-browser/localizationsActions.ts @@ -8,7 +8,7 @@ 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 { joinWithSlashes } from 'vs/base/common/extpath'; import { URI } from 'vs/base/common/uri'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { language } from 'vs/base/common/platform'; @@ -37,7 +37,7 @@ export class ConfigureLocaleAction extends Action { } public run(event?: any): Promise { - const file = URI.file(join(this.environmentService.appSettingsHome, 'locale.json')); + const file = URI.file(joinWithSlashes(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 => { diff --git a/src/vs/platform/node/minimalTranslations.ts b/src/vs/workbench/contrib/localizations/electron-browser/minimalTranslations.ts similarity index 100% rename from src/vs/platform/node/minimalTranslations.ts rename to src/vs/workbench/contrib/localizations/electron-browser/minimalTranslations.ts diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts index c68dc48444b..f5f71c9a1ee 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { join } from 'vs/base/common/paths'; +import { joinWithSlashes } from 'vs/base/common/extpath'; 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/contrib/output/common/output'; @@ -28,13 +28,13 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { ) { super(); let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - outputChannelRegistry.registerChannel({ id: Constants.mainLogChannelId, label: nls.localize('mainLog', "Main"), file: URI.file(join(environmentService.logsPath, `main.log`)), log: true }); - outputChannelRegistry.registerChannel({ id: Constants.sharedLogChannelId, label: nls.localize('sharedLog', "Shared"), file: URI.file(join(environmentService.logsPath, `sharedprocess.log`)), log: true }); - outputChannelRegistry.registerChannel({ id: Constants.rendererLogChannelId, label: nls.localize('rendererLog', "Window"), file: URI.file(join(environmentService.logsPath, `renderer${windowService.getCurrentWindowId()}.log`)), log: true }); + outputChannelRegistry.registerChannel({ id: Constants.mainLogChannelId, label: nls.localize('mainLog', "Main"), file: URI.file(joinWithSlashes(environmentService.logsPath, `main.log`)), log: true }); + outputChannelRegistry.registerChannel({ id: Constants.sharedLogChannelId, label: nls.localize('sharedLog', "Shared"), file: URI.file(joinWithSlashes(environmentService.logsPath, `sharedprocess.log`)), log: true }); + outputChannelRegistry.registerChannel({ id: Constants.rendererLogChannelId, label: nls.localize('rendererLog', "Window"), file: URI.file(joinWithSlashes(environmentService.logsPath, `renderer${windowService.getCurrentWindowId()}.log`)), log: true }); const registerTelemetryChannel = (level) => { if (level === LogLevel.Trace && !outputChannelRegistry.getChannel(Constants.telemetryLogChannelId)) { - outputChannelRegistry.registerChannel({ id: Constants.telemetryLogChannelId, label: nls.localize('telemetryLog', "Telemetry"), file: URI.file(join(environmentService.logsPath, `telemetry.log`)), log: true }); + outputChannelRegistry.registerChannel({ id: Constants.telemetryLogChannelId, label: nls.localize('telemetryLog', "Telemetry"), file: URI.file(joinWithSlashes(environmentService.logsPath, `telemetry.log`)), log: true }); } }; registerTelemetryChannel(logService.getLevel()); diff --git a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts index cc2468a05e1..186e19612ab 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; 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'; @@ -24,7 +24,7 @@ export class OpenLogsFolderAction extends Action { } run(): Promise { - return this.windowsService.showItemInFolder(paths.join(this.environmentService.logsPath, 'main.log')); + return this.windowsService.showItemInFolder(extpath.joinWithSlashes(this.environmentService.logsPath, 'main.log')); } } diff --git a/src/vs/workbench/contrib/markers/electron-browser/markersModel.ts b/src/vs/workbench/contrib/markers/electron-browser/markersModel.ts index cf207bf5e22..41522466990 100644 --- a/src/vs/workbench/contrib/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/contrib/markers/electron-browser/markersModel.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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/contrib/markers/electron-browser/markersPanel.ts b/src/vs/workbench/contrib/markers/electron-browser/markersPanel.ts index b3d35c26455..3f43d091c5e 100644 --- a/src/vs/workbench/contrib/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/electron-browser/markersPanel.ts @@ -32,7 +32,8 @@ import { FilterOptions } from 'vs/workbench/contrib/markers/electron-browser/mar 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 { joinWithSlashes } from 'vs/base/common/extpath'; +import { isAbsolute } from 'vs/base/common/path'; import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/electron-browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -235,7 +236,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } 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(); } @@ -253,7 +254,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { 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(); } @@ -287,7 +288,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return Object.keys(expr) .reduce((absExpr: IExpression, key: string) => { if (expr[key] && !isAbsolute(key)) { - const absPattern = join(root, key); + const absPattern = joinWithSlashes(root, key); absExpr[absPattern] = expr[key]; } @@ -357,7 +358,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; @@ -388,7 +389,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { })); this._register(Event.any(this.tree.onDidChangeSelection, this.tree.onDidChangeFocus)(() => { - const elements: TreeElement[] = [...this.tree.getSelection(), ...this.tree.getFocus()]; + const elements = [...this.tree.getSelection(), ...this.tree.getFocus()]; for (const element of elements) { if (element instanceof Marker) { const viewModel = this.markersViewModel.getViewModel(element); @@ -401,7 +402,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } 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([]); @@ -466,14 +467,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 { @@ -483,7 +485,7 @@ 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 { @@ -497,7 +499,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(); } @@ -693,7 +695,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { 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; diff --git a/src/vs/workbench/contrib/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/electron-browser/markersPanelActions.ts index c620c5e6b3f..50539360d9f 100644 --- a/src/vs/workbench/contrib/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/electron-browser/markersPanelActions.ts @@ -58,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(); } } diff --git a/src/vs/workbench/contrib/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/electron-browser/markersTreeViewer.ts index 5dc45a8645f..b666987f50f 100644 --- a/src/vs/workbench/contrib/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/electron-browser/markersTreeViewer.ts @@ -5,7 +5,7 @@ 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'; @@ -303,7 +303,7 @@ 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(); }; @@ -392,7 +392,7 @@ export class RelatedInformationRenderer implements ITreeRenderer { 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 } }; @@ -476,7 +476,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) { diff --git a/src/vs/workbench/contrib/markers/electron-browser/messages.ts b/src/vs/workbench/contrib/markers/electron-browser/messages.ts index 07b0ed7de14..e6b752e0574 100644 --- a/src/vs/workbench/contrib/markers/electron-browser/messages.ts +++ b/src/vs/workbench/contrib/markers/electron-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/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index a6421a616e6..eac6760bc34 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -41,7 +41,7 @@ import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { CollapseAction2 } from 'vs/workbench/browser/viewlet'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { OutlineConfigKeys, OutlineViewFiltered, OutlineViewFocused } from '../../../../editor/contrib/documentSymbols/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'; @@ -430,7 +430,7 @@ export class OutlinePanel extends ViewletPanel { } let textModel = editor.getModel(); - let loadingMessage: IDisposable; + let loadingMessage: IDisposable | undefined; if (!oldModel) { loadingMessage = new TimeoutTimer( () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))), @@ -448,17 +448,12 @@ export class OutlinePanel extends ViewletPanel { return this._showMessage(localize('no-symbols', "No symbols found in document '{0}'", basename(textModel.uri))); } - let newSize = TreeElement.size(newModel); - 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.")); - } - dom.removeClass(this._domNode, 'message'); 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); diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index 6fb19a3e569..a09231f1d15 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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'; @@ -30,7 +30,7 @@ export class LogViewerInput extends ResourceEditorInput { @ITextModelService textModelResolverService: ITextModelService, @IHashService hashService: IHashService ) { - 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, hashService); } public getTypeId(): string { diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index fb0cfa131c7..934be8edfdd 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -193,7 +193,7 @@ export class OpenLogOutputFile extends Action { private update(): void { const outputChannelDescriptor = this.getOutputChannelDescriptor(); - this.enabled = outputChannelDescriptor && outputChannelDescriptor.file && outputChannelDescriptor.log; + this.enabled = !!(outputChannelDescriptor && outputChannelDescriptor.file && outputChannelDescriptor.log); } public run(): Promise { diff --git a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts index c6cf2a01a14..10cb01e71bf 100644 --- a/src/vs/workbench/contrib/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts @@ -6,8 +6,9 @@ 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 path from 'vs/base/common/path'; import * as strings from 'vs/base/common/strings'; import * as arrays from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; @@ -88,8 +89,8 @@ export class OutputLinkComputer { const workspaceFolderPath = workspaceFolder.scheme === 'file' ? workspaceFolder.fsPath : workspaceFolder.path; const workspaceFolderVariants = arrays.distinct([ - paths.normalize(workspaceFolderPath, true), - paths.normalize(workspaceFolderPath, false) + path.normalize(workspaceFolderPath), + extpath.normalizeWithSlashes(workspaceFolderPath) ]); workspaceFolderVariants.forEach(workspaceFolderVariant => { diff --git a/src/vs/workbench/contrib/output/electron-browser/outputServices.ts b/src/vs/workbench/contrib/output/electron-browser/outputServices.ts index 6a9a65811a8..4f35b970f61 100644 --- a/src/vs/workbench/contrib/output/electron-browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/electron-browser/outputServices.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; +import { dirname } from 'vs/base/common/path'; import * as strings from 'vs/base/common/strings'; import * as extfs from 'vs/base/node/extfs'; import { Event, Emitter } from 'vs/base/common/event'; @@ -37,7 +38,7 @@ 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 { OutputAppender } from 'vs/workbench/contrib/output/node/outputAppender'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isNumber } from 'vs/base/common/types'; @@ -184,12 +185,12 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannel implements Out @IModeService modeService: IModeService, @ILogService logService: ILogService ) { - super({ ...outputChannelDescriptor, file: URI.file(paths.join(outputDir, `${outputChannelDescriptor.id}.log`)) }, modelUri, fileService, modelService, modeService); + super({ ...outputChannelDescriptor, file: URI.file(extpath.joinWithSlashes(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._register(watchOutputDirectory(dirname(this.file.fsPath), logService, (eventType, file) => this.onFileChangedInOutputDirector(eventType, file))); this.resettingDelayer = new ThrottledDelayer(50); } @@ -446,7 +447,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo ) { 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, '')}`); + this.outputDir = extpath.joinWithSlashes(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); @@ -492,10 +493,10 @@ export class OutputService extends Disposable implements IOutputService, ITextMo this.activeChannel = channel; let promise: Promise; if (this.isPanelShown()) { - promise = this.doShowChannel(channel, preserveFocus); + promise = this.doShowChannel(channel, !!preserveFocus); } else { this.panelService.openPanel(OUTPUT_PANEL_ID); - promise = this.doShowChannel(this.activeChannel, preserveFocus); + promise = this.doShowChannel(this.activeChannel, !!preserveFocus); } return promise.then(() => this._onActiveOutputChannel.fire(id)); } @@ -612,7 +613,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo private isPanelShown(): boolean { const panel = this.panelService.getActivePanel(); - return panel && panel.getId() === OUTPUT_PANEL_ID; + return !!panel && panel.getId() === OUTPUT_PANEL_ID; } private createInput(channel: IOutputChannel): ResourceEditorInput { diff --git a/src/vs/platform/output/node/outputAppender.ts b/src/vs/workbench/contrib/output/node/outputAppender.ts similarity index 100% rename from src/vs/platform/output/node/outputAppender.ts rename to src/vs/workbench/contrib/output/node/outputAppender.ts diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index 9bb55a53b25..2ec6c1bcf18 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -52,7 +52,7 @@ export class PerfviewInput extends ResourceEditorInput { ) { super( localize('name', "Startup Performance"), - undefined, + null, PerfviewInput.Uri, textModelResolverService, hashService ); @@ -85,7 +85,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 +106,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 +130,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,7 +148,7 @@ 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}`]); @@ -333,7 +343,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; @@ -357,7 +367,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/contrib/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index c9ad9a6a401..968a1a5fc78 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. 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'; diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 0cdaaf7ac93..4d520ed4755 100644 --- a/src/vs/workbench/contrib/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'; @@ -25,33 +25,46 @@ import { DefineKeybindingWidget, KeybindingsSearchWidget, KeybindingsSearchOptio 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 + 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 } from 'vs/platform/contextview/browser/contextView'; +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, listActiveSelectionForeground } 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,24 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor }); } + defineWhenExpression(keybindingEntry: IKeybindingItemEntry): void { + 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 +379,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 +398,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, $('', undefined, localize('header-message', "For advanced customizations open and edit"))); - const fileElement = DOM.append(openKeybindingsContainer, $('.file-name', undefined, 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,17 +414,29 @@ 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', undefined, localize('command', "Command")), - $('.header.keybinding', undefined, localize('keybinding', "Keybinding")), - $('.header.source', undefined, localize('source', "Source")), - $('.header.when', undefined, 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 }, ariaLabel: localize('keybindingsLabel', "Keybindings"), @@ -512,6 +542,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 +600,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 +628,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 +655,15 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor }; } + private createDefineWhenExpressionAction(keybindingItemEntry: IKeybindingItemEntry): IAction { + return { + label: localize('editWhen', "Change When Expression"), + enabled: true, + id: KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, + run: () => this.defineWhenExpression(keybindingItemEntry) + }; + } + private createRemoveAction(keybindingItem: IKeybindingItemEntry): IAction { return { label: localize('removeLabel', "Remove Keybinding"), @@ -711,72 +768,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 +891,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 +913,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 +938,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 +971,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,28 +999,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.whenColumn.title = ''; + 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 { @@ -940,8 +1122,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 listFocusAndSelectionForegroundColor = theme.getColor(listActiveSelectionForeground); - if (listFocusAndSelectionForegroundColor) { - collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list:focus .monaco-list-row.selected.focused > .column .monaco-keybinding-key { color: ${listFocusAndSelectionForegroundColor}; }`); + 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/contrib/preferences/browser/media/keybindingsEditor.css b/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css index a3cf8082534..3e73bc7fbfb 100644 --- a/src/vs/workbench/contrib/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/contrib/preferences/browser/media/settingsWidgets.css b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css index 8d341146a40..695926f9e33 100644 --- a/src/vs/workbench/contrib/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/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index b3dc05798a8..0a52f70fb78 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -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]); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 21c46e7f5fb..7f38f0ee1ce 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -499,7 +499,7 @@ class PreferencesRenderersController extends Disposable { this.consolidateAndUpdate(defaultFilterResult, editableFilterResult); this._lastFilterResult = defaultFilterResult; - return defaultFilterResult && defaultFilterResult.exactMatch; + return !!(defaultFilterResult && defaultFilterResult.exactMatch); }); } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index f0328a98c34..a65c6123b7e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -100,14 +100,14 @@ 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))); } @@ -460,7 +460,7 @@ export abstract class AbstractSettingRenderer implements ITreeRenderer; + readonly onLayout: Event; search(filter: string): void; focusSearch(): void; @@ -53,7 +56,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 +95,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,7 +106,7 @@ 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'; -export const FOLDER_SETTINGS_PATH = join('.vscode', 'settings.json'); +export const FOLDER_SETTINGS_PATH = joinWithSlashes('.vscode', 'settings.json'); export const DEFAULT_SETTINGS_EDITOR_SETTING = 'workbench.settings.openDefaultSettings'; export const MODIFIED_SETTING_TAG = 'modified'; diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css index 3c84a5c7efe..402d2bab95d 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css @@ -398,7 +398,7 @@ .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-contents .setting-item-enumDescription { diff --git a/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts index b1668428f7d..5ccb0154105 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts @@ -29,7 +29,7 @@ import { ResourceContextKey } from 'vs/workbench/common/resources'; 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 } from 'vs/workbench/contrib/preferences/common/preferences'; +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'; @@ -231,6 +231,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.defineWhenExpression(control.activeKeybindingEntry); + } + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: KEYBINDINGS_EDITOR_COMMAND_REMOVE, weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts index 431ab1c2b3b..42948953bfa 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts @@ -290,8 +290,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('##'); } diff --git a/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts index d29ebd7cf75..f1436ea0be7 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts @@ -755,7 +755,7 @@ export class SettingsEditor2 extends BaseEditor { 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 }; @@ -959,11 +959,11 @@ export class SettingsEditor2 extends BaseEditor { this.updateModifiedLabelForKey(key); this.scheduleRefresh(focusedSetting, key); - return Promise.resolve(null); + return Promise.resolve(); } } else { this.scheduleRefresh(focusedSetting); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -976,7 +976,7 @@ export class SettingsEditor2 extends BaseEditor { this.refreshTree(); } else { // Refresh requested for a key that we don't know about - return Promise.resolve(null); + return Promise.resolve(); } } else { this.refreshTree(); diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts index cd3d53d455e..b8a09c7cd57 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts @@ -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,7 +96,7 @@ class GotoLineEntry extends EditorQuickOpenEntry { // Inform user about valid range if input is invalid const maxLineNumber = this.getMaxLineNumber(); - if (this.invalidRange(maxLineNumber)) { + if (this.editorService.activeTextEditorWidget && this.invalidRange(maxLineNumber)) { const position = this.editorService.activeTextEditorWidget.getPosition(); if (position) { const currentLine = position.lineNumber; @@ -115,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) { @@ -132,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 { @@ -153,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 @@ -183,8 +190,8 @@ class GotoLineEntry extends EditorQuickOpenEntry { activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); // Decorate if possible - if (types.isFunction(activeTextEditorWidget.changeDecorations)) { - this.handler.decorateOutline(range, activeTextEditorWidget, this.editorService.activeControl.group!); + if (this.editorService.activeControl && types.isFunction(activeTextEditorWidget.changeDecorations)) { + this.handler.decorateOutline(range, activeTextEditorWidget, this.editorService.activeControl.group); } } @@ -236,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)])); diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index c9d2b8d47f3..e53df5f3e76 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -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,8 +337,8 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); // Decorate if possible - if (types.isFunction(activeTextEditorWidget.changeDecorations)) { - this.handler.decorateOutline(this.range, range, activeTextEditorWidget, this.editorService.activeControl.group!); + if (this.editorService.activeControl && types.isFunction(activeTextEditorWidget.changeDecorations)) { + this.handler.decorateOutline(this.range, range, activeTextEditorWidget, this.editorService.activeControl.group); } } @@ -401,7 +401,9 @@ 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 diff --git a/src/vs/workbench/contrib/scm/electron-browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/electron-browser/dirtydiffDecorator.ts index 864b3c1d100..77f28716b69 100644 --- a/src/vs/workbench/contrib/scm/electron-browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/electron-browser/dirtydiffDecorator.ts @@ -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'; @@ -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 = ''; } diff --git a/src/vs/workbench/contrib/scm/electron-browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/electron-browser/scm.contribution.ts index 7807df023e6..70258e96a6a 100644 --- a/src/vs/workbench/contrib/scm/electron-browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/electron-browser/scm.contribution.ts @@ -128,7 +128,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const args = repository.provider.acceptInputCommand.arguments; const commandService = accessor.get(ICommandService); - return commandService.executeCommand(id, ...args); + return commandService.executeCommand(id, ...(args || [])); } }); diff --git a/src/vs/workbench/contrib/scm/electron-browser/scmActivity.ts b/src/vs/workbench/contrib/scm/electron-browser/scmActivity.ts index df3d55c4e0e..2bbb403db14 100644 --- a/src/vs/workbench/contrib/scm/electron-browser/scmActivity.ts +++ b/src/vs/workbench/contrib/scm/electron-browser/scmActivity.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 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, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; @@ -184,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/contrib/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/electron-browser/scmViewlet.ts index 5ed9de8e244..077334bb5ca 100644 --- a/src/vs/workbench/contrib/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/electron-browser/scmViewlet.ts @@ -7,7 +7,7 @@ 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 { basename } from 'vs/base/common/resources'; 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'; @@ -170,7 +170,7 @@ class ProviderRenderer implements IListRenderer { getKeyboardNavigationLabel(e: ISCMResourceGroup | ISCMResource) { if (isSCMResource(e)) { - return basename(e.sourceUri.fsPath); + return basename(e.sourceUri); } else { return e.label; } @@ -773,7 +773,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; diff --git a/src/vs/workbench/contrib/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/contrib/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/contrib/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/contrib/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/contrib/search/browser/openFileHandler.ts b/src/vs/workbench/contrib/search/browser/openFileHandler.ts index 0434fc1c196..5927620509f 100644 --- a/src/vs/workbench/contrib/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'; @@ -23,7 +23,7 @@ import { EditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/ 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'; @@ -174,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)); } @@ -186,7 +186,7 @@ export class OpenFileHandler extends QuickOpenHandler { } private getAbsolutePathResult(query: IPreparedQuery): Promise { - if (paths.isAbsolute(query.original)) { + if (isAbsolute(query.original)) { const resource = URI.file(query.original); return this.fileService.resolveFile(resource).then(stat => stat.isDirectory ? undefined : resource, error => undefined); diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index a5e2a7e7b59..d53e63d5fe6 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -19,7 +19,7 @@ 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/contrib/search/common/search'; -import { basename } from 'vs/base/common/paths'; +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'; @@ -52,7 +52,7 @@ class SymbolEntry extends EditorQuickOpenEntry { 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 }); diff --git a/src/vs/workbench/contrib/search/electron-browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts similarity index 99% rename from src/vs/workbench/contrib/search/electron-browser/search.contribution.ts rename to src/vs/workbench/contrib/search/browser/search.contribution.ts index 8f8d622cb86..cc853ddf478 100644 --- a/src/vs/workbench/contrib/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. 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,7 +32,7 @@ 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, VIEW_ID, ISearchConfiguration } 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'; @@ -122,7 +122,7 @@ 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(); + accessor.get(IInstantiationService).createInstance(RemoveAction, tree, tree.getFocus()[0]).run(); } }); @@ -357,7 +357,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())); }); } @@ -393,7 +393,7 @@ 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)); + searchView.searchInFolders(null); }); } }); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 6e9c0f5e71e..f0a3c4dd3f6 100644 --- a/src/vs/workbench/contrib/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'; @@ -21,11 +21,11 @@ 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 { ISearchConfiguration, ISearchHistoryService, VIEW_ID } from 'vs/workbench/services/search/common/search'; 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 { FileMatch, FileMatchOrMatch, FolderMatch, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { FileMatch, FileMatchOrMatch, FolderMatch, Match, RenderableMatch, searchMatchComparer, SearchResult, BaseFolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -34,7 +34,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; 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 { @@ -54,7 +54,7 @@ export function openSearchView(viewletService: IViewletService, panelService: IP return Promise.resolve(panelService.openPanel(VIEW_ID, focus) as SearchView); } -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; @@ -65,7 +65,7 @@ export function getSearchView(viewletService: IViewletService, panelService: IPa return activePanel; } - return undefined; + return null; } function doAppendKeyBindingLabel(label: string, keyBinding: ResolvedKeybinding): string { @@ -230,7 +230,7 @@ export class RefreshAction extends Action { } get enabled(): boolean { - return this.searchView.isSearchSubmitted(); + return this.searchView && this.searchView.isSearchSubmitted(); } update(): void { @@ -242,6 +242,7 @@ export class RefreshAction extends Action { if (searchView) { searchView.onQueryChanged(); } + return Promise.resolve(null); } } @@ -261,7 +262,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 +277,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 +319,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 +327,7 @@ export class ClearSearchResultsAction extends Action { if (searchView) { searchView.clearSearchResults(); } - return Promise.resolve(null); + return Promise.resolve(); } } @@ -345,7 +346,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 { @@ -406,8 +407,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 +435,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 +456,6 @@ export class RemoveAction extends AbstractSearchAndReplaceAction { static LABEL = nls.localize('RemoveAction.label', "Dismiss"); constructor( - private viewlet: SearchView, private viewer: WorkbenchObjectTree, private element: RenderableMatch ) { @@ -473,24 +473,9 @@ export class RemoveAction extends AbstractSearchAndReplaceAction { 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.element.parent().remove(this.element); this.viewer.domFocus(); - this.viewlet.refreshTree({ elements: [elementToRefresh] }); + return Promise.resolve(); } } @@ -639,7 +624,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) => { @@ -710,12 +695,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; } diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts index fb053f632e8..c7f40a2eff2 100644 --- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -11,14 +11,14 @@ import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; 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'; @@ -135,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 }); } @@ -271,9 +271,9 @@ export class MatchRenderer extends Disposable implements ITreeRenderer; + private readonly selectCurrentMatchEmitter: Emitter; private delayedRefresh: Delayer; private changedWhileHidden: boolean; @@ -443,7 +443,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 +496,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); } @@ -704,7 +704,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } selectNextMatch(): void { - const [selected]: RenderableMatch[] = this.tree.getSelection(); + const [selected] = this.tree.getSelection(); // Expand the initial selected node, if needed if (selected instanceof FileMatch) { @@ -742,7 +742,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } selectPreviousMatch(): void { - const [selected]: RenderableMatch[] = this.tree.getSelection(); + const [selected] = this.tree.getSelection(); let navigator = this.tree.navigate(selected); let prev = navigator.previous(); @@ -757,7 +757,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; } @@ -967,7 +967,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; } @@ -985,7 +985,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } } - if (!isCodeEditor(activeTextEditorWidget)) { + if (!isCodeEditor(activeTextEditorWidget) || !activeTextEditorWidget.hasModel()) { return null; } @@ -1076,16 +1076,16 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } } - 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; } @@ -1097,14 +1097,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}`; } } else { - folderPath = resource.fsPath; + folderPath = resource.fsPath; // TODO rob: handle on-file URIs } } } diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 8b7ee3dbafa..3010fb299d0 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -25,7 +25,7 @@ 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/browser/contextScopedHistoryWidget'; @@ -185,7 +185,7 @@ export class SearchWidget extends Widget { } isReplaceActive(): boolean { - return this.replaceActive.get(); + return !!this.replaceActive.get(); } getReplaceValue(): string { diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index 5b0b644d105..66991d572a8 100644 --- a/src/vs/workbench/contrib/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,7 +16,7 @@ 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 { getExcludes, ICommonQueryProps, IFileQuery, IFolderQuery, IPatternInfo, ISearchConfiguration, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType } from 'vs/workbench/services/search/common/search'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; /** @@ -211,7 +211,7 @@ export class QueryBuilder { parseSearchPaths(pattern: string): ISearchPathsResult { 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) @@ -301,11 +301,11 @@ export class QueryBuilder { * Takes a searchPath like `./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)) }]; } diff --git a/src/vs/workbench/contrib/search/common/search.ts b/src/vs/workbench/contrib/search/common/search.ts index 09bc26d9b9d..0e56ed9e0ba 100644 --- a/src/vs/workbench/contrib/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/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 1e020ef4531..03efb83863e 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -19,8 +19,8 @@ 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'; @@ -598,7 +598,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/contrib/search/test/browser/openFileHandler.test.ts b/src/vs/workbench/contrib/search/test/browser/openFileHandler.test.ts index 97eebd7be8e..d6887d88e82 100644 --- a/src/vs/workbench/contrib/search/test/browser/openFileHandler.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/openFileHandler.test.ts @@ -8,7 +8,7 @@ import * as errors from 'vs/base/common/errors'; import * as objects from 'vs/base/common/objects'; 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/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index 452302f9df3..8f63fa9eb07 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -14,7 +14,7 @@ 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 { 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'; diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index 7e4e27f20b6..447fffccfbe 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -9,7 +9,7 @@ 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/contrib/search/common/searchModel'; diff --git a/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts index a698314ec1f..df104fe68f3 100644 --- a/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { IExpression } from 'vs/base/common/glob'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; 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/contrib/search/common/queryBuilder'; import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; @@ -908,7 +908,7 @@ function fixPath(...slashPathParts: string[]): string { slashPathParts.unshift('c:'); } - return paths.join(...slashPathParts); + return extpath.joinWithSlashes(...slashPathParts); } function normalizeExpression(expression: IExpression | undefined): IExpression | undefined { diff --git a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts index 753daca49ba..d1438b530b7 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -14,7 +14,7 @@ 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/contrib/search/common/searchModel'; diff --git a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts index 5824e9476b9..64f09791842 100644 --- a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts @@ -7,7 +7,7 @@ import * as sinon from 'sinon'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; 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'; diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index 45322bd3c09..3d6418cbfa3 100644 --- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts @@ -8,7 +8,8 @@ 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 'vs/base/common/paths'; +import { joinWithSlashes } from 'vs/base/common/extpath'; +import { 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'; @@ -88,14 +89,14 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir } } - const dir = join(envService.appSettingsHome, 'snippets'); + const dir = joinWithSlashes(envService.appSettingsHome, 'snippets'); for (const mode of modeService.getRegisteredModes()) { const label = modeService.getLanguageName(mode); if (label && !seen.has(mode)) { future.push({ label: mode, description: `(${label})`, - filepath: join(dir, `${mode}.json`), + filepath: joinWithSlashes(dir, `${mode}.json`), hint: true }); } @@ -206,7 +207,7 @@ CommandsRegistry.registerCommand(id, async (accessor): Promise => { 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')) + uri: URI.file(joinWithSlashes(envService.appSettingsHome, 'snippets')) }]; const workspaceSnippetPicks: SnippetPick[] = []; diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts index dc796e6acb7..bcf3f7a202f 100644 --- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts @@ -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/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index 3631253adcf..7ae06dbb3ef 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -7,7 +7,7 @@ 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 'vs/base/common/paths'; +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'; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 033852eaa4a..b5aeab0dce5 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { basename, extname, join } from 'vs/base/common/paths'; +import { joinWithSlashes } from 'vs/base/common/extpath'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { combinedDisposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; @@ -295,7 +295,7 @@ class SnippetsService implements ISnippetsService { } private _initUserSnippets(): Promise { - const userSnippetsFolder = URI.file(join(this._environmentService.appSettingsHome, 'snippets')); + const userSnippetsFolder = URI.file(joinWithSlashes(this._environmentService.appSettingsHome, 'snippets')); return this._fileService.createFolder(userSnippetsFolder).then(() => this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, this._disposables)); } @@ -321,10 +321,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).replace(/\.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/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index 018ac5b635d..4dad0437225 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -8,10 +8,9 @@ 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'; @@ -19,6 +18,9 @@ import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common import * as themes from 'vs/workbench/common/theme'; import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { joinWithSlashes } from 'vs/base/common/extpath'; class PartsSplash { @@ -32,10 +34,10 @@ class PartsSplash { constructor( @IThemeService private readonly _themeService: IThemeService, @IPartService private readonly _partService: IPartService, - @IStorageService private readonly _storageService: IStorageService, + @IFileService private readonly _fileService: IFileService, @IEnvironmentService private readonly _envService: IEnvironmentService, + @IBroadcastService private readonly _broadcastService: IBroadcastService, @ILifecycleService lifecycleService: ILifecycleService, - @IBroadcastService private readonly broadcastService: IBroadcastService ) { lifecycleService.when(LifecyclePhase.Restored).then(_ => this._removePartsSplash()); Event.debounce(Event.any( @@ -67,12 +69,16 @@ class PartsSplash { sideBarWidth: getTotalWidth(this._partService.getContainer(Parts.SIDEBAR_PART)!), statusBarHeight: getTotalHeight(this._partService.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(joinWithSlashes(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 +87,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) }) }); } } diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index 17f50ff18ed..c860ea88749 100644 --- a/src/vs/workbench/contrib/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 * as Extpath from 'vs/base/common/extpath'; 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 = Extpath.joinWithSlashes(matcher.filePrefix, filename); } if (fullPath === undefined) { throw new Error('FileLocationKind is not actionable. Does the matcher have a filePrefix? This should never happen.'); diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index 48cc70b770c..8e24623e7eb 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -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; diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 1b6177ee838..64284fe4728 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -223,7 +223,7 @@ 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 }; } @@ -350,7 +350,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; } diff --git a/src/vs/workbench/contrib/tasks/electron-browser/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/electron-browser/jsonSchema_v2.ts index 1543a793850..08edd544b2c 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/jsonSchema_v2.ts @@ -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/contrib/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts index 641dccb3fdb..347978108d1 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts @@ -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) => { @@ -852,7 +864,7 @@ class TaskService extends Disposable implements ITaskService { }); } - public run(task: Task, options?: ProblemMatcherRunOptions): Promise { + public run(task: Task | undefined, options?: ProblemMatcherRunOptions): Promise { 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); @@ -1001,8 +1013,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; @@ -1033,7 +1045,7 @@ class TaskService extends Disposable implements ITaskService { } } - let promise: Promise; + let promise: Promise | undefined; if (!fileConfig) { let value = { version: '2.0.0', @@ -1487,7 +1499,7 @@ class TaskService extends Disposable implements ITaskService { } private getLegacyTaskConfigurations(workspaceTasks: TaskSet): IStringDictionary { - let result: IStringDictionary; + let result: IStringDictionary | undefined; function getResult() { if (result) { return result; @@ -1579,7 +1591,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) @@ -1846,7 +1858,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) { @@ -2412,7 +2424,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) { @@ -2640,17 +2652,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' } ] } diff --git a/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts index 5d1612649c8..b0031d5cc66 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. 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,7 +14,7 @@ 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'; @@ -735,7 +734,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')) { @@ -815,23 +814,14 @@ 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); + shellLaunchConfig.cwd = path.normalize(cwd); } if (options.env) { shellLaunchConfig.env = options.env; diff --git a/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts b/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts index 5e346d78e8e..5399ba2ab23 100644 --- a/src/vs/workbench/contrib/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/contrib/terminal/browser/terminalFindWidget.css b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.css index 93422f893ea..e04c75bc0e2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.css +++ b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.css @@ -5,5 +5,4 @@ .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/contrib/terminal/electron-browser/media/terminal.css b/src/vs/workbench/contrib/terminal/electron-browser/media/terminal.css index 74e79a263ed..c5da665535e 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/electron-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/contrib/terminal/electron-browser/media/xterm.css b/src/vs/workbench/contrib/terminal/electron-browser/media/xterm.css index be7a3c7a177..9c52131931b 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/electron-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/contrib/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts index 1f3cf1d3b57..b15ee85b95b 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts @@ -514,7 +514,22 @@ 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(); diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalActions.ts index 67965756849..a202076042b 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalActions.ts @@ -40,23 +40,23 @@ 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': @@ -137,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)); } } @@ -291,7 +291,7 @@ export class SendSequenceTerminalCommand extends Command { const workspaceContextService = accessor.get(IWorkspaceContextService); const historyService = accessor.get(IHistoryService); const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file); - const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null; + const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) || undefined : undefined; const resolvedText = configurationResolverService.resolve(lastActiveWorkspaceRoot, args.text); terminalInstance.sendText(resolvedText, false); } @@ -324,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 @@ -651,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(); @@ -687,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; @@ -1050,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); } } @@ -1071,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/contrib/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalConfigHelper.ts index a6d3638f943..bf0faf95e77 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalConfigHelper.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 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'; diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstance.ts index b9ca8a82b51..e4c9b02f123 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstance.ts @@ -5,7 +5,7 @@ import { execFile } from 'child_process'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -13,7 +13,6 @@ 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'; @@ -924,7 +923,7 @@ 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()); } @@ -1245,7 +1244,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]; diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalLinkHandler.ts index e0d59d335ac..734d13fd45a 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalLinkHandler.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 platform from 'vs/base/common/platform'; import * as pfs from 'vs/base/node/pfs'; import { URI as Uri } from 'vs/base/common/uri'; diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalPanel.ts index efdff455b8c..bfde146007b 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalPanel.ts @@ -161,7 +161,7 @@ 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; } @@ -285,7 +285,7 @@ export class TerminalPanel extends Panel { } // 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; diff --git a/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts index bd134149555..18cd3308cf3 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalEnvironment.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 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'; @@ -111,10 +111,10 @@ export function getCwd(shell: IShellLaunchConfig, root?: Uri, customCwd?: string // 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); } } diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index e1bd577ca28..fdadec7eda5 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 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 { Event, Emitter } from 'vs/base/common/event'; diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts index ea0d7c93361..99edaab4671 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { Platform } from 'vs/base/common/platform'; import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/contrib/terminal/electron-browser/terminalLinkHandler'; import * as strings from 'vs/base/common/strings'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as sinon from 'sinon'; class TestTerminalLinkHandler extends TerminalLinkHandler { diff --git a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts index 89c17efaa79..9869fedd3fa 100644 --- a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. 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 { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import * as pfs from 'vs/base/node/pfs'; @@ -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] }; } @@ -178,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; } @@ -215,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); @@ -231,7 +231,7 @@ CommandsRegistry.registerCommand('_workbench.captureSyntaxTokens', function (acc let process = (resource: URI) => { let filePath = resource.fsPath; - let fileName = paths.basename(filePath); + let fileName = basename(filePath); let snapper = accessor.get(IInstantiationService).createInstance(Snapper); return pfs.readFile(filePath).then(content => { @@ -240,8 +240,8 @@ CommandsRegistry.registerCommand('_workbench.captureSyntaxTokens', function (acc }; 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/contrib/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts index 5ebb5c8a1f4..13292e55484 100644 --- a/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts @@ -26,6 +26,7 @@ import { WebviewEditorInput } from 'vs/workbench/contrib/webview/electron-browse 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/contrib/update/electron-browser/update.ts b/src/vs/workbench/contrib/update/electron-browser/update.ts index a7190a0e6cc..b7f22bce9dc 100644 --- a/src/vs/workbench/contrib/update/electron-browser/update.ts +++ b/src/vs/workbench/contrib/update/electron-browser/update.ts @@ -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 { @@ -461,7 +461,7 @@ 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); } @@ -510,7 +510,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: diff --git a/src/vs/workbench/contrib/watermark/electron-browser/watermark.ts b/src/vs/workbench/contrib/watermark/electron-browser/watermark.ts index 4a0f500840e..b2dffd595a3 100644 --- a/src/vs/workbench/contrib/watermark/electron-browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/electron-browser/watermark.ts @@ -146,6 +146,9 @@ export class WatermarkContribution implements IWorkbenchContribution { private create(): void { const container = this.partService.getContainer(Parts.EDITOR_PART); + if (!container) { + throw new Error('Could not find container'); + } container.classList.add('has-watermark'); this.watermark = $('.watermark'); @@ -176,7 +179,10 @@ export class WatermarkContribution implements IWorkbenchContribution { private destroy(): void { if (this.watermark) { this.watermark.remove(); - this.partService.getContainer(Parts.EDITOR_PART).classList.remove('has-watermark'); + const container = this.partService.getContainer(Parts.EDITOR_PART); + if (container) { + container.classList.remove('has-watermark'); + } this.dispose(); } } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webview-pre.js b/src/vs/workbench/contrib/webview/electron-browser/webview-pre.js index 5fb945732cc..389a4b89a2e 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webview-pre.js +++ b/src/vs/workbench/contrib/webview/electron-browser/webview-pre.js @@ -197,7 +197,7 @@ newDocument.querySelectorAll('a').forEach(a => { if (!a.title) { - a.title = a.href; + a.title = a.getAttribute('href'); } }); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts index 09ec22e7ae0..5e9c6243e94 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts @@ -228,7 +228,10 @@ 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._partService.getContainer(Parts.EDITOR_PART); + if (part) { + part.appendChild(this._container); + } } return this._container; } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts index 707ddc031cf..e46bac47814 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts @@ -19,7 +19,7 @@ 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; } @@ -34,7 +34,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { public serialize( input: WebviewEditorInput - ): string { + ): string | null { // Has no state, don't revive if (!input.state) { return null; diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts index 10dbe724d17..abbda87a79e 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts @@ -28,7 +28,7 @@ export interface IWebviewEditorService { title: string, showOptions: ICreateWebViewShowOptions, options: WebviewInputOptions, - extensionLocation: URI, + extensionLocation: URI | undefined, events: WebviewEvents ): WebviewEditorInput; @@ -39,7 +39,7 @@ export interface IWebviewEditorService { iconPath: { light: URI, dark: URI } | undefined, state: any, options: WebviewInputOptions, - extensionLocation: URI + extensionLocation: URI | undefined, ): WebviewEditorInput; revealWebview( @@ -90,7 +90,7 @@ export function areWebviewInputOptionsEqual(a: WebviewInputOptions, b: WebviewIn export class WebviewEditorService implements IWebviewEditorService { _serviceBrand: any; - private readonly _revivers = new Map(); + private readonly _revivers = new Map(); private _awaitingRevival: { input: WebviewEditorInput, resolve: (x: any) => void }[] = []; constructor( @@ -104,7 +104,7 @@ export class WebviewEditorService implements IWebviewEditorService { 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); @@ -120,7 +120,7 @@ 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 }); + this._editorGroupService.getGroup(webview.group!).moveEditor(webview, group, { preserveFocus }); } } @@ -146,7 +146,7 @@ export class WebviewEditorService implements IWebviewEditorService { // 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 }); + this._awaitingRevival.push({ input: webview, resolve: resolve! }); return promise; }); } @@ -160,15 +160,13 @@ export class WebviewEditorService implements IWebviewEditorService { reviver: WebviewReviver ): IDisposable { if (this._revivers.has(viewType)) { - this._revivers.get(viewType).push(reviver); - } else { - this._revivers.set(viewType, [reviver]); + throw new Error(`Reviver for ${viewType} already registered`); } - + 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); + const toRevive = this._awaitingRevival.filter(x => reviver.canRevive(x.input)); + this._awaitingRevival = this._awaitingRevival.filter(x => !reviver.canRevive(x.input)); for (const input of toRevive) { reviver.reviveWebview(input.input).then(() => input.resolve(undefined)); @@ -183,23 +181,19 @@ export class WebviewEditorService implements IWebviewEditorService { webview: WebviewEditorInput ): boolean { const viewType = webview.viewType; - return this._revivers.has(viewType) && this._revivers.get(viewType).some(reviver => reviver.canRevive(webview)); + const reviver = this._revivers.get(viewType); + return !!reviver && reviver.canRevive(webview); } private async tryRevive( webview: WebviewEditorInput ): Promise { - const revivers = this._revivers.get(webview.viewType); - if (!revivers) { + const reviver = this._revivers.get(webview.viewType); + if (!reviver || !reviver.canRevive(webview)) { return false; } - for (const reviver of revivers) { - if (reviver.canRevive(webview)) { - await reviver.reviveWebview(webview); - return true; - } - } - return false; + await reviver.reviveWebview(webview); + return true; } } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 79cda62b62d..993ca09e1c2 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -15,8 +15,6 @@ import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/the 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'; @@ -30,6 +28,7 @@ export interface WebviewContentOptions { readonly allowScripts?: boolean; readonly svgWhiteList?: string[]; readonly localResourceRoots?: ReadonlyArray; + readonly disableFindView?: boolean; } interface IKeydownEvent { @@ -142,8 +141,7 @@ class SvgBlocker extends Disposable { class WebviewKeyboardHandler extends Disposable { constructor( - private readonly _webview: Electron.WebviewTag, - private readonly _keybindingService: IKeybindingService + private readonly _webview: Electron.WebviewTag ) { super(); @@ -199,23 +197,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); } } @@ -240,8 +229,7 @@ export class WebviewElement extends Disposable { @IInstantiationService instantiationService: IInstantiationService, @IThemeService private readonly _themeService: IThemeService, @IEnvironmentService environmentService: IEnvironmentService, - @IFileService fileService: IFileService, - @IKeybindingService private readonly _keybindingService: IKeybindingService + @IFileService fileService: IFileService ) { super(); this._webview = document.createElement('webview'); @@ -282,7 +270,7 @@ export class WebviewElement extends Disposable { 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}`); @@ -347,14 +335,18 @@ export class WebviewElement extends Disposable { this._send('devtools-opened'); })); - this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this)); + if (!this.options || !this.options.disableFindView) { + this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this)); + } this.style(this._themeService.getTheme()); this._register(this._themeService.onThemeChange(this.style, this)); } public mountTo(parent: HTMLElement) { - parent.appendChild(this._webviewFindWidget.getDomNode()!); + if (this._webviewFindWidget) { + parent.appendChild(this._webviewFindWidget.getDomNode()!); + } parent.appendChild(this._webview); } @@ -365,8 +357,8 @@ export class WebviewElement extends Disposable { } } - this._webview = undefined; - this._webviewFindWidget = undefined; + this._webview = undefined!; + this._webviewFindWidget = undefined!; super.dispose(); } @@ -482,8 +474,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 { @@ -551,11 +544,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/contrib/webview/electron-browser/webviewProtocols.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts index 9cfa61a57ce..4aea4aa25a4 100644 --- a/src/vs/workbench/contrib/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 MIT License. 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/contrib/welcome/page/electron-browser/welcomePage.css b/src/vs/workbench/contrib/welcome/page/electron-browser/welcomePage.css index a24da657f52..756182d9e54 100644 --- a/src/vs/workbench/contrib/welcome/page/electron-browser/welcomePage.css +++ b/src/vs/workbench/contrib/welcome/page/electron-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/contrib/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/electron-browser/welcomePage.ts index 9c36ff502fd..cba26a0edd4 100644 --- a/src/vs/workbench/contrib/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/electron-browser/welcomePage.ts @@ -6,7 +6,7 @@ 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 * as path from 'vs/base/common/path'; import { ICommandService } from 'vs/platform/commands/common/commands'; import * as arrays from 'vs/base/common/arrays'; import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/node/walkThroughInput'; @@ -41,6 +41,7 @@ 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'; used(); @@ -73,9 +74,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); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/electron-browser/walkThroughPart.css b/src/vs/workbench/contrib/welcome/walkThrough/electron-browser/walkThroughPart.css index 2194a2a98c7..0d4122ccdff 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/electron-browser/walkThroughPart.css +++ b/src/vs/workbench/contrib/welcome/walkThrough/electron-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/electron-browser/shell.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts similarity index 100% rename from src/vs/workbench/electron-browser/shell.contribution.ts rename to src/vs/workbench/electron-browser/main.contribution.ts diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index e5d78340abc..bb8d71438ef 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -5,7 +5,8 @@ import * as nls from 'vs/nls'; import * as perf from 'vs/base/common/performance'; -import { Shell } from 'vs/workbench/electron-browser/shell'; +import { Workbench } from 'vs/workbench/electron-browser/workbench'; +import { ElectronWindow } from 'vs/workbench/electron-browser/window'; import * as browser from 'vs/base/browser/browser'; import { domContentLoaded } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -42,250 +43,285 @@ 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 { basename } from 'vs/base/common/path'; import { createHash } from 'crypto'; import { IdleValue } from 'vs/base/common/async'; import { setGlobalLeakWarningThreshold } from 'vs/base/common/event'; 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 { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { Disposable } from 'vs/base/common/lifecycle'; -gracefulFs.gracefulify(fs); // enable gracefulFs +export class CodeWindow extends Disposable { -export function startup(configuration: IWindowConfiguration): Promise { + constructor(private readonly configuration: IWindowConfiguration) { + super(); - // Massage configuration file URIs - revive(configuration); - - // 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); - } - if (workbench.workspace) { - workbench.workspace = reviveWorkspaceIdentifier(workbench.workspace); + 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 + perf.importEntries(this.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(!!this.configuration.fullscreen); + browser.setAccessibilitySupport(this.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 + }; + })); + } + + 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 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); + } + }); + } + }); + } - const environmentService = new EnvironmentService(configuration, configuration.execPath); + open(): Promise { + const mainProcessClient = this._register(new ElectronIPCClient(`window:${this.configuration.windowId}`)); - 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 this.initServices(mainProcessClient).then(services => { return domContentLoaded().then(() => { perf.mark('willStartWorkbench'); - // Create Shell - const shell = new Shell(document.body, { - contextService: workspaceService, - configurationService: workspaceService, - environmentService, - logService, - storageService - }, mainServices, mainProcessClient, configuration); + const instantiationService = new InstantiationService(services, true); - // Gracefully Shutdown Storage - shell.onWillShutdown(event => { - event.join(storageService.close()); - }); + // Create Workbench + const workbench: Workbench = instantiationService.createInstance( + Workbench, + document.body, + this.configuration, + services, + mainProcessClient + ); - // Open Shell - shell.open(); + // Workbench Lifecycle + this._register(workbench.onShutdown(() => this.dispose())); + this._register(workbench.onWillShutdown(event => event.join((services.get(IStorageService) as StorageService).close()))); + + // Startup + workbench.startup(); + + // Window + this._register(instantiationService.createInstance(ElectronWindow)); // 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)))); + onUnexpectedError(new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err)))); } } }); }); }); - }); -} - -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(undefined); - if (configuration.folderUri) { - workspaceInitializationPayload = resolveSingleFolderWorkspaceInitializationPayload(configuration.folderUri); + private initServices(mainProcessClient: ElectronIPCClient): Promise { + const serviceCollection = new ServiceCollection(); + + // Windows Channel + const windowsChannel = mainProcessClient.getChannel('windows'); + serviceCollection.set(IWindowsService, new WindowsChannelClient(windowsChannel)); + + // Update Channel + const updateChannel = mainProcessClient.getChannel('update'); + serviceCollection.set(IUpdateService, new SyncDescriptor(UpdateChannelClient, [updateChannel])); + + // URL Channel + const urlChannel = mainProcessClient.getChannel('url'); + const mainUrlService = new URLServiceChannelClient(urlChannel); + const urlService = new RelayURLService(mainUrlService); + serviceCollection.set(IURLService, urlService); + + // URLHandler Channel + const urlHandlerChannel = new URLHandlerChannel(urlService); + mainProcessClient.registerChannel('urlHandler', urlHandlerChannel); + + // Issue Channel + const issueChannel = mainProcessClient.getChannel('issue'); + serviceCollection.set(IIssueService, new SyncDescriptor(IssueChannelClient, [issueChannel])); + + // Menubar Channel + const menubarChannel = mainProcessClient.getChannel('menubar'); + serviceCollection.set(IMenubarService, new SyncDescriptor(MenubarChannelClient, [menubarChannel])); + + // Workspaces Channel + const workspacesChannel = mainProcessClient.getChannel('workspaces'); + serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel)); + + // Environment + const environmentService = new EnvironmentService(this.configuration, this.configuration.execPath); + serviceCollection.set(IEnvironmentService, environmentService); + + // Log + const logService = this._register(this.createLogService(mainProcessClient, environmentService)); + serviceCollection.set(ILogService, logService); + + // Resolve a workspace payload that we can get the workspace ID from + return this.resolveWorkspaceInitializationPayload(environmentService).then(payload => { + + return Promise.all([ + + // Create and initialize workspace/configuration service + this.createWorkspaceService(payload, environmentService, logService), + + // Create and initialize storage service + this.createStorageService(payload, environmentService, logService, mainProcessClient) + ]).then(services => { + serviceCollection.set(IWorkspaceContextService, services[0]); + serviceCollection.set(IConfigurationService, services[0]); + serviceCollection.set(IStorageService, services[1]); + + return serviceCollection; + }); + }); } - return workspaceInitializationPayload.then(payload => { + private resolveWorkspaceInitializationPayload(environmentService: EnvironmentService): Promise { - // 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')); - } - - payload = { id } as IEmptyWorkspaceInitializationPayload; + // Multi-root workspace + if (this.configuration.workspace) { + return Promise.resolve(this.configuration.workspace as IMultiFolderWorkspaceInitializationPayload); } - 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 | undefined; - 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(); - } + // Single-folder workspace + let workspaceInitializationPayload: Promise = Promise.resolve(undefined); + if (this.configuration.folderUri) { + workspaceInitializationPayload = this.resolveSingleFolderWorkspaceInitializationPayload(this.configuration.folderUri); } - // 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 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; + } + + 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 (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(); + } + } + + // 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, 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; + }); + } + + private createLogService(mainProcessClient: ElectronIPCClient, 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(mainProcessClient.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 window = new CodeWindow(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 window.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 1162363e9f8..00000000000 --- a/src/vs/workbench/electron-browser/media/shell.css +++ /dev/null @@ -1,195 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. 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 { font-family: -apple-system, BlinkMacSystemFont, sans-serif; } -.monaco-shell.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; } -.monaco-shell.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; } -.monaco-shell.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; } -.monaco-shell.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; } -/* windows */ -.monaco-shell.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; } -.monaco-shell.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; } -.monaco-shell.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; } -.monaco-shell.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Meiryo", sans-serif; } -.monaco-shell.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; } -/* linux */ -.monaco-shell.linux { font-family: "Ubuntu", "Droid Sans", sans-serif; } -.monaco-shell.linux:lang(zh-Hans) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } -.monaco-shell.linux:lang(zh-Hant) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; } -.monaco-shell.linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } -.monaco-shell.linux:lang(ko) { 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 951d0972276..00000000000 --- 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 MIT License. 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 74c2b6751e6..00000000000 --- a/src/vs/workbench/electron-browser/resources.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. 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.ts b/src/vs/workbench/electron-browser/shell.ts deleted file mode 100644 index 30a43711c8b..00000000000 --- a/src/vs/workbench/electron-browser/shell.ts +++ /dev/null @@ -1,614 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. 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/editor/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 { 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 } 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 { 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/workbench/services/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'; - -/** - * 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); - }, 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'); - } - - - 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)); - - // Telemetry - 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)); - } else { - this.telemetryService = NullTelemetryService; - } - - serviceCollection.set(ITelemetryService, this.telemetryService); - this._register(configurationTelemetry(this.telemetryService, this.configurationService)); - - 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(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(); - } - } - - 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 be8f0488a12..1f243734b82 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -33,7 +33,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { IDisposable, dispose } 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 { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; import { AccessibilitySupport, isRootUser, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; import product from 'vs/platform/node/product'; import pkg from 'vs/platform/node/package'; diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 3b279295ead..f4cfb45120b 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -3,24 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/workbench'; +import 'vs/workbench/browser/style'; import { localize } from 'vs/nls'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import * as DOM from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, addClasses, scheduleAtNextAnimationFrame, addClass, removeClass, trackFocus, isAncestor, getClientArea, position, size } 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 { getZoomLevel, onDidChangeFullscreen, isFullscreen, getZoomFactor } from 'vs/base/browser/browser'; +import { mark } from 'vs/base/common/performance'; +import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; 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 { isWindows, isLinux, isMacintosh, language } 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'; @@ -36,52 +34,41 @@ 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 { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; 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 { LifecyclePhase, StartupKind, ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWindowService, IWindowConfiguration, IPath, MenuBarVisibility, getTitleBarStyle, IWindowsService } 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'; @@ -97,23 +84,108 @@ import { NotificationsAlerts } from 'vs/workbench/browser/parts/notifications/no 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/editor/common/editorGroupsService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; -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 { RemoteFileDialogService } from 'vs/workbench/services/dialogs/electron-browser/dialogService'; -import { LogStorageAction } from 'vs/platform/storage/node/storageService'; +import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; 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'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +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 { ILabelService } from 'vs/platform/label/common/label'; +import { LabelService } from 'vs/workbench/services/label/common/labelService'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; +import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; +import { combinedAppender, LogAppender, NullTelemetryService, configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; +import { IDownloadService } from 'vs/platform/download/common/download'; +import { IExtensionGalleryService, IExtensionManagementServerService, IExtensionManagementService, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { CommandService } from 'vs/workbench/services/commands/common/commandService'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { MarkerService } from 'vs/platform/markers/common/markerService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { WorkbenchModeServiceImpl } from 'vs/workbench/services/mode/common/workbenchModeService'; +import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; +import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { ISearchService, ISearchHistoryService } from 'vs/workbench/services/search/common/search'; +import { SearchHistoryService } from 'vs/workbench/services/search/common/searchHistoryService'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { CodeEditorService } from 'vs/workbench/services/editor/browser/codeEditorService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { OpenerService } from 'vs/editor/browser/services/openerService'; +import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { HistoryService } from 'vs/workbench/services/history/browser/history'; +import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; + +// import@node +import product from 'vs/platform/node/product'; +import pkg from 'vs/platform/node/package'; +import { BackupFileService, InMemoryBackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; +import { WorkspaceService, DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationService'; +import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; +import { WorkspaceEditingService } from 'vs/workbench/services/workspace/node/workspaceEditingService'; +import { IPCClient, getDelayedChannel } from 'vs/base/parts/ipc/node/ipc'; +import { LogStorageAction } from 'vs/platform/storage/node/storageService'; +import { HashService } from 'vs/workbench/services/hash/node/hashService'; +import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; +import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; +import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; +import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties'; +import { IRequestService } from 'vs/platform/request/node/request'; +import { RequestService } from 'vs/platform/request/node/requestService'; +import { DownloadService } from 'vs/platform/download/node/downloadService'; +import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/node/remoteAgentService'; +import { DownloadServiceChannel } from 'vs/platform/download/node/downloadIpc'; +import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc'; +import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc'; +import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService'; +import { MultiExtensionManagementService } from 'vs/workbench/services/extensionManagement/node/multiExtensionManagement'; +import { SearchService } from 'vs/workbench/services/search/node/searchService'; +import { IntegrityServiceImpl } from 'vs/workbench/services/integrity/node/integrityServiceImpl'; +import { LocalizationsChannelClient } from 'vs/platform/localizations/node/localizationsIpc'; + +// import@electron-browser +import { ContextMenuService as NativeContextMenuService } from 'vs/workbench/services/contextview/electron-browser/contextmenuService'; +import { WorkbenchKeybindingService } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; +import { RemoteFileService } from 'vs/workbench/services/files/electron-browser/remoteFileService'; +import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; +import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; +import { ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; +import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver'; +import { IExtensionUrlHandler, ExtensionUrlHandler } from 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; +import { WorkbenchThemeService } from 'vs/workbench/services/themes/browser/workbenchThemeService'; +import { DialogService, FileDialogService } from 'vs/workbench/services/dialogs/electron-browser/dialogService'; +import { ShowPreviousWindowTab, MoveWindowTabToNewWindow, MergeAllWindowTabs, ShowNextWindowTab, ToggleWindowTabsBar, NewWindowTab, OpenRecentAction, ReloadWindowAction, ReloadWindowWithExtensionsDisabledAction } from 'vs/workbench/electron-browser/actions/windowActions'; +import { IBroadcastService, BroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService'; +import { WindowService } from 'vs/platform/windows/electron-browser/windowService'; +import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService'; +import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; +import { ExtensionService } from 'vs/workbench/services/extensions/electron-browser/extensionService'; +import { TextResourcePropertiesService } from 'vs/workbench/services/textfile/electron-browser/textResourcePropertiesService'; +import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; +import { TextMateService } from 'vs/workbench/services/textMate/electron-browser/TMSyntax'; interface WorkbenchParams { configuration: IWindowConfiguration; @@ -130,13 +202,12 @@ interface IZenModeSettings { restore: boolean; } -export interface IWorkbenchStartedInfo { +interface IWorkbenchStartedInfo { customKeybindingsCount: number; pinnedViewlets: string[]; restoredViewlet: string; restoredEditorsCount: number; } - type FontAliasingOption = 'default' | 'antialiased' | 'none' | 'auto'; const fontAliasingValues: FontAliasingOption[] = ['antialiased', 'none', 'auto']; @@ -189,8 +260,17 @@ export class Workbench extends Disposable implements IPartService { private static readonly closeWhenEmptyConfigurationKey = 'window.closeWhenEmpty'; private static readonly fontAliasingConfigurationKey = 'workbench.fontAliasing'; + 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; } + _serviceBrand: any; + private previousErrorValue: string; + private previousErrorTime: number = 0; + private workbenchParams: WorkbenchParams; private workbench: HTMLElement; private workbenchStarted: boolean; @@ -203,6 +283,11 @@ export class Workbench extends Disposable implements IPartService { private contextKeyService: IContextKeyService; private keybindingService: IKeybindingService; private backupFileService: IBackupFileService; + private notificationService: NotificationService; + private themeService: WorkbenchThemeService; + private telemetryService: ITelemetryService; + private windowService: IWindowService; + private lifecycleService: LifecycleService; private fileService: IFileService; private quickInput: QuickInputService; @@ -254,30 +339,92 @@ export class Workbench extends Disposable implements IPartService { 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 + @ILogService private readonly logService: ILogService, + @IWindowsService private readonly windowsService: IWindowsService ) { super(); this.workbenchParams = { configuration, serviceCollection }; - this.hasInitialFilesToOpen = + this.hasInitialFilesToOpen = !!( (configuration.filesToCreate && configuration.filesToCreate.length > 0) || (configuration.filesToOpen && configuration.filesToOpen.length > 0) || - (configuration.filesToDiff && configuration.filesToDiff.length > 0); + (configuration.filesToDiff && configuration.filesToDiff.length > 0)); + + this.registerErrorHandler(); } - startup(): Promise { + private registerErrorHandler(): 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)); + } + + private handleUnexpectedError(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); + } + } + + startup(): Promise { + try { + return this.doStartup().then(undefined, error => this.logService.error(toErrorMessage(error, true))); + } catch (error) { + this.logService.error(toErrorMessage(error, true)); + + throw error; // rethrow because this is a critical issue we cannot handle properly here + } + } + + private doStartup(): Promise { this.workbenchStarted = true; + // Logging + this.logService.trace('workbench configuration', JSON.stringify(this.configuration)); + + // ARIA + setARIAContainer(document.body); + + // Warm up font cache information before building up too many dom elements + restoreFontInfo(this.storageService); + readFontInfo(BareFontInfo.createFromRawSettings(this.configurationService.getValue('editor'), getZoomLevel())); + this._register(this.storageService.onWillSaveState(() => { + saveFontInfo(this.storageService); // Keep font info for next startup around + })); + // Create Workbench Container this.createWorkbench(); @@ -302,11 +449,23 @@ export class Workbench extends Disposable implements IPartService { // Workbench Layout this.createWorkbenchLayout(); + // Layout + this.layout(); + // Driver if (this.environmentService.driverHandle) { registerWindowDriver(this.mainProcessClient, this.configuration.windowId, this.instantiationService).then(disposable => this._register(disposable)); } + // 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); + }); + // Restore Parts return this.restoreParts(); } @@ -314,11 +473,8 @@ export class Workbench extends Disposable implements IPartService { 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 - })); + addClasses(this.workbench, 'monaco-workbench', isWindows ? 'windows' : isLinux ? 'linux' : 'mac'); } private createGlobalActions(): void { @@ -347,12 +503,159 @@ export class Workbench extends Disposable implements IPartService { private initServices(): void { const { serviceCollection } = this.workbenchParams; - // Services we contribute + // Parts serviceCollection.set(IPartService, this); + // Labels + serviceCollection.set(ILabelService, new SyncDescriptor(LabelService, undefined, true)); + // Clipboard serviceCollection.set(IClipboardService, new SyncDescriptor(ClipboardService)); + // Broadcast + serviceCollection.set(IBroadcastService, new SyncDescriptor(BroadcastService, [this.configuration.windowId])); + + // Notifications + this.notificationService = new NotificationService(); + serviceCollection.set(INotificationService, this.notificationService); + + // Window + this.windowService = this.instantiationService.createInstance(WindowService, this.configuration); + serviceCollection.set(IWindowService, this.windowService); + + // Shared Process + const sharedProcess = this.windowsService.whenSharedProcessReady() + .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${this.configuration.windowId}`)) + .then(client => { + client.registerChannel('dialog', this.instantiationService.createInstance(DialogChannel)); + + return client; + }); + + // Telemetry + 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(this.instantiationService.createInstance(TelemetryService, config)); + this._register(new ErrorTelemetry(this.telemetryService)); + } else { + this.telemetryService = NullTelemetryService; + } + + serviceCollection.set(ITelemetryService, this.telemetryService); + this._register(configurationTelemetry(this.telemetryService, this.configurationService)); + + // Dialogs + serviceCollection.set(IDialogService, this.instantiationService.createInstance(DialogService)); + + // Lifecycle + this.lifecycleService = this.instantiationService.createInstance(LifecycleService); + this.lifecycleService.phase = LifecyclePhase.Ready; // Set lifecycle phase to `Ready` + + serviceCollection.set(ILifecycleService, this.lifecycleService); + + this._register(this.lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event))); + this._register(this.lifecycleService.onShutdown(() => { + this._onShutdown.fire(); + this.dispose(); + })); + + // Request Service + serviceCollection.set(IRequestService, new SyncDescriptor(RequestService, undefined, true)); + + // Download Service + serviceCollection.set(IDownloadService, new SyncDescriptor(DownloadService, undefined, true)); + + // Extension Gallery + serviceCollection.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService, undefined, true)); + + // Remote Resolver + const remoteAuthorityResolverService = new RemoteAuthorityResolverService(); + serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); + + // Remote Agent + const remoteAgentService = new RemoteAgentService(this.configuration, this.notificationService, this.environmentService, remoteAuthorityResolverService); + serviceCollection.set(IRemoteAgentService, remoteAgentService); + + const remoteAgentConnection = remoteAgentService.getConnection(); + if (remoteAgentConnection) { + remoteAgentConnection.registerChannel('dialog', this.instantiationService.createInstance(DialogChannel)); + remoteAgentConnection.registerChannel('download', new DownloadServiceChannel()); + remoteAgentConnection.registerChannel('loglevel', new LogLevelSetterChannel(this.logService)); + } + + // Extensions Management + 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)); + + // Extension Enablement + const extensionEnablementService = this._register(this.instantiationService.createInstance(ExtensionEnablementService)); + serviceCollection.set(IExtensionEnablementService, extensionEnablementService); + + // Extensions + serviceCollection.set(IExtensionService, this.instantiationService.createInstance(ExtensionService)); + + // Theming + this.themeService = this.instantiationService.createInstance(WorkbenchThemeService, document.body); + serviceCollection.set(IWorkbenchThemeService, this.themeService); + + // Commands + serviceCollection.set(ICommandService, new SyncDescriptor(CommandService, undefined, true)); + + // Markers + serviceCollection.set(IMarkerService, new SyncDescriptor(MarkerService, undefined, true)); + + // Editor Mode + serviceCollection.set(IModeService, new SyncDescriptor(WorkbenchModeServiceImpl)); + + // Text Resource Config + serviceCollection.set(ITextResourceConfigurationService, new SyncDescriptor(TextResourceConfigurationService)); + + // Text Resource Properties + serviceCollection.set(ITextResourcePropertiesService, new SyncDescriptor(TextResourcePropertiesService)); + + // Editor Models + serviceCollection.set(IModelService, new SyncDescriptor(ModelServiceImpl, undefined, true)); + + // Marker Decorations + serviceCollection.set(IMarkerDecorationsService, new SyncDescriptor(MarkerDecorationsService)); + + // Editor Worker + serviceCollection.set(IEditorWorkerService, new SyncDescriptor(EditorWorkerServiceImpl)); + + // Untitled Editors + serviceCollection.set(IUntitledEditorService, new SyncDescriptor(UntitledEditorService, undefined, true)); + + // Text Mate + serviceCollection.set(ITextMateService, new SyncDescriptor(TextMateService)); + + // Search + serviceCollection.set(ISearchService, new SyncDescriptor(SearchService)); + serviceCollection.set(ISearchHistoryService, new SyncDescriptor(SearchHistoryService)); + + // Code Editor + serviceCollection.set(ICodeEditorService, new SyncDescriptor(CodeEditorService)); + + // Opener + serviceCollection.set(IOpenerService, new SyncDescriptor(OpenerService, undefined, true)); + + // Integrity + serviceCollection.set(IIntegrityService, new SyncDescriptor(IntegrityServiceImpl)); + + // Localization + const localizationsChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('localizations'))); + serviceCollection.set(ILocalizationsService, new SyncDescriptor(LocalizationsChannelClient, [localizationsChannel])); + + // Hash + serviceCollection.set(IHashService, new SyncDescriptor(HashService, undefined, true)); + // Status bar this.statusbarPart = this.instantiationService.createInstance(StatusbarPart, Identifiers.STATUSBAR_PART); serviceCollection.set(IStatusbarService, this.statusbarPart); @@ -425,7 +728,7 @@ export class Workbench extends Disposable implements IPartService { serviceCollection.set(IHistoryService, new SyncDescriptor(HistoryService)); // File Dialogs - serviceCollection.set(IFileDialogService, new SyncDescriptor(RemoteFileDialogService)); + serviceCollection.set(IFileDialogService, new SyncDescriptor(FileDialogService)); // Backup File Service if (this.workbenchParams.configuration.backupPath) { @@ -484,7 +787,7 @@ export class Workbench extends Disposable implements IPartService { this.instantiationService.createInstance(DefaultConfigurationExportHelper); - this.configurationService.acquireInstantiationService(this.getInstantiationService()); + this.configurationService.acquireInstantiationService(this.instantiationService); } //#region event handling @@ -514,21 +817,46 @@ export class Workbench extends Disposable implements IPartService { this._register(this.configurationService.onDidChangeConfiguration(() => this.onDidUpdateConfiguration())); // Fullscreen changes - this._register(browser.onDidChangeFullscreen(() => this.onFullscreenChanged())); + this._register(onDidChangeFullscreen(() => this.onFullscreenChanged())); // Group changes this._register(this.editorGroupService.onDidAddGroup(() => this.centerEditorLayout(this.shouldCenterLayout))); this._register(this.editorGroupService.onDidRemoveGroup(() => this.centerEditorLayout(this.shouldCenterLayout))); + + // Layout + this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true))); + + // Prevent workbench from scrolling #55456 + this._register(addDisposableListener(this.workbench, EventType.SCROLL, () => { + this.workbench.scrollTop = 0; + })); + } + + 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(); + } } private onFullscreenChanged(): void { // Apply as CSS class - const isFullscreen = browser.isFullscreen(); - if (isFullscreen) { - DOM.addClass(this.workbench, 'fullscreen'); + if (isFullscreen()) { + addClass(this.workbench, 'fullscreen'); } else { - DOM.removeClass(this.workbench, 'fullscreen'); + removeClass(this.workbench, 'fullscreen'); if (this.zenMode.transitionedToFullScreen && this.zenMode.active) { this.toggleZenMode(); @@ -546,7 +874,7 @@ export class Workbench extends Disposable implements IPartService { if (visible !== this.menubarToggled) { this.menubarToggled = visible; - if (browser.isFullscreen() && (this.menubarVisibility === 'toggle' || this.menubarVisibility === 'default')) { + if (isFullscreen() && (this.menubarVisibility === 'toggle' || this.menubarVisibility === 'default')) { this._onTitleBarVisibilityChange.fire(); this.layout(); } @@ -616,7 +944,7 @@ export class Workbench extends Disposable implements IPartService { } const newMenubarVisibility = this.configurationService.getValue(Workbench.menubarVisibilityConfigurationKey); - this.setMenubarVisibility(newMenubarVisibility, skipLayout); + this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); } //#endregion @@ -648,7 +976,7 @@ export class Workbench extends Disposable implements IPartService { const activeControl = this.editorService.activeControl; const visibleEditors = this.editorService.visibleControls; - textCompareEditorActive.set(activeControl && activeControl.getId() === TEXT_DIFF_EDITOR_ID); + textCompareEditorActive.set(!!activeControl && activeControl.getId() === TEXT_DIFF_EDITOR_ID); textCompareEditorVisible.set(visibleEditors.some(control => control.getId() === TEXT_DIFF_EDITOR_ID)); if (visibleEditors.length > 0) { @@ -685,7 +1013,7 @@ export class Workbench extends Disposable implements IPartService { const inputFocused = InputFocusedContext.bindTo(this.contextKeyService); function activeElementIsInput(): boolean { - return document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA'); + return !!document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA'); } function trackInputFocus(): void { @@ -693,7 +1021,7 @@ export class Workbench extends Disposable implements IPartService { inputFocused.set(isInputFocused); if (isInputFocused) { - const tracker = DOM.trackFocus(document.activeElement as HTMLElement); + const tracker = trackFocus(document.activeElement as HTMLElement); Event.once(tracker.onDidBlur)(() => { inputFocused.set(activeElementIsInput()); @@ -702,7 +1030,7 @@ export class Workbench extends Disposable implements IPartService { } } - this._register(DOM.addDisposableListener(window, 'focusin', () => trackInputFocus(), true)); + this._register(addDisposableListener(window, 'focusin', () => trackInputFocus(), true)); const workbenchStateRawContext = new RawContextKey('workbenchState', getWorkbenchStateString(this.configurationService.getWorkbenchState())); const workbenchStateContext = workbenchStateRawContext.bindTo(this.contextKeyService); @@ -732,11 +1060,11 @@ export class Workbench extends Disposable implements IPartService { updateSplitEditorsVerticallyContext(); } - private restoreParts(): Promise { + private restoreParts(): Promise { const restorePromises: Promise[] = []; // Restore Editorpart - perf.mark('willRestoreEditors'); + mark('willRestoreEditors'); restorePromises.push(this.editorPart.whenRestored.then(() => { function openEditors(editors: IResourceEditor[], editorService: IEditorService) { @@ -754,10 +1082,10 @@ export class Workbench extends Disposable implements IPartService { } return editorsToOpen.then(editors => openEditors(editors, this.editorService)); - }).then(() => perf.mark('didRestoreEditors'))); + }).then(() => mark('didRestoreEditors'))); // Restore Sidebar - let viewletIdToRestore: string; + let viewletIdToRestore: string | undefined; if (!this.sideBarHidden) { this.sideBarVisibleContext.set(true); @@ -769,21 +1097,21 @@ export class Workbench extends Disposable implements IPartService { viewletIdToRestore = this.sidebarPart.getDefaultViewletId(); } - perf.mark('willRestoreViewlet'); + mark('willRestoreViewlet'); restorePromises.push(this.sidebarPart.openViewlet(viewletIdToRestore) .then(viewlet => viewlet || this.sidebarPart.openViewlet(this.sidebarPart.getDefaultViewletId())) - .then(() => perf.mark('didRestoreViewlet'))); + .then(() => 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'); + 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'); + mark('didRestorePanel'); } // Restore Zen Mode if active and supported for restore on startup @@ -798,7 +1126,7 @@ export class Workbench extends Disposable implements IPartService { this.centerEditorLayout(true); } - const onRestored = (error?: Error): IWorkbenchStartedInfo => { + const onRestored = (error?: Error): void => { this.workbenchRestored = true; // Set lifecycle phase to `Restored` @@ -813,20 +1141,64 @@ export class Workbench extends Disposable implements IPartService { }, 2500); if (error) { - errors.onUnexpectedError(error); + onUnexpectedError(error); } - return { + this.logStartupTelemetry({ 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 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, + pinnedViewlets: info.pinnedViewlets, + restoredViewlet: info.restoredViewlet, + restoredEditors: info.restoredEditorsCount, + startupKind: this.lifecycleService.startupKind + }); + + // Telemetry: startup metrics + mark('didStartWorkbench'); + } + private shouldRestoreLastOpenedViewlet(): boolean { if (!this.environmentService.isBuilt) { return true; // always restore sidebar when we are in development mode @@ -998,9 +1370,9 @@ export class Workbench extends Disposable implements IPartService { // Adjust CSS if (hidden) { - DOM.addClass(this.workbench, 'nostatusbar'); + addClass(this.workbench, 'nostatusbar'); } else { - DOM.removeClass(this.workbench, 'nostatusbar'); + removeClass(this.workbench, 'nostatusbar'); } // Layout @@ -1027,6 +1399,7 @@ export class Workbench extends Disposable implements IPartService { 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); @@ -1038,7 +1411,6 @@ export class Workbench extends Disposable implements IPartService { this.workbenchGrid = new Grid(this.editorPartView, { proportionalLayout: false }); this.workbench.prepend(this.workbenchGrid.element); - this.layout(); } else { this.workbenchGrid = this.instantiationService.createInstance( WorkbenchLayout, @@ -1064,23 +1436,23 @@ export class Workbench extends Disposable implements IPartService { // Apply sidebar state as CSS class if (this.sideBarHidden) { - DOM.addClass(this.workbench, 'nosidebar'); + addClass(this.workbench, 'nosidebar'); } if (this.panelHidden) { - DOM.addClass(this.workbench, 'nopanel'); + addClass(this.workbench, 'nopanel'); } if (this.statusBarHidden) { - DOM.addClass(this.workbench, 'nostatusbar'); + addClass(this.workbench, 'nostatusbar'); } // Apply font aliasing this.setFontAliasing(this.fontAliasing); // Apply fullscreen state - if (browser.isFullscreen()) { - DOM.addClass(this.workbench, 'fullscreen'); + if (isFullscreen()) { + addClass(this.workbench, 'fullscreen'); } // Create Parts @@ -1142,7 +1514,7 @@ export class Workbench extends Disposable implements IPartService { private createPart(id: string, classes: string[], role: string): HTMLElement { const part = document.createElement('div'); - classes.forEach(clazz => DOM.addClass(part, clazz)); + classes.forEach(clazz => addClass(part, clazz)); part.id = id; part.setAttribute('role', role); @@ -1183,10 +1555,6 @@ export class Workbench extends Disposable implements IPartService { 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); @@ -1227,7 +1595,7 @@ export class Workbench extends Disposable implements IPartService { } const container = this.getContainer(part); - return DOM.isAncestor(activeElement, container); + return isAncestor(activeElement, container); } getContainer(part: Parts): HTMLElement | null { @@ -1254,7 +1622,7 @@ export class Workbench extends Disposable implements IPartService { case Parts.TITLEBAR_PART: if (!this.useCustomTitleBarStyle()) { return false; - } else if (!browser.isFullscreen()) { + } else if (!isFullscreen()) { return true; } else if (isMacintosh) { return false; @@ -1284,13 +1652,13 @@ export class Workbench extends Disposable implements IPartService { 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; + offset = this.titlebarPart.maximumHeight; } else { offset = this.workbenchGrid.partLayoutInfo.titlebar.height; - } - if (isMacintosh || this.menubarVisibility === 'hidden') { - offset /= browser.getZoomFactor(); + if (isMacintosh || this.menubarVisibility === 'hidden') { + offset /= getZoomFactor(); + } } } @@ -1321,7 +1689,7 @@ export class Workbench extends Disposable implements IPartService { if (this.zenMode.active) { const config = this.configurationService.getValue('zenMode'); - toggleFullScreen = !browser.isFullscreen() && config.fullScreen; + toggleFullScreen = !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); @@ -1372,7 +1740,7 @@ export class Workbench extends Disposable implements IPartService { this.editorGroupService.activeGroup.focus(); - toggleFullScreen = this.zenMode.transitionedToFullScreen && browser.isFullscreen(); + toggleFullScreen = this.zenMode.transitionedToFullScreen && isFullscreen(); } this.inZenMode.set(this.zenMode.active); @@ -1487,14 +1855,14 @@ export class Workbench extends Disposable implements IPartService { } } - layout(options?: ILayoutOptions): void { + private 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); + const dimensions = getClientArea(this.container); + position(this.workbench, 0, 0, 0, 0, 'relative'); + size(this.workbench, dimensions.width, dimensions.height); // Layout the grid this.workbenchGrid.layout(dimensions.width, dimensions.height); @@ -1591,9 +1959,9 @@ export class Workbench extends Disposable implements IPartService { // Adjust CSS if (hidden) { - DOM.addClass(this.workbench, 'nosidebar'); + addClass(this.workbench, 'nosidebar'); } else { - DOM.removeClass(this.workbench, 'nosidebar'); + removeClass(this.workbench, 'nosidebar'); } // If sidebar becomes hidden, also hide the current active Viewlet if any @@ -1620,7 +1988,6 @@ export class Workbench extends Disposable implements IPartService { } } - // Remember in settings const defaultHidden = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; if (hidden !== defaultHidden) { @@ -1644,9 +2011,9 @@ export class Workbench extends Disposable implements IPartService { // Adjust CSS if (hidden) { - DOM.addClass(this.workbench, 'nopanel'); + addClass(this.workbench, 'nopanel'); } else { - DOM.removeClass(this.workbench, 'nopanel'); + removeClass(this.workbench, 'nopanel'); } // If panel part becomes hidden, also hide the current active panel if any @@ -1663,7 +2030,6 @@ export class Workbench extends Disposable implements IPartService { } } - // Remember in settings if (!hidden) { this.storageService.store(Workbench.panelHiddenStorageKey, 'false', StorageScope.WORKSPACE); @@ -1722,10 +2088,10 @@ export class Workbench extends Disposable implements IPartService { 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); + removeClass(this.activitybarPart.getContainer(), oldPositionValue); + removeClass(this.sidebarPart.getContainer(), oldPositionValue); + addClass(this.activitybarPart.getContainer(), newPositionValue); + addClass(this.sidebarPart.getContainer(), newPositionValue); // Update Styles this.activitybarPart.updateStyles(); @@ -1758,7 +2124,7 @@ export class Workbench extends Disposable implements IPartService { // Layout if (!skipLayout) { if (this.workbenchGrid instanceof Grid) { - const dimensions = DOM.getClientArea(this.container); + const dimensions = getClientArea(this.container); this.workbenchGrid.layout(dimensions.width, dimensions.height); } else { this.workbenchGrid.layout(); @@ -1790,8 +2156,8 @@ export class Workbench extends Disposable implements IPartService { 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); + removeClass(this.panelPart.getContainer(), oldPositionValue); + addClass(this.panelPart.getContainer(), newPositionValue); // Update Styles this.panelPart.updateStyles(); @@ -1808,4 +2174,6 @@ export class Workbench extends Disposable implements IPartService { this.workbenchGrid.layout(); } } + + //#endregion } diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index ae72e145857..a089102b7f9 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 MIT License. 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'; 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 ca6f648b3cd..e39ddee054b 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 @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as platform from 'vs/base/common/platform'; 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, hashPath } from 'vs/workbench/services/backup/node/backupFileService'; diff --git a/src/vs/platform/broadcast/electron-browser/broadcastService.ts b/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts similarity index 91% rename from src/vs/platform/broadcast/electron-browser/broadcastService.ts rename to src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts index 27d8cfb4e5a..d78d3e3b56e 100644 --- a/src/vs/platform/broadcast/electron-browser/broadcastService.ts +++ b/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts @@ -19,15 +19,16 @@ 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; + _serviceBrand: any; private readonly _onBroadcast: Emitter; + get onBroadcast(): Event { return this._onBroadcast.event; } constructor( private windowId: number, @@ -46,11 +47,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, { diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configuration.ts index e219d2882bc..995a2e3f4b1 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configuration.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { createHash } from 'crypto'; -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 { Event, Emitter } from 'vs/base/common/event'; import * as pfs from 'vs/base/node/pfs'; @@ -14,7 +14,6 @@ import * as collections from 'vs/base/common/collections'; import { Disposable, IDisposable, dispose } 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'; @@ -23,7 +22,7 @@ import * as extfs from 'vs/base/node/extfs'; import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { relative } from 'path'; +import { extname } 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'; @@ -308,8 +307,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 = extpath.joinWithSlashes(this.environmentService.userDataPath, 'CachedConfigurations', 'workspaces', workspaceIdentifier.id); + this.cachedConfigurationPath = extpath.joinWithSlashes(this.cachedWorkspacePath, 'workspace.json'); } } @@ -497,7 +496,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) { @@ -530,14 +529,8 @@ 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)); - } + if (resources.isEqualOrParent(resource, this.folderConfigurationPath)) { + return resources.relativePath(this.folderConfigurationPath, resource); } return null; } @@ -559,8 +552,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 = extpath.joinWithSlashes(environmentService.userDataPath, 'CachedConfigurations', 'folders', createHash('md5').update(extpath.joinWithSlashes(folder.path, configFolderRelativePath)).digest('hex')); + this.cachedConfigurationPath = extpath.joinWithSlashes(this.cachedFolderPath, 'configuration.json'); this.configurationModel = new ConfigurationModel(); } diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index 7b595d32955..6943c5b3819 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -474,7 +474,7 @@ export class ConfigurationEditingService { private isWorkspaceConfigurationResource(resource: URI): 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 { diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 82885fa7b0c..ca8a08e4842 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -22,7 +22,7 @@ import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationC import { IWorkspaceConfigurationService, 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, massageFolderPathForWorkspace } 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'; @@ -31,7 +31,6 @@ import { ConfigurationEditingService } from 'vs/workbench/services/configuration 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 { UserConfiguration } from 'vs/platform/configuration/node/configuration'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { localize } from 'vs/nls'; 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 2abf49537b3..5127f5f2068 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 MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { join } from 'vs/base/common/paths'; +import { joinWithSlashes } from 'vs/base/common/extpath'; 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'; @@ -123,76 +123,76 @@ suite('WorkspaceConfigurationChangeEvent', () => { assert.ok(testObject.affectsConfiguration('window.zoomLevel')); assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file('folder1'))); - assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiguration('window.zoomLevel', URI.file(joinWithSlashes('folder1', 'file1')))); assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file1'))); assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file('file2'))); - assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(join('folder3', 'file3')))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(joinWithSlashes('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('window.zoomLevel', URI.file(joinWithSlashes('folder3', 'file3')))); assert.ok(testObject.affectsConfiguration('window.restoreFullscreen')); - assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file(joinWithSlashes('folder1', 'file1')))); assert.ok(testObject.affectsConfiguration('window.restoreFullscreen', URI.file('folder1'))); assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file1'))); assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file('file2'))); - assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder2', 'file2')))); - assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(join('folder3', 'file3')))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(joinWithSlashes('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('window.restoreFullscreen', URI.file(joinWithSlashes('folder3', 'file3')))); assert.ok(testObject.affectsConfiguration('window.restoreWindows')); assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiguration('window.restoreWindows', URI.file(joinWithSlashes('folder2', 'file2')))); assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file('file2'))); - assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder1', 'file1')))); - assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(join('folder3', 'file3')))); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(joinWithSlashes('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('window.restoreWindows', URI.file(joinWithSlashes('folder3', 'file3')))); assert.ok(testObject.affectsConfiguration('window.title')); assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder1'))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file(joinWithSlashes('folder1', 'file1')))); assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file(joinWithSlashes('folder2', 'file2')))); assert.ok(testObject.affectsConfiguration('window.title', URI.file('folder3'))); - assert.ok(testObject.affectsConfiguration('window.title', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiguration('window.title', URI.file(joinWithSlashes('folder3', 'file3')))); assert.ok(testObject.affectsConfiguration('window.title', URI.file('file1'))); assert.ok(testObject.affectsConfiguration('window.title', URI.file('file2'))); assert.ok(testObject.affectsConfiguration('window.title', URI.file('file3'))); assert.ok(testObject.affectsConfiguration('window')); assert.ok(testObject.affectsConfiguration('window', URI.file('folder1'))); - assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder1', 'file1')))); + assert.ok(testObject.affectsConfiguration('window', URI.file(joinWithSlashes('folder1', 'file1')))); assert.ok(testObject.affectsConfiguration('window', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiguration('window', URI.file(joinWithSlashes('folder2', 'file2')))); assert.ok(testObject.affectsConfiguration('window', URI.file('folder3'))); - assert.ok(testObject.affectsConfiguration('window', URI.file(join('folder3', 'file3')))); + assert.ok(testObject.affectsConfiguration('window', URI.file(joinWithSlashes('folder3', 'file3')))); assert.ok(testObject.affectsConfiguration('window', URI.file('file1'))); assert.ok(testObject.affectsConfiguration('window', URI.file('file2'))); assert.ok(testObject.affectsConfiguration('window', URI.file('file3'))); assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview')); assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(joinWithSlashes('folder2', 'file2')))); assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file(joinWithSlashes('folder1', 'file1')))); assert.ok(!testObject.affectsConfiguration('workbench.editor.enablePreview', URI.file('folder3'))); assert.ok(testObject.affectsConfiguration('workbench.editor')); assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiguration('workbench.editor', URI.file(joinWithSlashes('folder2', 'file2')))); assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file(joinWithSlashes('folder1', 'file1')))); assert.ok(!testObject.affectsConfiguration('workbench.editor', URI.file('folder3'))); assert.ok(testObject.affectsConfiguration('workbench')); assert.ok(testObject.affectsConfiguration('workbench', URI.file('folder2'))); - assert.ok(testObject.affectsConfiguration('workbench', URI.file(join('folder2', 'file2')))); + assert.ok(testObject.affectsConfiguration('workbench', URI.file(joinWithSlashes('folder2', 'file2')))); assert.ok(!testObject.affectsConfiguration('workbench', URI.file('folder1'))); assert.ok(!testObject.affectsConfiguration('workbench', URI.file('folder3'))); assert.ok(!testObject.affectsConfiguration('files')); assert.ok(!testObject.affectsConfiguration('files', URI.file('folder1'))); - assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder1', 'file1')))); + assert.ok(!testObject.affectsConfiguration('files', URI.file(joinWithSlashes('folder1', 'file1')))); assert.ok(!testObject.affectsConfiguration('files', URI.file('folder2'))); - assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder2', 'file2')))); + assert.ok(!testObject.affectsConfiguration('files', URI.file(joinWithSlashes('folder2', 'file2')))); assert.ok(!testObject.affectsConfiguration('files', URI.file('folder3'))); - assert.ok(!testObject.affectsConfiguration('files', URI.file(join('folder3', 'file3')))); + assert.ok(!testObject.affectsConfiguration('files', URI.file(joinWithSlashes('folder3', 'file3')))); }); }); 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 857ba62339e..09fbb2c580b 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'; 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 d6e5d442fcf..19df41013d8 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'; @@ -36,7 +36,7 @@ import { Uri } from 'vscode'; import { createHash } from 'crypto'; import { Emitter, Event } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; -import { fsPath } from 'vs/base/common/resources'; +import { originalFSPath } from 'vs/base/common/resources'; import { isLinux } from 'vs/base/common/platform'; import { IWorkspaceIdentifier } from 'vs/workbench/services/configuration/node/configuration'; @@ -1246,7 +1246,7 @@ suite('WorkspaceConfigurationService-Multiroot', () => { }); function getWorkspaceId(configPath: URI): string { - let workspaceConfigPath = configPath.scheme === Schemas.file ? fsPath(configPath) : configPath.toString(); + let workspaceConfigPath = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString(); if (!isLinux) { workspaceConfigPath = workspaceConfigPath.toLowerCase(); // sanitize for platform file system } diff --git a/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts similarity index 98% rename from src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts rename to src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index cdc76396f9d..24b3264415f 100644 --- a/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -5,7 +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 path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import * as Types from 'vs/base/common/types'; import { Schemas } from 'vs/base/common/network'; @@ -16,7 +16,7 @@ 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'; @@ -58,7 +58,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; diff --git a/src/vs/workbench/services/configurationResolver/node/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts similarity index 97% rename from src/vs/workbench/services/configurationResolver/node/variableResolver.ts rename to src/vs/workbench/services/configurationResolver/common/variableResolver.ts index 7e86f0e4185..88d1c2de2f0 100644 --- a/src/vs/workbench/services/configurationResolver/node/variableResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -3,11 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as paths from 'vs/base/common/paths'; +import * as paths from 'vs/base/common/path'; 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'; @@ -116,12 +115,12 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe 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; + let variableName: string | undefined; + let resolvedValue: string | undefined; const replaced = value.replace(AbstractVariableResolverService.VARIABLE_REGEXP, (match: string, variable: string) => { variableName = variable; - let argument: string; + let argument: string | undefined; const parts = variable.split(':'); if (parts && parts.length > 1) { variable = parts[0]; @@ -237,7 +236,7 @@ export class AbstractVariableResolverService implements IConfigurationResolverSe case 'relativeFile': if (folderUri) { - return resolvedValue = paths.normalize(relative(folderUri.fsPath, filePath)); + return resolvedValue = paths.normalize(paths.relative(folderUri.fsPath, filePath)); } return resolvedValue = filePath; 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 2d6bf3ad7f5..23106fa0871 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,7 +9,7 @@ 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 { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -19,7 +19,7 @@ import { CancellationToken } from 'vscode'; import * as Types from 'vs/base/common/types'; 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 mockCommandService: MockCommandService; let editorService: TestEditorService; @@ -45,46 +45,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 +93,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 +102,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' }, @@ -256,7 +255,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 +284,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 +317,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 +348,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 +373,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 +400,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 +427,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 +455,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 +489,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 { diff --git a/src/vs/workbench/services/decorations/browser/decorations.ts b/src/vs/workbench/services/decorations/browser/decorations.ts index 73cfd4cce86..063ec4ee52b 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 2e3b151afc9..e0ef2f3814f 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -301,7 +301,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); diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index 4b80e39c48a..68d1589ebca 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import product from 'vs/platform/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, INativeOpenDialogOptions, OpenDialogOptions, IURIToOpen, FileFilter } 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 { ILogService } from 'vs/platform/log/common/log'; @@ -15,12 +15,12 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/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 { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { Schemas } from 'vs/base/common/network'; import * as resources from 'vs/base/common/resources'; -import { isParent } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { RemoteFileDialog } from 'vs/workbench/services/dialogs/electron-browser/remoteFileDialog'; +import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; interface IMassagedMessageBoxOptions { @@ -164,10 +164,10 @@ export class FileDialogService implements IFileDialogService { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IHistoryService private readonly historyService: IHistoryService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IInstantiationService private readonly instantiationService: IInstantiationService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { } - defaultFilePath(schemeFilter: string): URI | undefined { + defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { // Check for last active file first... let candidate = this.historyService.getLastActiveFile(schemeFilter); @@ -180,7 +180,7 @@ export class FileDialogService implements IFileDialogService { return candidate && resources.dirname(candidate) || undefined; } - defaultFolderPath(schemeFilter: string): URI | undefined { + defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { // Check for last active file root first... let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); @@ -193,12 +193,12 @@ export class FileDialogService implements IFileDialogService { return candidate && resources.dirname(candidate) || undefined; } - defaultWorkspacePath(schemeFilter: string): URI | undefined { + defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { // Check for current workspace config file first... - if (schemeFilter === Schemas.file && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { + if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { const configuration = this.contextService.getWorkspace().configuration; - if (configuration && !isUntitledWorkspace(configuration.fsPath, this.environmentService)) { + if (configuration && !isUntitledWorkspace(configuration, this.environmentService)) { return resources.dirname(configuration) || undefined; } } @@ -218,36 +218,66 @@ export class FileDialogService implements IFileDialogService { } pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { - const defaultUri = options.defaultUri; - if (!defaultUri) { - options.defaultUri = this.defaultFilePath(Schemas.file); + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFilePath(schema); + } + + if (schema !== Schemas.file) { + const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); + const availableFileSystems = [schema, Schemas.file]; // 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 defaultUri = options.defaultUri; - if (!defaultUri) { - options.defaultUri = this.defaultFilePath(Schemas.file); + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFilePath(schema); + } + + if (schema !== Schemas.file) { + const title = nls.localize('openFile.title', 'Open File'); + const availableFileSystems = [schema, Schemas.file]; // 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 defaultUri = options.defaultUri; - if (!defaultUri) { - options.defaultUri = this.defaultFolderPath(Schemas.file); + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFolderPath(schema); + } + + if (schema !== Schemas.file) { + const title = nls.localize('openFolder.title', 'Open Folder'); + const availableFileSystems = [schema, Schemas.file]; // 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 defaultUri = options.defaultUri; - if (!defaultUri) { - options.defaultUri = this.defaultWorkspacePath(Schemas.file); + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultWorkspacePath(schema); + } + + if (schema !== Schemas.file) { + const title = nls.localize('openWorkspace.title', 'Open Workspace'); + const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; + const availableFileSystems = [schema, Schemas.file]; // 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)); @@ -263,9 +293,10 @@ export class FileDialogService implements IFileDialogService { } 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.')); + const schema = this.getFileSystemSchema(options); + if (schema !== Schemas.file) { + options.availableFileSystems = [schema, Schemas.file]; // always allow file as well + return this.saveRemoteResource(options); } return this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)).then(result => { @@ -277,17 +308,16 @@ export class FileDialogService implements IFileDialogService { }); } - public showSaveRemoteDialog(options: ISaveDialogOptions): Promise { - const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); - return remoteFileDialog.showSaveDialog(options); - } - 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 schema = this.getFileSystemSchema(options); + if (schema !== Schemas.file) { + 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, @@ -313,33 +343,35 @@ export class FileDialogService implements IFileDialogService { return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined); } - public showOpenRemoteDialog(options: IOpenDialogOptions): Promise { + private pickRemoteResourceAndOpen(options: IOpenDialogOptions, forceNewWindow: boolean, forceOpenWorkspaceAsFile: boolean) { + return this.pickRemoteResource(options).then(urisToOpen => { + if (urisToOpen) { + return this.windowService.openWindow(urisToOpen, { forceNewWindow, forceOpenWorkspaceAsFile }); + } + return void 0; + }); + } + + private pickRemoteResource(options: IOpenDialogOptions): Promise { const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); return remoteFileDialog.showOpenDialog(options); } -} -export class RemoteFileDialogService extends FileDialogService { - - constructor( - @IWindowService windowService: IWindowService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IHistoryService historyService: IHistoryService, - @IEnvironmentService environmentService: IEnvironmentService, - @IInstantiationService instantiationService: IInstantiationService, - ) { - super(windowService, contextService, historyService, environmentService, instantiationService); + private saveRemoteResource(options: ISaveDialogOptions): Promise { + const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); + return remoteFileDialog.showSaveDialog(options); } - public showSaveDialog(options: ISaveDialogOptions): Promise { - const defaultUri = options.defaultUri; - if (defaultUri && defaultUri.scheme === REMOTE_HOST_SCHEME) { - return this.showSaveRemoteDialog(options); - } - return super.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: string, environmentService: IEnvironmentService): boolean { - return isParent(path, environmentService.workspacesHome, !isLinux /* ignore case */); +function isUntitledWorkspace(path: URI, environmentService: IEnvironmentService): boolean { + return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome); } \ No newline at end of file diff --git a/src/vs/workbench/services/dialogs/electron-browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/electron-browser/remoteFileDialog.ts index cb03268fdc2..51c7a6b149e 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/remoteFileDialog.ts @@ -10,12 +10,11 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IQuickInputService, IQuickPickItem, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; import { isWindows } from 'vs/base/common/platform'; -import { ISaveDialogOptions, IOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; +import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { INotificationService } from 'vs/platform/notification/common/notification'; interface FileQuickPickItem extends IQuickPickItem { @@ -28,7 +27,7 @@ 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 fallbackPickerButton = { iconPath: this.getAlternateDialogIcons(), tooltip: 'Use Alternate File System' }; private acceptButton = { iconPath: this.getIcons('accept.svg'), tooltip: 'Select' }; private cancelButton = { iconPath: this.getIcons('cancel.svg'), tooltip: 'Cancel' }; private currentFolder: URI; @@ -43,40 +42,37 @@ export class RemoteFileDialog { @IWindowService private readonly windowService: IWindowService, @ILabelService private readonly labelService: ILabelService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IEditorService private readonly editorService: IEditorService, @INotificationService private readonly notificationService: INotificationService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, ) { this.remoteAuthority = this.windowService.getConfiguration().remoteAuthority; } - public async showOpenDialog(options: IOpenDialogOptions = {}): Promise { - if (!this.remoteAuthority) { - this.notificationService.info(nls.localize('remoteFileDialog.notConnectedToRemote', 'Not connected to a remote.')); - return Promise.resolve(); - } + public async showOpenDialog(options: IOpenDialogOptions = {}): Promise { const defaultUri = options.defaultUri ? options.defaultUri : URI.from({ scheme: REMOTE_HOST_SCHEME, authority: this.remoteAuthority, path: '/' }); + if (!this.remoteFileService.canHandleResource(defaultUri)) { + this.notificationService.info(nls.localize('remoteFileDialog.notConnectedToRemote', 'File system provider for {0} is not available.', defaultUri.toString())); + return Promise.resolve(undefined); + } + const title = nls.localize('remoteFileDialog.openTitle', 'Open File or Folder'); - return this.pickResource({ title, defaultUri, canSelectFiles: true, canSelectFolders: true }).then(async fileFolderUri => { + return this.pickResource({ title, defaultUri, canSelectFiles: true, canSelectFolders: true, availableFileSystems: options.availableFileSystems }).then(async fileFolderUri => { if (fileFolderUri) { const stat = await this.remoteFileService.resolveFile(fileFolderUri); - if (stat.isDirectory) { - return this.windowService.openWindow([{ uri: fileFolderUri, typeHint: 'folder' }]); - } else { - return this.editorService.openEditor({ resource: fileFolderUri }).then(() => { - return Promise.resolve(); - }); - } + return [{ uri: fileFolderUri, typeHint: stat.isDirectory ? 'folder' : 'file' }]; + } - return Promise.resolve(); + return Promise.resolve(undefined); }); } public showSaveDialog(options: ISaveDialogOptions): Promise { - if (!this.remoteAuthority) { - this.notificationService.info(nls.localize('remoteFileDialog.notConnectedToRemote', 'Not connected to a remote.')); + const defaultUri = options.defaultUri ? options.defaultUri : URI.from({ scheme: REMOTE_HOST_SCHEME, authority: this.remoteAuthority, path: '/' }); + if (!this.remoteFileService.canHandleResource(defaultUri)) { + this.notificationService.info(nls.localize('remoteFileDialog.notConnectedToRemote', 'File system provider for {0} is not available.', defaultUri.toString())); return Promise.resolve(undefined); } - const defaultUri = options.defaultUri ? options.defaultUri : URI.from({ scheme: REMOTE_HOST_SCHEME, authority: this.remoteAuthority, path: '/' }); + return new Promise((resolve) => { let saveNameBox = this.quickInputService.createInputBox(); saveNameBox.title = options.title; @@ -87,11 +83,21 @@ export class RemoteFileDialog { saveNameBox.onDidChangeValue(v => { saveNameBox.validationMessage = this.isValidBaseName(v) ? void 0 : nls.localize('remoteFileDialog.error.invalidfilename', 'Not a valid file name'); }); + saveNameBox.buttons = [this.fallbackPickerButton]; + saveNameBox.onDidTriggerButton(button => { + if (button === this.fallbackPickerButton) { + options.availableFileSystems.shift(); + this.fileDialogService.showSaveDialog(options).then(result => { + resolve(result); + }); + } + saveNameBox.dispose(); + }); saveNameBox.onDidAccept(_ => { const name = saveNameBox.value; if (this.isValidBaseName(name)) { saveNameBox.hide(); - this.pickResource({ defaultUri: defaultUri, canSelectFolders: true, title: nls.localize('remoteFileDialogerror.titleFolderPage', 'Folder for \'{0}\'', name) }, { step: 2, totalSteps: 2 }).then(folderUri => { + this.pickResource({ defaultUri: defaultUri, canSelectFolders: true, title: nls.localize('remoteFileDialogerror.titleFolderPage', 'Folder for \'{0}\'', name), availableFileSystems: options.availableFileSystems }, { step: 2, totalSteps: 2 }).then(folderUri => { if (folderUri) { resolve(this.remoteUriFrom(this.remotePathJoin(folderUri, name))); } else { @@ -130,9 +136,19 @@ export class RemoteFileDialog { let isAcceptHandled = false; this.currentFolder = homedir; - this.filePickBox.buttons = [this.acceptButton, this.cancelButton]; + if (options.availableFileSystems.length > 1) { + this.filePickBox.buttons = [this.fallbackPickerButton, this.acceptButton, this.cancelButton]; + } else { + this.filePickBox.buttons = [this.acceptButton, this.cancelButton]; + } this.filePickBox.onDidTriggerButton(button => { - if (button === this.acceptButton) { + if (button === this.fallbackPickerButton) { + options.availableFileSystems.shift(); + isResolved = true; + this.fileDialogService.pickFileAndOpen(options).then(result => { + resolve(result ? result[0] : undefined); + }); + } else if (button === this.acceptButton) { resolve(this.currentFolder); isResolved = true; } @@ -206,20 +222,18 @@ export class RemoteFileDialog { } } - private updateItems(newFolder: URI | null) { - if (newFolder) { - this.currentFolder = newFolder; - this.filePickBox.placeholder = this.labelService.getUriLabel(newFolder, { endWithSeparator: true }); - this.filePickBox.value = ''; - 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 updateItems(newFolder: URI) { + this.currentFolder = newFolder; + this.filePickBox.placeholder = this.labelService.getUriLabel(newFolder, { endWithSeparator: true }); + this.filePickBox.value = ''; + 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 isValidBaseName(name: string): boolean { @@ -257,14 +271,14 @@ export class RemoteFileDialog { private basenameWithTrailingSlash(fullPath: URI): string { const child = this.labelService.getUriLabel(fullPath, { endWithSeparator: true }); - const parent = this.labelService.getUriLabel(resources.dirname(fullPath)!, { endWithSeparator: true }); + const parent = this.labelService.getUriLabel(resources.dirname(fullPath), { endWithSeparator: true }); return child.substring(parent.length); } private createBackItem(currFolder: URI): FileQuickPickItem | null { const parentFolder = resources.dirname(currFolder)!; if (!resources.isEqual(currFolder, parentFolder)) { - return { label: '..', uri: resources.dirname(currFolder)!, isFolder: true }; + return { label: '..', uri: resources.dirname(currFolder), isFolder: true }; } return null; } @@ -318,4 +332,11 @@ export class RemoteFileDialog { dark: URI.parse(require.toUrl(`vs/workbench/services/dialogs/media/dark/${name}`)) }; } + + private getAlternateDialogIcons(): { light: URI, dark: URI } { + return { + dark: URI.parse(require.toUrl(`vs/editor/contrib/suggest/media/Folder_inverse_16x.svg`)), + light: URI.parse(require.toUrl(`vs/editor/contrib/suggest/media/Folder_16x.svg`)) + }; + } } \ 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 693779a1efa..228dfe7afb4 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -15,11 +15,11 @@ 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/editor/common/editorGroupsService'; -import { IResourceEditor, ACTIVE_GROUP_TYPE, SIDE_GROUP_TYPE, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; +import { IResourceEditor, ACTIVE_GROUP_TYPE, SIDE_GROUP_TYPE, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IActiveEditor } 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'; @@ -152,13 +152,13 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - get activeControl(): IEditor { + get activeControl(): IActiveEditor | 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(); @@ -179,10 +179,10 @@ 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 ? activeGroup.activeEditor || undefined : undefined; } get visibleControls(): IEditor[] { @@ -246,7 +246,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } 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') { @@ -381,7 +381,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { 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) { @@ -417,7 +417,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; } } @@ -478,7 +478,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region createInput() - createInput(input: IEditorInputWithOptions | IEditorInput | IResourceEditor): EditorInput { + createInput(input: IEditorInputWithOptions | IEditorInput | IResourceEditor): EditorInput | null { // Typed Editor Input Support (EditorInput) if (input instanceof EditorInput) { @@ -531,7 +531,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) } return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.forceFile) as EditorInput; @@ -578,11 +578,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(); // Do not try to extract any paths from simple untitled editors - if (res.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(res)) { + if (res && res.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(res)) { return input.getName(); } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index 77759b057c8..7564844b33a 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -8,6 +8,7 @@ import { createDecorator, ServiceIdentifier, ServicesAccessor } from 'vs/platfor import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection } from 'vs/workbench/common/editor'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IActiveEditor } from 'vs/workbench/services/editor/common/editorService'; export const IEditorGroupsService = createDecorator('editorGroupsService'); @@ -332,19 +333,19 @@ export interface IEditorGroup { /** * The active control is the currently visible control of the group. */ - readonly activeControl: IEditor; + readonly activeControl: IActiveEditor | 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 +360,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 +380,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 +390,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. diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 462dd7f33a0..18fb4e87975 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -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 IActiveEditor 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: IActiveEditor | 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 @@ -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; } \ No newline at end of file diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index ef4e6423cb7..ca549547ab7 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -41,7 +41,7 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput { constructor(private resource: URI) { super(); } getTypeId() { return 'testEditorInputForEditorGroupService'; } - resolve(): Promise { return Promise.resolve(undefined); } + 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!; } 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 7f2c1932568..af7465c877b 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,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -120,7 +120,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); @@ -260,7 +260,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 { @@ -403,7 +403,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); @@ -604,5 +604,5 @@ function toResource(path: string) { } function toFileResource(self: any, path: string) { - return URI.file(paths.join('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path)); + return URI.file(extpath.joinWithSlashes('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path)); } diff --git a/src/vs/workbench/services/extensionManagement/node/multiExtensionManagement.ts b/src/vs/workbench/services/extensionManagement/node/multiExtensionManagement.ts index d9297d67d42..c13e8ba5594 100644 --- a/src/vs/workbench/services/extensionManagement/node/multiExtensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/node/multiExtensionManagement.ts @@ -9,7 +9,7 @@ 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'; @@ -60,7 +60,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 +136,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 +159,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); } diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index fabecb6b379..d40c975542c 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -89,7 +89,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( diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 29cbfd65c41..75c2d6f8b83 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -5,13 +5,13 @@ 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'; @@ -297,7 +297,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 34e7cbf0a57..56734fd231c 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -21,7 +21,7 @@ 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'; @@ -435,7 +435,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { }, workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { configuration: workspace.configuration || undefined, - folders: workspace.folders, id: workspace.id, name: this._labelService.getWorkspaceLabel(workspace) }, diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts index 852b3130a2e..16ebfa07804 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts @@ -33,12 +33,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/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 02e23141003..d7d69bb07c9 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'; @@ -37,13 +37,13 @@ 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)); @@ -101,7 +101,7 @@ 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._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.getCurrentWindowId()}`)); this._registry = null; this._installedExtensionsReady = new Barrier(); this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; @@ -167,7 +167,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); @@ -856,7 +856,7 @@ export class ExtensionService extends Disposable implements IExtensionService { if (!this._extensionsMessages.has(extensionKey)) { this._extensionsMessages.set(extensionKey, []); } - this._extensionsMessages.get(extensionKey).push({ + this._extensionsMessages.get(extensionKey)!.push({ type: severity, message: message, extensionId: null, diff --git a/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts index 6ad4cac017d..972c560d6b9 100644 --- a/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts @@ -189,8 +189,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 ec9cd8be9e4..6f62be729ad 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostMain.ts @@ -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[] = []; @@ -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); 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()}`; diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 26b4f26d07d..e2b2719a810 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'; diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 9205a62dd1b..628b70814f8 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -8,7 +8,7 @@ import * as https from 'https'; import * as nodeurl from 'url'; import { assign } from 'vs/base/common/objects'; -import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; +import { ExtHostWorkspaceProvider } 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'; @@ -25,7 +25,7 @@ interface ConnectionResult { } export function connectProxyResolver( - extHostWorkspace: ExtHostWorkspace, + extHostWorkspace: ExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extensionService: ExtHostExtensionService, extHostLogService: ExtHostLogService, @@ -39,7 +39,7 @@ export function connectProxyResolver( const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache'. function createProxyAgents( - extHostWorkspace: ExtHostWorkspace, + extHostWorkspace: ExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extHostLogService: ExtHostLogService, mainThreadTelemetry: MainThreadTelemetryShape diff --git a/src/vs/workbench/services/files/electron-browser/encoding.ts b/src/vs/workbench/services/files/electron-browser/encoding.ts index 038d8b86822..b600d76c684 100644 --- a/src/vs/workbench/services/files/electron-browser/encoding.ts +++ b/src/vs/workbench/services/files/electron-browser/encoding.ts @@ -8,7 +8,7 @@ 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 { 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'; diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index 1a66c56808e..ab16a378ff3 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. 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 { 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'; @@ -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(parseInt(this.environmentService.args['max-memory']) * 1024 * 1024 || 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 @@ -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(parseInt(this.environmentService.args['max-memory']) * 1024 * 1024 || 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 @@ -839,11 +839,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 +915,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 +1142,7 @@ export class StatResolver { this.etag = etag(size, mtime); } - resolve(options: IResolveFileOptions): Promise { + resolve(options: IResolveFileOptions | undefined): Promise { // General Data const fileStat: IFileStat = { diff --git a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts index e73018103bc..1edead97b2a 100644 --- a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts +++ b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts @@ -291,7 +291,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 = []; @@ -505,7 +505,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)); }); }); @@ -593,7 +593,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)); @@ -620,7 +620,7 @@ export class RemoteFileService extends FileService { 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; 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 6600556b1d6..9ba4518f60b 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/unix/chokidarWatcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts index 4ea2032f5a6..84c332f6af7 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'; @@ -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 43ac686e8bb..cf2ad1c1d98 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/win32/watcherService.ts b/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts index 688f5e23c7b..1b07376f34c 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 6be722e48fb..c36d6a01d34 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,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 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'; 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 8d2af37c06f..57f7692827f 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,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 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'; @@ -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/history/electron-browser/history.ts b/src/vs/workbench/services/history/browser/history.ts similarity index 97% rename from src/vs/workbench/services/history/electron-browser/history.ts rename to src/vs/workbench/services/history/browser/history.ts index c9fdaa454ad..03234a94c8e 100644 --- a/src/vs/workbench/services/history/electron-browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -21,11 +21,11 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co 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 { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -217,7 +217,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 +281,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(); + } + }); } } @@ -889,7 +899,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners); } - return input; + return input || undefined; } } 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/integrityServiceImpl.ts similarity index 98% rename from src/vs/platform/integrity/node/integrityServiceImpl.ts rename to src/vs/workbench/services/integrity/node/integrityServiceImpl.ts index 25970d2f57f..699851ed92d 100644 --- a/src/vs/platform/integrity/node/integrityServiceImpl.ts +++ b/src/vs/workbench/services/integrity/node/integrityServiceImpl.ts @@ -8,7 +8,7 @@ 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 { INotificationService } from 'vs/platform/notification/common/notification'; diff --git a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts index e832b4a11b0..e21a0e65f59 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts @@ -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 > 1) { + 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,7 @@ 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); + this.applyEditsToBuffer(setProperty(model.getValue(), [-1], this.asObject(keybindingItem.resolvedKeybinding.getUserSettingsLabel(), keybindingItem.command, keybindingItem.when ? keybindingItem.when.serialize() : undefined, true), { tabSize, insertSpaces, eol })[0], model); } private removeUnassignedDefaultKeybinding(keybindingItem: ResolvedKeybindingItem, model: ITextModel): void { @@ -177,11 +181,11 @@ 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, when: string, negate: boolean): any { const object = { key }; object['command'] = negate ? `-${command}` : command; if (when) { - object['when'] = when.serialize(); + object['when'] = when; } return object; } diff --git a/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts index 123719291a7..a052a184b6e 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts @@ -40,7 +40,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 { @@ -120,11 +120,15 @@ export class MacLinuxFallbackKeyboardMapper implements IKeyboardMapper { 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)]; - } + let parts: SimpleKeybinding[] = []; if (_firstPart) { - return [new USLayoutResolvedKeybinding(_firstPart, this._OS)]; + parts.push(_firstPart); + } + if (_chordPart) { + parts.push(_chordPart); + } + 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 515a166f134..98d6b166a88 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); } } @@ -1012,31 +963,26 @@ 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)); + } + let result: NativeResolvedKeybinding[] = []; + this._generateResolvedKeybindings(chordParts, 0, [], result); + return result; + } - if (keybinding.type === KeybindingType.Chord) { - const firstParts = this.simpleKeybindingToScanCodeBinding(keybinding.firstPart); - const chordParts = this.simpleKeybindingToScanCodeBinding(keybinding.chordPart); - - 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 +1040,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[] { @@ -1108,22 +1054,15 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper { } 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); - } + let parts: ScanCodeBinding[][] = []; + if (_firstPart) { + parts.push(this._resolveSimpleUserBinding(_firstPart)); } + if (_chordPart) { + parts.push(this._resolveSimpleUserBinding(_chordPart)); + } + let result: NativeResolvedKeybinding[] = []; + this._generateResolvedKeybindings(parts, 0, [], result); return result; } diff --git a/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts index 5f5fda563e5..f3e6e0d4d43 100644 --- a/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ 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'; export interface IWindowsKeyMapping { vkey: string; @@ -76,42 +77,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 +101,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 +132,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 +165,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 +383,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 +432,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 { @@ -557,11 +467,15 @@ export class WindowsKeyboardMapper implements IKeyboardMapper { 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)]; - } + let parts: SimpleKeybinding[] = []; if (_firstPart) { - return [new WindowsNativeResolvedKeybinding(this, _firstPart, null)]; + parts.push(_firstPart); + } + if (_chordPart) { + parts.push(_chordPart); + } + 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 817b087ec8e..27a6e310a1b 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -15,7 +15,7 @@ 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'; @@ -38,6 +38,7 @@ 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'; export class KeyboardMapperFactory { public static readonly INSTANCE = new KeyboardMapperFactory(); @@ -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,6 +318,9 @@ 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 @@ -569,10 +576,31 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } let schemaId = 'vscode://schemas/keybindings'; +let commandsSchemas: IJSONSchema[] = []; +let commandsEnum: string[] = []; +let commandsEnumDescriptions: string[] = []; 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 +612,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 +623,52 @@ 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 allCommands = CommandsRegistry.getCommands(); + for (let commandId in allCommands) { + const commandDescription = allCommands[commandId].description; + + if (!/^_/.test(commandId)) { + commandsEnum.push(commandId); + commandsEnumDescriptions.push(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 configurationRegistry = Registry.as(ConfigExtensions.Configuration); const keyboardConfiguration: IConfigurationNode = { 'id': 'keyboard', 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 3240a4cb1fb..eee5ac58940 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'; @@ -120,28 +120,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 +149,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 +159,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 +232,28 @@ 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 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('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 +270,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/keyboardMapperTestUtils.ts b/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts index 37241813bae..185688a524a 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 { diff --git a/src/vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts index 72905db8fe5..e4fd593bdac 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'], }] ); }); @@ -65,7 +65,7 @@ suite('keyboardMapper - MAC fallback', () => { userSettingsLabel: 'cmd+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+Z', null], + dispatchParts: ['meta+Z'], } ); }); @@ -105,7 +105,7 @@ suite('keyboardMapper - MAC fallback', () => { userSettingsLabel: 'cmd+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -129,7 +129,7 @@ suite('keyboardMapper - LINUX fallback', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Z', null], + dispatchParts: ['ctrl+Z'], }] ); }); @@ -167,7 +167,7 @@ suite('keyboardMapper - LINUX fallback', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Z', null], + dispatchParts: ['ctrl+Z'], } ); }); @@ -201,7 +201,7 @@ suite('keyboardMapper - LINUX fallback', () => { userSettingsLabel: 'ctrl+,', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+,', null], + dispatchParts: ['ctrl+,'], }] ); }); @@ -224,7 +224,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 544b8ae03ef..ec0096039cd 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]'], }] ); }); @@ -118,7 +118,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[KeyY]', null], + dispatchParts: ['meta+[KeyY]'], } ); }); @@ -133,7 +133,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]'], }] ); }); @@ -156,7 +156,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+[BracketRight]', isWYSIWYG: false, isChord: false, - dispatchParts: ['meta+[BracketRight]', null], + dispatchParts: ['meta+[BracketRight]'], } ); }); @@ -171,7 +171,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 +186,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 +201,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 +246,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+down', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[ArrowDown]', null], + dispatchParts: ['meta+[ArrowDown]'], }] ); }); @@ -261,7 +261,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+numpad0', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[Numpad0]', null], + dispatchParts: ['meta+[Numpad0]'], }] ); }); @@ -276,7 +276,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[Home]', null], + dispatchParts: ['meta+[Home]'], }] ); }); @@ -299,7 +299,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[Home]', null], + dispatchParts: ['meta+[Home]'], } ); }); @@ -339,7 +339,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -362,7 +362,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -416,7 +416,7 @@ suite('keyboardMapper - MAC en_us', () => { userSettingsLabel: 'cmd+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -439,7 +439,7 @@ suite('keyboardMapper - MAC en_us', () => { userSettingsLabel: 'cmd+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -491,7 +491,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+a', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyA]', null], + dispatchParts: ['ctrl+[KeyA]'], }] ); }); @@ -506,7 +506,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyY]', null], + dispatchParts: ['ctrl+[KeyY]'], }] ); }); @@ -529,7 +529,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyY]', null], + dispatchParts: ['ctrl+[KeyY]'], } ); }); @@ -559,7 +559,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+[BracketRight]', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+[BracketRight]', null], + dispatchParts: ['ctrl+[BracketRight]'], } ); }); @@ -574,7 +574,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 +582,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 +597,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 +612,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 +649,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+down', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[ArrowDown]', null], + dispatchParts: ['ctrl+[ArrowDown]'], }] ); }); @@ -664,7 +664,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+numpad0', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Numpad0]', null], + dispatchParts: ['ctrl+[Numpad0]'], }] ); }); @@ -679,7 +679,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Home]', null], + dispatchParts: ['ctrl+[Home]'], }] ); }); @@ -702,7 +702,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Home]', null], + dispatchParts: ['ctrl+[Home]'], } ); }); @@ -725,7 +725,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+x', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyX]', null], + dispatchParts: ['ctrl+[KeyX]'], } ); }); @@ -765,7 +765,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -788,7 +788,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -821,7 +821,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+a', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyA]', null], + dispatchParts: ['ctrl+[KeyA]'], }] ); }); @@ -836,7 +836,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyZ]', null], + dispatchParts: ['ctrl+[KeyZ]'], }] ); }); @@ -859,7 +859,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyZ]', null], + dispatchParts: ['ctrl+[KeyZ]'], } ); }); @@ -874,7 +874,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+]', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[BracketRight]', null], + dispatchParts: ['ctrl+[BracketRight]'], }] ); }); @@ -897,7 +897,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+]', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[BracketRight]', null], + dispatchParts: ['ctrl+[BracketRight]'], } ); }); @@ -912,7 +912,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'shift+]', isWYSIWYG: true, isChord: false, - dispatchParts: ['shift+[BracketRight]', null], + dispatchParts: ['shift+[BracketRight]'], }] ); }); @@ -927,7 +927,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+/', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Slash]', null], + dispatchParts: ['ctrl+[Slash]'], }] ); }); @@ -942,7 +942,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+shift+/', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+shift+[Slash]', null], + dispatchParts: ['ctrl+shift+[Slash]'], }] ); }); @@ -987,7 +987,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+down', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[ArrowDown]', null], + dispatchParts: ['ctrl+[ArrowDown]'], }] ); }); @@ -1002,7 +1002,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+numpad0', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Numpad0]', null], + dispatchParts: ['ctrl+[Numpad0]'], }] ); }); @@ -1017,7 +1017,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Home]', null], + dispatchParts: ['ctrl+[Home]'], }] ); }); @@ -1040,7 +1040,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Home]', null], + dispatchParts: ['ctrl+[Home]'], } ); }); @@ -1055,7 +1055,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 +1063,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+[IntlBackslash]', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+[IntlBackslash]', null], + dispatchParts: ['ctrl+[IntlBackslash]'], }] ); }); @@ -1078,7 +1078,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+enter', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Enter]', null], + dispatchParts: ['ctrl+[Enter]'], }] ); }); @@ -1101,7 +1101,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+enter', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Enter]', null], + dispatchParts: ['ctrl+[Enter]'], } ); }); @@ -1135,7 +1135,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+,', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Comma]', null], + dispatchParts: ['ctrl+[Comma]'], }] ); }); @@ -1158,7 +1158,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -1181,7 +1181,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -1216,7 +1216,7 @@ suite('keyboardMapper', () => { userSettingsLabel: 'ctrl+`', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Backquote]', null], + dispatchParts: ['ctrl+[Backquote]'], } ); }); @@ -1242,7 +1242,7 @@ suite('keyboardMapper', () => { userSettingsLabel: userSettingsLabel, isWYSIWYG: true, isChord: false, - dispatchParts: [dispatch, null], + dispatchParts: [dispatch], } ); } @@ -1281,7 +1281,7 @@ suite('keyboardMapper', () => { userSettingsLabel: userSettingsLabel, isWYSIWYG: true, isChord: false, - dispatchParts: [dispatch, null], + dispatchParts: [dispatch], } ); } @@ -1339,7 +1339,7 @@ suite('keyboardMapper - LINUX ru', () => { userSettingsLabel: 'ctrl+s', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyS]', null], + dispatchParts: ['ctrl+[KeyS]'], }] ); }); @@ -1376,7 +1376,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 +1409,7 @@ suite('keyboardMapper - MAC zh_hant', () => { userSettingsLabel: 'cmd+c', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[KeyC]', null], + dispatchParts: ['meta+[KeyC]'], }] ); }); @@ -1425,17 +1425,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 7c3fdefcc64..22bc65c18bd 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'], }] ); }); @@ -83,7 +83,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Z', null], + dispatchParts: ['ctrl+Z'], } ); }); @@ -99,7 +99,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+oem_6', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+]', null], + dispatchParts: ['ctrl+]'], }] ); }); @@ -122,7 +122,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+oem_6', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+]', null], + dispatchParts: ['ctrl+]'], } ); }); @@ -138,7 +138,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'shift+oem_6', isWYSIWYG: false, isChord: false, - dispatchParts: ['shift+]', null], + dispatchParts: ['shift+]'], }] ); }); @@ -154,7 +154,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+oem_2', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+/', null], + dispatchParts: ['ctrl+/'], }] ); }); @@ -170,7 +170,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+shift+oem_2', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+shift+/', null], + dispatchParts: ['ctrl+shift+/'], }] ); }); @@ -210,7 +210,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+down', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+DownArrow', null], + dispatchParts: ['ctrl+DownArrow'], }] ); }); @@ -226,7 +226,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+numpad0', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+NumPad0', null], + dispatchParts: ['ctrl+NumPad0'], }] ); }); @@ -242,7 +242,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Home', null], + dispatchParts: ['ctrl+Home'], }] ); }); @@ -265,7 +265,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Home', null], + dispatchParts: ['ctrl+Home'], } ); }); @@ -305,7 +305,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -368,7 +368,7 @@ suite('keyboardMapper - WINDOWS en_us', () => { userSettingsLabel: 'ctrl+,', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+,', null], + dispatchParts: ['ctrl+,'], }] ); }); @@ -391,7 +391,7 @@ suite('keyboardMapper - WINDOWS en_us', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -427,7 +427,7 @@ suite('keyboardMapper - WINDOWS por_ptb', () => { userSettingsLabel: 'ctrl+abnt_c1', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+ABNT_C1', null], + dispatchParts: ['ctrl+ABNT_C1'], } ); }); @@ -450,7 +450,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 +514,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 51e39703916..4d017ef56bd 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -186,7 +186,7 @@ export class LabelService implements ILabelService { } // Workspace: Untitled - if (isEqualOrParent(workspace.configPath, URI.file(this.environmentService.workspacesHome))) { + if (isEqualOrParent(workspace.configPath, this.environmentService.untitledWorkspacesHome)) { return localize('untitledWorkspace', "Untitled (Workspace)"); } @@ -194,7 +194,7 @@ export class LabelService implements ILabelService { const filename = basename(workspace.configPath); const workspaceName = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1); if (options && options.verbose) { - return localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspace.configPath)!, workspaceName))); + return localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspace.configPath), workspaceName))); } return localize('workspaceName', "{0} (Workspace)", workspaceName); diff --git a/src/vs/workbench/services/label/test/label.test.ts b/src/vs/workbench/services/label/test/label.test.ts index 233d942bc6c..f8abf19df41 100644 --- a/src/vs/workbench/services/label/test/label.test.ts +++ b/src/vs/workbench/services/label/test/label.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { TestEnvironmentService, TestContextService, TestWindowService } 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'; @@ -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/panel/common/panelService.ts b/src/vs/workbench/services/panel/common/panelService.ts index 11732a0207a..9b4b359bfa4 100644 --- a/src/vs/workbench/services/panel/common/panelService.ts +++ b/src/vs/workbench/services/panel/common/panelService.ts @@ -25,12 +25,12 @@ 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) diff --git a/src/vs/workbench/services/part/common/partService.ts b/src/vs/workbench/services/part/common/partService.ts index ad8908763f7..c779417568b 100644 --- a/src/vs/workbench/services/part/common/partService.ts +++ b/src/vs/workbench/services/part/common/partService.ts @@ -13,8 +13,7 @@ export const enum Parts { PANEL_PART, EDITOR_PART, STATUSBAR_PART, - TITLEBAR_PART, - MENUBAR_PART + TITLEBAR_PART } export const enum Position { diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index ca15cd76763..ee6c999089f 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -110,7 +110,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic 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); diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index f155b15b94d..09ee6228adc 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -5,7 +5,7 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; -import { join } from 'vs/base/common/paths'; +import { joinWithSlashes } from 'vs/base/common/extpath'; import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; @@ -135,7 +135,7 @@ export interface IFilterMetadata { export interface IPreferencesEditorModel { uri?: URI; - getPreference(key: string): T; + getPreference(key: string): T | null; dispose(): void; } @@ -147,7 +147,7 @@ export interface ISettingsEditorModel extends IPreferencesEditorModel 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): IFilterResult | null; } export interface ISettingsEditorOptions extends IEditorOptions { @@ -165,7 +165,7 @@ export class SettingsEditorOptions extends EditorOptions implements ISettingsEdi folderUri?: URI; query?: string; - static create(settings: ISettingsEditorOptions): SettingsEditorOptions { + static create(settings: ISettingsEditorOptions): SettingsEditorOptions | null { if (!settings) { return null; } @@ -230,6 +230,6 @@ export function getSettingsTargetName(target: ConfigurationTarget, resource: URI return ''; } -export const FOLDER_SETTINGS_PATH = join('.vscode', 'settings.json'); +export const FOLDER_SETTINGS_PATH = joinWithSlashes('.vscode', '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/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index fab9317ee52..f61324585e4 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -27,7 +27,7 @@ export abstract class AbstractSettingsModel extends EditorModel { protected _currentResultGroups = new Map(); - updateResultGroup(id: string, resultGroup: ISearchResultGroup): IFilterResult { + updateResultGroup(id: string, resultGroup: ISearchResultGroup): IFilterResult | null { if (resultGroup) { this._currentResultGroups.set(id, resultGroup); } else { @@ -44,9 +44,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 +76,7 @@ export abstract class AbstractSettingsModel extends EditorModel { return filterMatches.sort((a, b) => b.score - a.score); } - getPreference(key: string): ISetting { + getPreference(key: string): ISetting | null { for (const group of this.settingsGroups) { for (const section of group.sections) { for (const setting of section.settings) { @@ -111,12 +111,12 @@ export abstract class AbstractSettingsModel extends EditorModel { abstract findValueMatches(filter: string, setting: ISetting): IRange[]; - protected abstract update(): IFilterResult; + protected abstract update(): IFilterResult | null; } export class SettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel { - private _settingsGroups: ISettingsGroup[]; + private _settingsGroups: ISettingsGroup[] | null; protected settingsModel: ITextModel; private readonly _onDidChangeGroups: Emitter = this._register(new Emitter()); @@ -144,7 +144,7 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti if (!this._settingsGroups) { this.parse(); } - return this._settingsGroups; + return this._settingsGroups!; } get content(): string { @@ -163,7 +163,7 @@ 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 | null { const resultGroups = map.values(this._currentResultGroups); if (!resultGroups.length) { return null; @@ -179,7 +179,7 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti }); }); - let filteredGroup: ISettingsGroup; + let filteredGroup: ISettingsGroup | undefined; const modelGroup = this.settingsGroups[0]; // Editable model has one or zero groups if (modelGroup) { filteredGroup = { @@ -836,7 +836,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements return []; } - getPreference(key: string): ISetting { + getPreference(key: string): ISetting | null { for (const group of this.settingsGroups) { for (const section of group.sections) { for (const setting of section.settings) { @@ -1003,7 +1003,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; @@ -1037,28 +1037,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) }, { @@ -1071,17 +1071,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); 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 85a11654b20..5e9f18429c6 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/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 7ed4772449e..a6caab8223e 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 99% rename from src/vs/platform/search/common/search.ts rename to src/vs/workbench/services/search/common/search.ts index 369d036d711..08462508241 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -9,7 +9,7 @@ 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'; @@ -371,7 +371,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; diff --git a/src/vs/workbench/services/search/common/searchHelpers.ts b/src/vs/workbench/services/search/common/searchHelpers.ts index 988792bb112..e444f2b355c 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/searchHistoryService.ts b/src/vs/workbench/services/search/common/searchHistoryService.ts similarity index 97% rename from src/vs/workbench/services/search/node/searchHistoryService.ts rename to src/vs/workbench/services/search/common/searchHistoryService.ts index d3435bc34be..dbd9bebb186 100644 --- a/src/vs/workbench/services/search/node/searchHistoryService.ts +++ b/src/vs/workbench/services/search/common/searchHistoryService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { ISearchHistoryValues, ISearchHistoryService } from 'vs/platform/search/common/search'; +import { ISearchHistoryValues, ISearchHistoryService } from 'vs/workbench/services/search/common/search'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { isEmptyObject } from 'vs/base/common/types'; diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index e656f69ccc6..9d4553aa00e 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,7 +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 { IFileQuery, IFolderQuery, IProgress, ISearchEngineStats } from 'vs/workbench/services/search/common/search'; import { IRawFileMatch, ISearchEngine, ISearchEngineSuccess } from 'vs/workbench/services/search/node/search'; import { spawnRipgrepCmd } from './ripgrepFileSearch'; diff --git a/src/vs/workbench/services/search/node/fileSearchManager.ts b/src/vs/workbench/services/search/node/fileSearchManager.ts index 3c95ee21387..6ffabf94a20 100644 --- a/src/vs/workbench/services/search/node/fileSearchManager.ts +++ b/src/vs/workbench/services/search/node/fileSearchManager.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. 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 { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery } from 'vs/workbench/services/search/common/search'; import { QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/node/search'; import * as vscode from 'vscode'; diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index da52da42622..436fc42af4f 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'; @@ -17,7 +17,7 @@ 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 { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery } 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'; diff --git a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts index f56f7a6b4aa..44821d950ec 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 720e020f4a2..3d8e787c342 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 f70c901e072..f201ae9dc01 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 index d59a9c53a0b..635c1c1d1b1 100644 --- a/src/vs/workbench/services/search/node/search.ts +++ b/src/vs/workbench/services/search/node/search.ts @@ -5,7 +5,7 @@ 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 { IFileSearchStats, IFolderQuery, IProgress, IRawFileQuery, IRawTextQuery, ISearchEngineStats, ISearchQuery, ITextSearchMatch, ITextSearchStats, ITextSearchResult } from 'vs/workbench/services/search/common/search'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; export interface ITelemetryEvent { diff --git a/src/vs/workbench/services/search/node/searchIpc.ts b/src/vs/workbench/services/search/node/searchIpc.ts index d6ef91388c6..3d3b0ad9364 100644 --- a/src/vs/workbench/services/search/node/searchIpc.ts +++ b/src/vs/workbench/services/search/node/searchIpc.ts @@ -5,7 +5,7 @@ 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 { IRawFileQuery, IRawTextQuery } from 'vs/workbench/services/search/common/search'; import { IRawSearchService, ISerializedSearchComplete, ISerializedSearchProgressItem } from './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 08bb261385c..71cafb82bd7 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -20,7 +20,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; 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, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType, ISearchConfiguration } 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 } 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'; @@ -60,6 +60,8 @@ export class SearchService extends Disposable implements ISearchService { list = this.textSearchProviders; } else if (type === SearchProviderType.fileIndex) { list = this.fileIndexProviders; + } else { + throw new Error('Unknown SearchProviderType'); } list.set(scheme, provider); @@ -321,7 +323,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' : diff --git a/src/vs/workbench/services/search/node/textSearchAdapter.ts b/src/vs/workbench/services/search/node/textSearchAdapter.ts index 02117a2eda4..2308349c84d 100644 --- a/src/vs/workbench/services/search/node/textSearchAdapter.ts +++ b/src/vs/workbench/services/search/node/textSearchAdapter.ts @@ -5,7 +5,7 @@ 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 } 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'; diff --git a/src/vs/workbench/services/search/node/textSearchManager.ts b/src/vs/workbench/services/search/node/textSearchManager.ts index 6fbcba08b7e..9f2dd2b0c88 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 MIT License. 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,7 +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 { IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult } from 'vs/workbench/services/search/common/search'; import { QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/node/search'; import * as vscode from 'vscode'; 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 c8685c2e2b4..47085ed1ec0 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 MIT License. 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 147c0cfc65b..4e32035be54 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 MIT License. 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 3bd14e02209..a39fead78dc 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 de7cf1d7964..7c156c5e583 100644 --- a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts +++ b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts @@ -4,12 +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 { 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 } 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'; 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 ff89992c919..069ed3175ec 100644 --- a/src/vs/workbench/services/search/test/node/search.test.ts +++ b/src/vs/workbench/services/search/test/node/search.test.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 { 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 } 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'; @@ -188,7 +187,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 8b56afda721..02d457aaadd 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -4,12 +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 { deserializeSearchError, IFolderQuery, ISearchRange, ITextQuery, ITextSearchContext, ITextSearchMatch, QueryType, SearchErrorCode } from 'vs/workbench/services/search/common/search'; import { ISerializedFileMatch } from 'vs/workbench/services/search/node/search'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; 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 2b2dcf13734..6a1cb0a90e0 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/TMSyntax.ts b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts index 3142782ef07..9caecebb71d 100644 --- a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts +++ b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts @@ -25,6 +25,8 @@ import { ITextMateService } from 'vs/workbench/services/textMate/electron-browse 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 { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class TMScopeRegistry { @@ -158,7 +160,8 @@ export class TextMateService extends Disposable implements ITextMateService { @IWorkbenchThemeService private readonly _themeService: IWorkbenchThemeService, @IFileService private readonly _fileService: IFileService, @INotificationService private readonly _notificationService: INotificationService, - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: ILogService, + @IConfigurationService private readonly _configurationService: ConfigurationService ) { super(); this._styleElement = dom.createStyleSheet(); @@ -226,7 +229,7 @@ export class TextMateService extends Disposable implements ITextMateService { 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); + return new TMTokenization(this._scopeRegistry, r.languageId, r.grammar, r.initialState, r.containsEmbeddedLanguages, this._notificationService, this._configurationService); }, e => { onUnexpectedError(e); return null; @@ -257,7 +260,7 @@ export class TextMateService extends Disposable implements ITextMateService { let injections: string[] = []; for (let i = 1; i <= scopeParts.length; i++) { const subScopeName = scopeParts.slice(0, i).join('.'); - injections = [...injections, ...this._injections[subScopeName]]; + injections = [...injections, ...(this._injections[subScopeName] || [])]; } return injections; } @@ -442,15 +445,17 @@ class TMTokenization implements ITokenizationSupport { 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) { + 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 { @@ -466,13 +471,13 @@ class TMTokenization implements ITokenizationSupport { throw new Error('Unexpected: offsetDelta should be 0.'); } - // Do not attempt to tokenize if a line has over 20k - if (line.length >= 20000) { + // 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 lines longer than 20k characters for performance reasons.")); + 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 20k characters, tokenization skipped.`); + console.log(`Line (${line.substr(0, 15)}...): longer than ${this._maxTokenizationLineLength} characters, tokenization skipped.`); return nullTokenize2(this._languageId, line, state, offsetDelta); } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index c127e2646f9..deb855d39ad 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 MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { guessMimeTypes } from 'vs/base/common/mime'; @@ -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'; /** @@ -268,7 +268,7 @@ 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 */ @@ -769,7 +769,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 +784,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(extpath.joinWithSlashes(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(extpath.joinWithSlashes(this.environmentService.appSettingsHome, 'snippets')))) { return 'snippets'; } @@ -797,7 +797,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil const folders = this.contextService.getWorkspace().folders; for (const folder of folders) { if (isEqualOrParent(this.resource, folder.toResource('.vscode'))) { - const filename = path.basename(this.resource.fsPath); + const filename = basename(this.resource); if (TextFileEditorModel.WHITELIST_WORKSPACE_JSON.indexOf(filename) > -1) { return `.vscode/${filename}`; } @@ -808,8 +808,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private getTelemetryData(reason: number): Object { - const ext = path.extname(this.resource.fsPath); - const fileName = path.basename(this.resource.fsPath); + const ext = extname(this.resource); + const fileName = basename(this.resource); const telemetryData = { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext, @@ -1004,7 +1004,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } isReadonly(): boolean { - return this.lastResolvedDiskStat && this.lastResolvedDiskStat.isReadonly; + return !!(this.lastResolvedDiskStat && this.lastResolvedDiskStat.isReadonly); } isDisposed(): boolean { @@ -1100,8 +1100,8 @@ export class SaveSequentializer { // so that we can return a promise that completes when the save operation // has completed. if (!this._nextSave) { - let promiseResolve: () => void; - let promiseReject: (error: Error) => void; + let promiseResolve: (() => void) | undefined; + let promiseReject: ((error: Error) => void) | undefined; const promise = new Promise((resolve, reject) => { promiseResolve = resolve; promiseReject = reject; @@ -1129,6 +1129,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/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 2a85b26c188..bf1aa366728 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; 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 errors from 'vs/base/common/errors'; import * as objects from 'vs/base/common/objects'; import { Event, Emitter } from 'vs/base/common/event'; @@ -28,11 +28,16 @@ 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'; export interface IBackupResult { didBackup: boolean; @@ -43,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; @@ -65,27 +70,31 @@ export abstract class TextFileService extends Disposable implements ITextFileSer 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); @@ -97,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 = 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 { @@ -177,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) { @@ -393,7 +515,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; @@ -435,8 +557,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer // Untitled with associated file path don't need to prompt if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { - const authority = this.windowService.getConfiguration().remoteAuthority; - targetUri = authority ? untitled.with({ scheme: REMOTE_HOST_SCHEME, authority }) : untitled.with({ scheme: Schemas.file }); + targetUri = this.untitledToAssociatedFileResource(untitled); } // Otherwise ask user @@ -473,6 +594,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 => { @@ -560,7 +687,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer 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) { @@ -568,8 +695,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(() => { @@ -580,30 +712,56 @@ 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()); + this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); + + // save model + return targetModel.save(options).then(() => true); + }); }, error => { // binary model: delete the file and run the operation again @@ -636,7 +794,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer } 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 { @@ -750,7 +908,7 @@ 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: extpath.joinWithSlashes(target.path, sourceModelResource.path.substr(source.path.length + 1)) }); } // Remember as dirty target model to load after the operation 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 ad1f19a9500..00000000000 --- 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 MIT License. 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/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index 680eab70090..33fa05d3021 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -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++; } @@ -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 dcbe783ebad..56a363ebf72 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -7,7 +7,7 @@ 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 { joinWithSlashes } from 'vs/base/common/extpath'; 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'; @@ -30,7 +30,7 @@ class ServiceAccessor { } function toResource(path: string): URI { - return URI.file(join('C:\\', path)); + return URI.file(joinWithSlashes('C:\\', path)); } suite('Files - TextFileEditorModelManager', () => { diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index 11847180005..c05d512145c 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 90% rename from src/vs/workbench/services/themes/electron-browser/colorThemeData.ts rename to src/vs/workbench/services/themes/browser/colorThemeData.ts index 3ff37d4ac5a..0f0f33b306d 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 MIT License. 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); @@ -257,11 +259,10 @@ 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 themeSelector = toCSSSelector(extensionData.extensionId, theme.path); let themeData = new ColorThemeData(); themeData.id = `${baseTheme} ${themeSelector}`; - themeData.label = theme.label || Paths.basename(theme.path); + themeData.label = theme.label || basename(theme.path); themeData.settingsId = theme.id || themeData.label; themeData.description = theme.description; themeData.watch = theme._watch === true; @@ -272,9 +273,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 +288,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 +297,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 +323,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 +336,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 98% rename from src/vs/workbench/services/themes/electron-browser/colorThemeStore.ts rename to src/vs/workbench/services/themes/browser/colorThemeStore.ts index 7955ba6b932..960bb54ec3a 100644 --- a/src/vs/workbench/services/themes/electron-browser/colorThemeStore.ts +++ b/src/vs/workbench/services/themes/browser/colorThemeStore.ts @@ -9,7 +9,7 @@ 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 { 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 { 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'; 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 b5863d584de..577b32e4206 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 99% rename from src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts rename to src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 8413a65817a..6540a94ea80 100644 --- a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -19,9 +19,9 @@ import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/pl 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'; diff --git a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/common/fileIconThemeData.ts similarity index 99% rename from src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts rename to src/vs/workbench/services/themes/common/fileIconThemeData.ts index 7694bf4c616..8b345ada938 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'; @@ -202,7 +202,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 99% rename from src/vs/workbench/services/themes/electron-browser/fileIconThemeStore.ts rename to src/vs/workbench/services/themes/common/fileIconThemeStore.ts index aed1e0de01d..0f204325a2b 100644 --- a/src/vs/workbench/services/themes/electron-browser/fileIconThemeStore.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeStore.ts @@ -11,7 +11,7 @@ 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({ 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 00000000000..e4478a5ecf6 --- /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 MIT License. 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; +} \ No newline at end of file diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index c8a35127665..be1ba98ee64 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -378,6 +378,7 @@ class TimerService implements ITimerService { } const activeViewlet = this._viewletService.getActiveViewlet(); + const activePanel = this._panelService.getActivePanel(); return { version: 2, ellapsed: perf.getDuration(startMark, 'didStartWorkbench'), @@ -389,7 +390,7 @@ class TimerService implements ITimerService { windowCount: await this._windowsService.getWindowCount(), 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: { diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index 6e4f073e0c0..0f8bbc918b9 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -9,7 +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 { IWorkspaceIdentifier, IWorkspaceFolderCreationData, isWorkspaceIdentifier, toWorkspaceIdentifier, IWorkspacesService, rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -25,7 +25,6 @@ import { isLinux } from 'vs/base/common/platform'; import { isEqual, basename } 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'; export class WorkspaceEditingService implements IWorkspaceEditingService { @@ -63,7 +62,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 +78,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); } } @@ -177,7 +176,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(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")], @@ -257,7 +256,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 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 5c6f6e9e13b..b959ede3928 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,7 +173,7 @@ 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); @@ -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/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index b29dbd3f74d..bc186f8d331 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -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(); diff --git a/src/vs/workbench/test/common/editor/untitledEditor.test.ts b/src/vs/workbench/test/common/editor/untitledEditor.test.ts index c5df4077e8c..36f98b4a3e3 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 { joinWithSlashes } from 'vs/base/common/extpath'; 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'; @@ -99,7 +99,7 @@ suite('Workbench untitled editors', () => { test('Untitled with associated resource', function () { const service = accessor.untitledEditorService; - const file = URI.file(join('C:\\', '/foo/file.txt')); + const file = URI.file(joinWithSlashes('C:\\', '/foo/file.txt')); const untitled = service.createOrGet(file); assert.ok(service.hasAssociatedFilePath(untitled.getResource())); @@ -140,7 +140,7 @@ suite('Workbench untitled editors', () => { return service.loadOrCreate({ resource: input.getResource() }).then(model3 => { assert.equal(model3.getResource().toString(), input.getResource().toString()); - const file = URI.file(join('C:\\', '/foo/file44.txt')); + const file = URI.file(joinWithSlashes('C:\\', '/foo/file44.txt')); return service.loadOrCreate({ resource: file }).then(model4 => { assert.ok(service.hasAssociatedFilePath(model4.getResource())); assert.ok(model4.isDirty()); @@ -165,7 +165,7 @@ suite('Workbench untitled editors', () => { test('Untitled with associated path remains dirty when content gets empty', function () { const service = accessor.untitledEditorService; - const file = URI.file(join('C:\\', '/foo/file.txt')); + const file = URI.file(joinWithSlashes('C:\\', '/foo/file.txt')); const input = service.createOrGet(file); // dirty 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 b70f616cb78..4018e8cfd91 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 418690b7e06..49c0a50275a 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/contrib/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( @@ -137,10 +138,8 @@ suite('ExtHostLanguageFeatureCommands', function () { mainThread.dispose(); }); - teardown(function () { - while (disposables.length) { - disposables.pop().dispose(); - } + teardown(() => { + disposables = dispose(disposables); return rpcProtocol.sync(); }); @@ -664,9 +663,9 @@ suite('ExtHostLanguageFeatureCommands', function () { 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!.arguments![0], 1); + assert.equal(first.command!.arguments![1], true); + assert.equal(first.command!.arguments![2], complexArg); }); }); }); @@ -796,20 +795,21 @@ 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 [ + 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) - ]; + ]]; } })); 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 75ebe01221d..25213f7c845 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; +import { ExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostConfigProvider } from 'vs/workbench/api/node/extHostConfiguration'; import { MainThreadConfigurationShape, IConfigurationInitData } from 'vs/workbench/api/node/extHost.protocol'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; @@ -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 ExtHostWorkspaceProvider(new TestRPCProtocol(), null, new NullLogService(), new Counter()), createConfigurationData(contents)); } function createConfigurationData(contents: any): IConfigurationInitData { @@ -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 ExtHostWorkspaceProvider(new TestRPCProtocol(), null, new NullLogService(), new Counter()), { defaults: new ConfigurationModel({ 'editor': { @@ -308,7 +308,7 @@ suite('ExtHostConfiguration', function () { folders[workspaceUri.toString()] = workspace; const testObject = new ExtHostConfigProvider( new class extends mock() { }, - new ExtHostWorkspace(new TestRPCProtocol(), { + new ExtHostWorkspaceProvider(new TestRPCProtocol(), { 'id': 'foo', 'folders': [aWorkspaceFolder(URI.file('foo'), 0)], 'name': 'foo' @@ -382,7 +382,7 @@ suite('ExtHostConfiguration', function () { const testObject = new ExtHostConfigProvider( new class extends mock() { }, - new ExtHostWorkspace(new TestRPCProtocol(), { + new ExtHostWorkspaceProvider(new TestRPCProtocol(), { 'id': 'foo', 'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)], 'name': 'foo' @@ -591,7 +591,7 @@ suite('ExtHostConfiguration', function () { const workspaceFolder = aWorkspaceFolder(URI.file('folder1'), 0); const testObject = new ExtHostConfigProvider( new class extends mock() { }, - new ExtHostWorkspace(new TestRPCProtocol(), { + new ExtHostWorkspaceProvider(new TestRPCProtocol(), { 'id': 'foo', 'folders': [workspaceFolder], 'name': 'foo' 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 b52780e7af3..e0cc18a3d78 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -341,7 +341,7 @@ suite('ExtHostDocumentSaveParticipant', () => { rangeLength: undefined!, }], eol: undefined!, - versionId: documents.getDocumentData(uri).version + 1 + versionId: documents.getDocumentData(uri)!.version + 1 }, true); } } @@ -350,7 +350,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 c2fc1dc68eb..433967a73d9 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -33,7 +33,7 @@ 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 { 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(() => { @@ -122,10 +128,8 @@ suite('ExtHostLanguageFeatures', function () { mainThread.dispose(); }); - teardown(function () { - while (disposables.length) { - disposables.pop().dispose(); - } + teardown(() => { + disposables = dispose(disposables); return rpcProtocol.sync(); }); @@ -214,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 () => { @@ -232,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 @@ -456,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); }); @@ -477,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); }); @@ -521,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 @@ -600,9 +604,9 @@ suite('ExtHostLanguageFeatures', function () { assert.equal(value.length, 2); const [first, second] = value; 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 () => { @@ -624,8 +628,8 @@ suite('ExtHostLanguageFeatures', function () { assert.equal(value.length, 1); const [first] = value; 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'); }); @@ -906,6 +910,12 @@ suite('ExtHostLanguageFeatures', function () { // --- 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 { @@ -914,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'); @@ -932,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 () => { @@ -956,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'); @@ -971,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 }); }); @@ -995,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); @@ -1013,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 () => { @@ -1025,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 }); }); @@ -1094,17 +1104,18 @@ suite('ExtHostLanguageFeatures', function () { test('Selection Ranges, data conversion', async () => { disposables.push(extHost.registerSelectionRangeProvider(defaultExtension, defaultSelector, new class implements vscode.SelectionRangeProvider { provideSelectionRanges() { - return [ + 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) - ]; + ]]; } })); 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); }); }); }); 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 985ccdb5ab4..5caa4710b56 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'; 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 45f61e09759..8cc4279dfd9 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -5,10 +5,9 @@ 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 { basename } from 'vs/base/common/path'; +import { ExtHostWorkspaceProvider } 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'; @@ -30,18 +29,14 @@ suite('ExtHostWorkspace', function () { version: undefined! }; - function assertAsRelativePath(workspace: ExtHostWorkspace, input: string, expected: string, includeWorkspace?: boolean) { + function assertAsRelativePath(workspace: ExtHostWorkspaceProvider, 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 = new ExtHostWorkspaceProvider(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 +50,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 = new ExtHostWorkspaceProvider(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 = new ExtHostWorkspaceProvider(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 = new ExtHostWorkspaceProvider(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 = new ExtHostWorkspaceProvider(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 +84,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 = new ExtHostWorkspaceProvider(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 +94,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 = new ExtHostWorkspaceProvider(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 = new ExtHostWorkspaceProvider(new TestRPCProtocol(), null!, new NullLogService(), new Counter()); assert.equal(ws.getPath(), undefined); - ws = new ExtHostWorkspace(new TestRPCProtocol(), undefined!, new NullLogService(), new Counter()); + ws = new ExtHostWorkspaceProvider(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 = new ExtHostWorkspaceProvider(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 = new ExtHostWorkspaceProvider(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 = new ExtHostWorkspaceProvider(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 +122,7 @@ suite('ExtHostWorkspace', function () { }); test('getContainingWorkspaceFolder', () => { - const ws = new ExtHostWorkspace(new TestRPCProtocol(), { + const ws = new ExtHostWorkspaceProvider(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [ @@ -140,42 +135,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 = new ExtHostWorkspaceProvider(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); let finished = false; const finish = (error?: any) => { @@ -238,27 +233,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 = new ExtHostWorkspaceProvider(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 = new ExtHostWorkspaceProvider(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 +262,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 = new ExtHostWorkspaceProvider(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 +284,17 @@ suite('ExtHostWorkspace', function () { assertRegistered: undefined! }; - const ws = new ExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + const ws = new ExtHostWorkspaceProvider(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 +311,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 +343,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 +370,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 +399,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 +435,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 +463,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 +474,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,9 +501,9 @@ 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(); }); @@ -522,7 +517,7 @@ suite('ExtHostWorkspace', function () { } }; - let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = new ExtHostWorkspaceProvider(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); let sub = ws.onDidChangeWorkspace(e => { try { assert.throws(() => { @@ -541,7 +536,7 @@ suite('ExtHostWorkspace', function () { }); 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 = new ExtHostWorkspaceProvider(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [ aWorkspaceFolderData(URI.file('c:/Users/marek/Desktop/vsc_test/'), 0) ] 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 6b0abfd5d04..60dfeb7108d 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/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index 11a15cc0c13..1376b5e4a47 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,12 +18,12 @@ 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/contrib/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/editor/common/editorGroupsService'; import { SearchService } from 'vs/workbench/services/search/node/searchService'; 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 ba26107a57f..cd61bc45126 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 MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/workbench/contrib/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/editor/common/editorGroupsService'; -import { ISearchService } from 'vs/platform/search/common/search'; +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'; diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 8e51ba0bc7f..0f8aaf1b46f 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -6,7 +6,7 @@ import 'vs/workbench/contrib/files/electron-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 * as extpath from 'vs/base/common/extpath'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -62,7 +62,7 @@ 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/editor/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IOpenEditorOverrideHandler, IActiveEditor } 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'; @@ -163,7 +163,7 @@ export class TestContextService implements IWorkspaceContextService { } public toResource(workspaceRelativePath: string): URI { - return URI.file(paths.join('C:\\', workspaceRelativePath)); + return URI.file(extpath.joinWithSlashes('C:\\', workspaceRelativePath)); } public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { @@ -179,21 +179,45 @@ export class TestTextFileService extends TextFileService { 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 { @@ -236,6 +260,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); } @@ -248,6 +276,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); @@ -274,7 +303,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()); @@ -384,13 +412,13 @@ export class TestFileDialogService implements IFileDialogService { public _serviceBrand: any; - public defaultFilePath(_schemeFilter: string): URI | undefined { + public defaultFilePath(_schemeFilter?: string): URI | undefined { return undefined; } - public defaultFolderPath(_schemeFilter: string): URI | undefined { + public defaultFolderPath(_schemeFilter?: string): URI | undefined { return undefined; } - public defaultWorkspacePath(_schemeFilter: string): URI | undefined { + public defaultWorkspacePath(_schemeFilter?: string): URI | undefined { return undefined; } public pickFileFolderAndOpen(_options: IPickAndOpenOptions): Promise { @@ -601,7 +629,7 @@ export class TestEditorGroup implements IEditorGroupView { constructor(public id: number) { } get group(): EditorGroup { throw new Error('not implemented'); } - activeControl: IEditor; + activeControl: IActiveEditor; activeEditor: IEditorInput; previewEditor: IEditorInput; count: number; @@ -702,7 +730,7 @@ export class TestEditorService implements EditorServiceImpl { onDidCloseEditor: Event = Event.None; onDidOpenEditorFail: Event = Event.None; - activeControl: IEditor; + activeControl: IActiveEditor; activeTextEditorWidget: any; activeEditor: IEditorInput; editors: ReadonlyArray = []; @@ -790,7 +818,7 @@ export class TestFileService implements IFileService { encoding: 'utf8', mtime: Date.now(), isDirectory: false, - name: paths.basename(resource.fsPath) + name: resources.basename(resource) }); } @@ -799,7 +827,7 @@ export class TestFileService implements IFileService { } existsFile(_resource: URI): Promise { - throw new Error('not implemented'); + return Promise.resolve(true); } resolveContent(resource: URI, _options?: IResolveContentOptions): Promise { @@ -809,7 +837,7 @@ export class TestFileService implements IFileService { etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), - name: paths.basename(resource.fsPath) + name: resources.basename(resource) }); } @@ -829,7 +857,7 @@ export class TestFileService implements IFileService { etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), - name: paths.basename(resource.fsPath) + name: resources.basename(resource) }); } @@ -840,7 +868,7 @@ export class TestFileService implements IFileService { encoding: 'utf8', mtime: Date.now(), isDirectory: false, - name: paths.basename(resource.fsPath) + name: resources.basename(resource) })); } @@ -1448,5 +1476,5 @@ export class TestViewletService implements IViewletService { } export function getRandomTestPath(tmpdir: string, ...segments: string[]): string { - return paths.join(tmpdir, ...segments, generateUuid()); + return extpath.joinWithSlashes(tmpdir, ...segments, generateUuid()); } diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 5f2faf28af7..3d8b0dbf2c4 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'; @@ -74,7 +74,7 @@ import 'vs/workbench/contrib/stats/node/stats.contribution'; import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; // Search -import 'vs/workbench/contrib/search/electron-browser/search.contribution'; +import 'vs/workbench/contrib/search/browser/search.contribution'; import 'vs/workbench/contrib/search/browser/searchView'; import 'vs/workbench/contrib/search/browser/openAnythingHandler'; @@ -140,6 +140,9 @@ 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'; @@ -175,6 +178,9 @@ 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'; diff --git a/yarn.lock b/yarn.lock index 4a5b178d8c4..3fdec48c164 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9342,10 +9342,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-pre.0: + version "1.34.0-pre.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.34.0-pre.0.tgz#ba2110875f1127c9ce773351e1054b85fab67c00" + integrity sha512-JjrlTpUG7uFO5yctHQcx8zLbnyw30cUMVaKw0GEiski0lJ1w8A8zXP18MUqDJN7dMHqcB4r6PsuicUOgjLIFJA== vscode-fsevents@0.3.10: version "0.3.10" @@ -9380,10 +9380,10 @@ vscode-languageserver@^5.1.0: vscode-languageserver-protocol "3.13.0" vscode-uri "^1.0.6" -vscode-nls-dev@3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/vscode-nls-dev/-/vscode-nls-dev-3.2.4.tgz#9eb92a539fb1ee675836b6aeab8a2846c281c8d9" - integrity sha512-mWERxHbifhuNZomuL3iRyw2LTwKnoKoA8fjWJrlfnbOUF9EN/tbDT0ly2GnOEvLRq0hzEzZNqKLWu0jXPDs7UA== +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" @@ -9442,10 +9442,10 @@ 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.12.0-beta2: - version "3.12.0-beta2" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.12.0-beta2.tgz#fbbf6d1926e0628d4e0b85b1bfdefa2b0bf40df6" - integrity sha512-NhLYyJXjwLXXzn+NYEOkAtTxbdbAGkjztabE2Vy8j4fXXOwHHHg6KurMCbL7bd8BOEMIPrTIw6zLhzfexs5Vhw== +vscode-xterm@3.12.0-beta4: + version "3.12.0-beta4" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.12.0-beta4.tgz#b8ed223e00e6b53efea62e7c7dce5bc2ac556df3" + integrity sha512-coUhJoIIjSlIZ33VCxI6EEyEKSqet41753wfSy/Av3UCv1oiwrrCRfvLdRYC+qdabWDtVy5K7aYYDh1k7WsVeA== vso-node-api@6.1.2-preview: version "6.1.2-preview"