diff --git a/.github/commands.yml b/.github/commands.yml index c9279232529..21ebd49ec3c 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -4,7 +4,7 @@ { type: 'comment', name: 'question', - allowUsers: ['cleidigh', 'usernamehw'], + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], action: 'updateLabels', addLabel: '*question' }, @@ -60,7 +60,7 @@ { type: 'comment', name: 'duplicate', - allowUsers: ['cleidigh', 'usernamehw'], + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], action: 'updateLabels', addLabel: '*duplicate' }, @@ -74,7 +74,7 @@ { type: 'comment', name: 'confirm', - allowUsers: ['cleidigh', 'usernamehw'], + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], action: 'updateLabels', addLabel: 'confirmed', removeLabel: 'confirmation-pending' @@ -90,14 +90,14 @@ { type: 'comment', name: 'findDuplicates', - allowUsers: ['cleidigh', 'usernamehw'], + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], action: 'comment', comment: "Potential duplicates:\n${potentialDuplicates}" }, { type: 'comment', name: 'needsMoreInfo', - allowUsers: ['cleidigh', 'usernamehw'], + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], action: 'updateLabels', addLabel: 'needs more info', comment: "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 89aba8e5fe0..5db8fb3fbc5 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -81,6 +81,7 @@ "type": "gulp", "task": "hygiene", "problemMatcher": [] - } + }, + ] -} \ No newline at end of file +} diff --git a/.yarnrc b/.yarnrc index c45abdbacad..98712ee2b43 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "4.2.7" +target "4.2.9" runtime "electron" diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 1d854430864..64f72e53be0 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -7,7 +7,7 @@ This project incorporates components from the projects listed below. The origina 1. atom/language-clojure version 0.22.7 (https://github.com/atom/language-clojure) 2. atom/language-coffee-script version 0.49.3 (https://github.com/atom/language-coffee-script) -3. atom/language-java version 0.31.2 (https://github.com/atom/language-java) +3. atom/language-java version 0.31.3 (https://github.com/atom/language-java) 4. atom/language-sass version 0.61.4 (https://github.com/atom/language-sass) 5. atom/language-shellscript version 0.26.0 (https://github.com/atom/language-shellscript) 6. atom/language-xml version 0.35.2 (https://github.com/atom/language-xml) @@ -25,8 +25,8 @@ This project incorporates components from the projects listed below. The origina 18. Ikuyadeu/vscode-R version 0.5.5 (https://github.com/Ikuyadeu/vscode-R) 19. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) 20. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) -21. jeff-hykin/cpp-textmate-grammar version 1.11.0 (https://github.com/jeff-hykin/cpp-textmate-grammar) -22. jeff-hykin/cpp-textmate-grammar version 1.11.7 (https://github.com/jeff-hykin/cpp-textmate-grammar) +21. jeff-hykin/cpp-textmate-grammar version 1.12.11 (https://github.com/jeff-hykin/cpp-textmate-grammar) +22. jeff-hykin/cpp-textmate-grammar version 1.12.18 (https://github.com/jeff-hykin/cpp-textmate-grammar) 23. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) 24. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) 25. language-docker (https://github.com/moby/moby) @@ -39,7 +39,7 @@ This project incorporates components from the projects listed below. The origina 32. mdn-data version 1.1.12 (https://github.com/mdn/data) 33. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage) 34. Microsoft/vscode-JSON.tmLanguage (https://github.com/Microsoft/vscode-JSON.tmLanguage) -35. Microsoft/vscode-mssql version 1.4.0 (https://github.com/Microsoft/vscode-mssql) +35. Microsoft/vscode-mssql version 1.6.0 (https://github.com/Microsoft/vscode-mssql) 36. mmims/language-batchfile version 0.7.5 (https://github.com/mmims/language-batchfile) 37. octicons version 8.3.0 (https://github.com/primer/octicons) 38. octref/language-css version 0.42.11 (https://github.com/octref/language-css) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index b712e072849..84764519f53 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -2,11 +2,12 @@ steps: - script: | mkdir -p .build echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true @@ -106,6 +107,16 @@ steps: displayName: Run integration tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) +- script: | + set -e + cd test/smoke + yarn compile + cd - + yarn smoketest --web --headless + continueOnError: true + displayName: Run web smoke tests + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | set -e pushd ../VSCode-darwin && zip -r -X -y ../VSCode-darwin.zip * && popd diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index 6f3008f73d2..8b0aaca2ad9 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -46,9 +46,12 @@ steps: - script: | DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" displayName: Run Unit Tests +- script: | + DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" + displayName: Run Integration Tests - task: PublishTestResults@2 displayName: Publish Tests Results inputs: testResultsFiles: '*-results.xml' searchFolder: '$(Build.ArtifactStagingDirectory)/test-results' - condition: succeededOrFailed() \ No newline at end of file + condition: succeededOrFailed() diff --git a/build/azure-pipelines/linux/multiarch/arm64/build.sh b/build/azure-pipelines/linux/multiarch/arm64/build.sh new file mode 100755 index 00000000000..4f01bc9a7e5 --- /dev/null +++ b/build/azure-pipelines/linux/multiarch/arm64/build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/arm64/prebuild.sh b/build/azure-pipelines/linux/multiarch/arm64/prebuild.sh new file mode 100755 index 00000000000..4f01bc9a7e5 --- /dev/null +++ b/build/azure-pipelines/linux/multiarch/arm64/prebuild.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/multiarch/arm64/publish.sh b/build/azure-pipelines/linux/multiarch/arm64/publish.sh new file mode 100755 index 00000000000..4f01bc9a7e5 --- /dev/null +++ b/build/azure-pipelines/linux/multiarch/arm64/publish.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/product-build-linux-multiarch.yml b/build/azure-pipelines/linux/product-build-linux-multiarch.yml index dff5ae2ceaf..d36c115be78 100644 --- a/build/azure-pipelines/linux/product-build-linux-multiarch.yml +++ b/build/azure-pipelines/linux/product-build-linux-multiarch.yml @@ -2,11 +2,12 @@ steps: - script: | mkdir -p .build echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true @@ -112,4 +113,4 @@ steps: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' - continueOnError: true \ No newline at end of file + continueOnError: true diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index e97f5cfdeff..a6cc1c34965 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -2,11 +2,12 @@ steps: - script: | mkdir -p .build echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true @@ -93,16 +94,23 @@ steps: - script: | set -e - yarn gulp "electron-x64" - - # xvfb seems to be crashing often, let's make sure it's always up service xvfb start + displayName: Start xvfb + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) +- script: | + set -e DISPLAY=:10 ./scripts/test.sh --build --tfs "Unit Tests" - # yarn smoketest -- --build "$(agent.builddirectory)/VSCode-linux-x64" displayName: Run unit tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) +- script: | + set -e + DISPLAY=:10 ./scripts/test-integration.sh --build --tfs "Integration Tests" + # yarn smoketest -- --build "$(agent.builddirectory)/VSCode-linux-x64" + displayName: Run integration tests + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - script: | set -e AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 469494c7d58..f38f4311826 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -66,6 +66,17 @@ jobs: steps: - template: linux/product-build-linux-multiarch.yml +- job: LinuxArm64 + condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), eq(variables['VSCODE_BUILD_LINUX_ARM64'], 'true'), ne(variables['VSCODE_QUALITY'], 'stable')) + pool: + vmImage: 'Ubuntu-16.04' + variables: + VSCODE_ARCH: arm64 + dependsOn: + - Compile + steps: + - template: linux/product-build-linux-multiarch.yml + - job: LinuxAlpine condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), eq(variables['VSCODE_BUILD_LINUX_ALPINE'], 'true'), ne(variables['VSCODE_QUALITY'], 'stable')) pool: @@ -123,13 +134,17 @@ jobs: - LinuxSnap - LinuxArmhf - LinuxAlpine + - LinuxWeb - macOS steps: - template: sync-mooncake.yml +trigger: none +pr: none + schedules: - cron: "0 5 * * Mon-Fri" displayName: Mon-Fri at 7:00 branches: include: - - master \ No newline at end of file + - master diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index e5105326e7b..169dfe154fb 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -2,11 +2,12 @@ steps: - script: | mkdir -p .build echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true @@ -116,9 +117,9 @@ steps: - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true alias: 'Compilation' - condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) \ No newline at end of file + condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true')) diff --git a/build/azure-pipelines/upload-sourcemaps.js b/build/azure-pipelines/upload-sourcemaps.js index ac3bd55a7f5..b70335efe5b 100644 --- a/build/azure-pipelines/upload-sourcemaps.js +++ b/build/azure-pipelines/upload-sourcemaps.js @@ -13,19 +13,37 @@ const util = require('../lib/util'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); -function main() { - const vs = vfs.src('out-vscode-min/**/*.map', { base: 'out-vscode-min' }) // client source-maps only +// optionally allow to pass in explicit base/maps to upload +const [, , base, maps] = process.argv; + +const fetch = function (base, maps = `${base}/**/*.map`) { + return vfs.src(maps, { base }) .pipe(es.mapSync(f => { f.path = `${f.base}/core/${f.relative}`; return f; })); +}; - const extensionsOut = vfs.src(['.build/extensions/**/*.js.map', '!**/node_modules/**'], { base: '.build' }); +function main() { + const sources = []; - return es.merge(vs, extensionsOut) + // vscode client maps (default) + if (!base) { + const vs = fetch('out-vscode-min'); // client source-maps only + sources.push(vs); + + const extensionsOut = vfs.src(['.build/extensions/**/*.js.map', '!**/node_modules/**'], { base: '.build' }); + sources.push(extensionsOut); + } + + // specific client base/maps + else { + sources.push(fetch(base, maps)); + } + + return es.merge(...sources) .pipe(es.through(function (data) { - // debug - console.log('Uploading Sourcemap', data.relative); + console.log('Uploading Sourcemap', data.relative); // debug this.emit('data', data); })) .pipe(azure.upload({ @@ -36,4 +54,4 @@ function main() { })); } -main(); \ No newline at end of file +main(); diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 7c65dc982f0..fe5231dacc1 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -2,11 +2,12 @@ steps: - script: | mkdir -p .build echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true @@ -87,6 +88,15 @@ steps: yarn gulp vscode-web-min-ci displayName: Build + # upload only the workbench.web.api.js source maps because + # we just compiled these bits in the previous step and the + # general task to upload source maps has already been run +- script: | + set -e + AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ + node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.api.js.map + displayName: Upload sourcemaps (Web) + - script: | set -e AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ diff --git a/build/azure-pipelines/web/publish.sh b/build/azure-pipelines/web/publish.sh index 9d662fcfe8b..2c112362a25 100755 --- a/build/azure-pipelines/web/publish.sh +++ b/build/azure-pipelines/web/publish.sh @@ -7,6 +7,9 @@ ROOT="$REPO/.." WEB_BUILD_NAME="vscode-web" WEB_TARBALL_FILENAME="vscode-web.tar.gz" WEB_TARBALL_PATH="$ROOT/$WEB_TARBALL_FILENAME" +BUILD="$ROOT/$WEB_BUILD_NAME" +PACKAGEJSON="$BUILD/package.json" +VERSION=$(node -p "require(\"$PACKAGEJSON\").version") rm -rf $ROOT/vscode-web.tar.* diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 66583616c6d..71a47e63d45 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -2,11 +2,12 @@ steps: - powershell: | mkdir .build -ea 0 "$env:BUILD_SOURCEVERSION" | Out-File -Encoding ascii -NoNewLine .build\commit + "$env:VSCODE_QUALITY" | Out-File -Encoding ascii -NoNewLine .build\quality displayName: Prepare cache flag - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 inputs: - keyfile: 'build/.cachesalt, .build/commit' + keyfile: 'build/.cachesalt, .build/commit, .build/quality' targetfolder: '.build, out-build, out-vscode-min, out-vscode-reh-min, out-vscode-reh-web-min' vstsFeed: 'npm-vscode' platformIndependent: true @@ -99,6 +100,7 @@ steps: exec { yarn gulp "vscode-win32-$env:VSCODE_ARCH-min-ci" } exec { yarn gulp "vscode-reh-win32-$env:VSCODE_ARCH-min-ci" } exec { yarn gulp "vscode-reh-web-win32-$env:VSCODE_ARCH-min-ci" } + exec { yarn gulp "vscode-win32-$env:VSCODE_ARCH-code-helper" } exec { yarn gulp "vscode-win32-$env:VSCODE_ARCH-inno-updater" } displayName: Build diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index a3483f451e2..664ef4bba18 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.35.3", + "version": "1.38.3", "repo": "https://github.com/Microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.29", + "version": "0.0.30", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index f6594804322..c065556dc09 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -50,6 +50,7 @@ const indentationFilter = [ '!src/vs/css.js', '!src/vs/css.build.js', '!src/vs/loader.js', + '!src/vs/base/common/insane/insane.js', '!src/vs/base/common/marked/marked.js', '!src/vs/base/node/terminateProcess.sh', '!src/vs/base/node/cpuUsage.sh', @@ -131,24 +132,44 @@ const eslintFilter = [ '!src/vs/nls.js', '!src/vs/css.build.js', '!src/vs/nls.build.js', + '!src/**/insane.js', '!src/**/marked.js', '!**/test/**' ]; -const tslintFilter = [ - 'src/**/*.ts', - 'test/**/*.ts', - 'extensions/**/*.ts', +const tslintBaseFilter = [ '!**/fixtures/**', '!**/typings/**', '!**/node_modules/**', - '!extensions/typescript/test/colorize-fixtures/**', + '!extensions/typescript-basics/test/colorize-fixtures/**', '!extensions/vscode-api-tests/testWorkspace/**', '!extensions/vscode-api-tests/testWorkspace2/**', '!extensions/**/*.test.ts', '!extensions/html-language-features/server/lib/jquery.d.ts' ]; +const tslintCoreFilter = [ + 'src/**/*.ts', + 'test/**/*.ts', + '!extensions/**/*.ts', + '!test/smoke/**', + ...tslintBaseFilter +]; + +const tslintExtensionsFilter = [ + 'extensions/**/*.ts', + '!src/**/*.ts', + '!test/**/*.ts', + ...tslintBaseFilter +]; + +const tslintHygieneFilter = [ + 'src/**/*.ts', + 'test/**/*.ts', + 'extensions/**/*.ts', + ...tslintBaseFilter +]; + const copyrightHeaderLines = [ '/*---------------------------------------------------------------------------------------------', ' * Copyright (c) Microsoft Corporation. All rights reserved.', @@ -165,12 +186,20 @@ gulp.task('eslint', () => { }); gulp.task('tslint', () => { - const options = { emitError: true }; + return es.merge([ - return vfs.src(all, { base: '.', follow: true, allowEmpty: true }) - .pipe(filter(tslintFilter)) - .pipe(gulptslint.default({ rulesDirectory: 'build/lib/tslint' })) - .pipe(gulptslint.default.report(options)); + // Core: include type information (required by certain rules like no-nodejs-globals) + vfs.src(all, { base: '.', follow: true, allowEmpty: true }) + .pipe(filter(tslintCoreFilter)) + .pipe(gulptslint.default({ rulesDirectory: 'build/lib/tslint', program: tslint.Linter.createProgram('src/tsconfig.json') })) + .pipe(gulptslint.default.report({ emitError: true })), + + // Exenstions: do not include type information + vfs.src(all, { base: '.', follow: true, allowEmpty: true }) + .pipe(filter(tslintExtensionsFilter)) + .pipe(gulptslint.default({ rulesDirectory: 'build/lib/tslint' })) + .pipe(gulptslint.default.report({ emitError: true })) + ]); }); function hygiene(some) { @@ -283,7 +312,7 @@ function hygiene(some) { .pipe(copyrights); const typescript = result - .pipe(filter(tslintFilter)) + .pipe(filter(tslintHygieneFilter)) .pipe(formatting) .pipe(tsl); diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 7085c4f8f50..a956fba979e 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -31,6 +31,7 @@ const BUILD_TARGETS = [ { platform: 'linux', arch: 'ia32', pkgTarget: 'node8-linux-x86' }, { platform: 'linux', arch: 'x64', pkgTarget: 'node8-linux-x64' }, { platform: 'linux', arch: 'armhf', pkgTarget: 'node8-linux-armv7' }, + { platform: 'linux', arch: 'arm64', pkgTarget: 'node8-linux-arm64' }, { platform: 'linux', arch: 'alpine', pkgTarget: 'node8-linux-alpine' }, ]; @@ -41,6 +42,7 @@ gulp.task('vscode-reh-win32-x64-min', noop); gulp.task('vscode-reh-darwin-min', noop); gulp.task('vscode-reh-linux-x64-min', noop); gulp.task('vscode-reh-linux-armhf-min', noop); +gulp.task('vscode-reh-linux-arm64-min', noop); gulp.task('vscode-reh-linux-alpine-min', noop); gulp.task('vscode-reh-web-win32-ia32-min', noop); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 99bd930a91c..98f776abb7e 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -47,7 +47,7 @@ const nodeModules = ['electron', 'original-fs'] // Build const vscodeEntryPoints = _.flatten([ - buildfile.entrypoint('vs/workbench/workbench.main'), + buildfile.entrypoint('vs/workbench/workbench.desktop.main'), buildfile.base, buildfile.serviceWorker, buildfile.workbench, @@ -250,8 +250,8 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const out = sourceFolderName; const checksums = computeChecksums(out, [ - 'vs/workbench/workbench.main.js', - 'vs/workbench/workbench.main.css', + 'vs/workbench/workbench.desktop.main.js', + 'vs/workbench/workbench.desktop.main.css', 'vs/code/electron-browser/workbench/workbench.html', 'vs/code/electron-browser/workbench/workbench.js' ]); @@ -365,7 +365,15 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(electron(_.extend({}, config, { platform, arch, ffmpegChromium: true }))) .pipe(filter(['**', '!LICENSE', '!LICENSES.chromium.html', '!version'], { dot: true })); - // result = es.merge(result, gulp.src('resources/completions/**', { base: '.' })); + if (platform === 'linux') { + result = es.merge(result, gulp.src('resources/completions/bash/code', { base: '.' }) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename(function (f) { f.basename = product.applicationName; }))); + + result = es.merge(result, gulp.src('resources/completions/zsh/_code', { base: '.' }) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename(function (f) { f.basename = '_' + product.applicationName; }))); + } if (platform === 'win32') { result = es.merge(result, gulp.src('resources/win32/bin/code.js', { base: 'resources/win32', allowEmpty: true })); diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index a718cfb0082..54a270695bd 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -54,11 +54,13 @@ function prepareDebPackage(arch) { const icon = gulp.src('resources/linux/code.png', { base: '.' }) .pipe(rename('usr/share/pixmaps/' + product.linuxIconName + '.png')); - // const bash_completion = gulp.src('resources/completions/bash/code') - // .pipe(rename('usr/share/bash-completion/completions/code')); + const bash_completion = gulp.src('resources/completions/bash/code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('usr/share/bash-completion/completions/' + product.applicationName)); - // const zsh_completion = gulp.src('resources/completions/zsh/_code') - // .pipe(rename('usr/share/zsh/vendor-completions/_code')); + const zsh_completion = gulp.src('resources/completions/zsh/_code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('usr/share/zsh/vendor-completions/_' + product.applicationName)); const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) .pipe(rename(function (p) { p.dirname = 'usr/share/' + product.applicationName + '/' + p.dirname; })); @@ -94,7 +96,7 @@ function prepareDebPackage(arch) { .pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@')) .pipe(rename('DEBIAN/postinst')); - const all = es.merge(control, postinst, postrm, prerm, desktops, appdata, icon, /* bash_completion, zsh_completion, */ code); + const all = es.merge(control, postinst, postrm, prerm, desktops, appdata, icon, bash_completion, zsh_completion, code); return all.pipe(vfs.dest(destination)); }; @@ -144,11 +146,13 @@ function prepareRpmPackage(arch) { const icon = gulp.src('resources/linux/code.png', { base: '.' }) .pipe(rename('BUILD/usr/share/pixmaps/' + product.linuxIconName + '.png')); - // const bash_completion = gulp.src('resources/completions/bash/code') - // .pipe(rename('BUILD/usr/share/bash-completion/completions/code')); + const bash_completion = gulp.src('resources/completions/bash/code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('BUILD/usr/share/bash-completion/completions/' + product.applicationName)); - // const zsh_completion = gulp.src('resources/completions/zsh/_code') - // .pipe(rename('BUILD/usr/share/zsh/site-functions/_code')); + const zsh_completion = gulp.src('resources/completions/zsh/_code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('BUILD/usr/share/zsh/site-functions/_' + product.applicationName)); const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) .pipe(rename(function (p) { p.dirname = 'BUILD/usr/share/' + product.applicationName + '/' + p.dirname; })); @@ -171,7 +175,7 @@ function prepareRpmPackage(arch) { const specIcon = gulp.src('resources/linux/rpm/code.xpm', { base: '.' }) .pipe(rename('SOURCES/' + product.applicationName + '.xpm')); - const all = es.merge(code, desktops, appdata, icon, /* bash_completion, zsh_completion, */ spec, specIcon); + const all = es.merge(code, desktops, appdata, icon, bash_completion, zsh_completion, spec, specIcon); return all.pipe(vfs.dest(getRpmBuildPath(rpmArch))); }; diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index 11da75e482d..8f63d7b7d0b 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -41,6 +41,9 @@ const vscodeWebResources = [ // Webview 'out-build/vs/workbench/contrib/webview/browser/pre/*.js', + // Extension Worker + 'out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js', + // Excludes '!out-build/vs/**/{node,electron-browser,electron-main}/**', '!out-build/vs/editor/standalone/**', @@ -53,6 +56,7 @@ const buildfile = require('../src/buildfile'); const vscodeWebEntryPoints = [ buildfile.workbenchWeb, buildfile.serviceWorker, + buildfile.workerExtensionHost, buildfile.keyboardMaps, buildfile.base ]; @@ -148,4 +152,4 @@ const dashed = (str) => (str ? `-${str}` : ``); vscodeWebTaskCI )); gulp.task(vscodeWebTask); -}); \ No newline at end of file +}); diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index b20efebdb62..497fc553c03 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -135,12 +135,17 @@ function copyInnoUpdater(arch) { }; } -function patchInnoUpdater(arch) { +function updateIcon(executablePath) { return cb => { const icon = path.join(repoPath, 'resources', 'win32', 'code.ico'); - rcedit(path.join(buildPath(arch), 'tools', 'inno_updater.exe'), { icon }, cb); + rcedit(executablePath, { icon }, cb); }; } -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 +gulp.task(task.define('vscode-win32-ia32-inno-updater', task.series(copyInnoUpdater('ia32'), updateIcon(path.join(buildPath('ia32'), 'tools', 'inno_updater.exe'))))); +gulp.task(task.define('vscode-win32-x64-inno-updater', task.series(copyInnoUpdater('x64'), updateIcon(path.join(buildPath('x64'), 'tools', 'inno_updater.exe'))))); + +// CodeHelper.exe icon + +gulp.task(task.define('vscode-win32-ia32-code-helper', task.series(updateIcon(path.join(buildPath('ia32'), 'resources', 'app', 'out', 'vs', 'platform', 'files', 'node', 'watcher', 'win32', 'CodeHelper.exe'))))); +gulp.task(task.define('vscode-win32-x64-code-helper', task.series(updateIcon(path.join(buildPath('x64'), 'resources', 'app', 'out', 'vs', 'platform', 'files', 'node', 'watcher', 'win32', 'CodeHelper.exe'))))); diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 73d2c7acef3..a98f3b890cf 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -29,12 +29,18 @@ const commit = util.getVersion(root); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; function fromLocal(extensionPath) { const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js'); - if (fs.existsSync(webpackFilename)) { - return fromLocalWebpack(extensionPath); - } - else { - return fromLocalNormal(extensionPath); - } + const input = fs.existsSync(webpackFilename) + ? fromLocalWebpack(extensionPath) + : fromLocalNormal(extensionPath); + const tmLanguageJsonFilter = filter('**/*.tmLanguage.json', { restore: true }); + return input + .pipe(tmLanguageJsonFilter) + .pipe(buffer()) + .pipe(es.mapSync((f) => { + f.contents = Buffer.from(JSON.stringify(JSON.parse(f.contents.toString('utf8')))); + return f; + })) + .pipe(tmLanguageJsonFilter.restore); } function fromLocalWebpack(extensionPath) { const result = es.through(); diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 4b185aff681..05bc7094846 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -30,11 +30,20 @@ const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${ function fromLocal(extensionPath: string): Stream { const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js'); - if (fs.existsSync(webpackFilename)) { - return fromLocalWebpack(extensionPath); - } else { - return fromLocalNormal(extensionPath); - } + const input = fs.existsSync(webpackFilename) + ? fromLocalWebpack(extensionPath) + : fromLocalNormal(extensionPath); + + const tmLanguageJsonFilter = filter('**/*.tmLanguage.json', { restore: true }); + + return input + .pipe(tmLanguageJsonFilter) + .pipe(buffer()) + .pipe(es.mapSync((f: File) => { + f.contents = Buffer.from(JSON.stringify(JSON.parse(f.contents.toString('utf8')))); + return f; + })) + .pipe(tmLanguageJsonFilter.restore); } function fromLocalWebpack(extensionPath: string): Stream { diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 32b0e6fb4c7..570d97669c7 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -269,6 +269,10 @@ { "name": "vs/workbench/services/preferences", "project": "vscode-preferences" + }, + { + "name": "vs/workbench/services/notification", + "project": "vscode-workbench" } ] } diff --git a/build/lib/tslint/abstractGlobalsRule.js b/build/lib/tslint/abstractGlobalsRule.js new file mode 100644 index 00000000000..2aadf2c3bd9 --- /dev/null +++ b/build/lib/tslint/abstractGlobalsRule.js @@ -0,0 +1,40 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const Lint = require("tslint"); +class AbstractGlobalsRuleWalker extends Lint.RuleWalker { + constructor(file, program, opts, _config) { + super(file, opts); + this.program = program; + this._config = _config; + } + visitIdentifier(node) { + if (this.getDisallowedGlobals().some(disallowedGlobal => disallowedGlobal === node.text)) { + if (this._config.allowed && this._config.allowed.some(allowed => allowed === node.text)) { + return; // override + } + const checker = this.program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); + if (symbol) { + const valueDeclaration = symbol.valueDeclaration; + if (valueDeclaration) { + const parent = valueDeclaration.parent; + if (parent) { + const sourceFile = parent.getSourceFile(); + if (sourceFile) { + const fileName = sourceFile.fileName; + if (fileName && fileName.indexOf(this.getDefinitionPattern()) >= 0) { + this.addFailureAtNode(node, `Cannot use global '${node.text}' in '${this._config.target}'`); + } + } + } + } + } + } + super.visitIdentifier(node); + } +} +exports.AbstractGlobalsRuleWalker = AbstractGlobalsRuleWalker; diff --git a/build/lib/tslint/abstractGlobalsRule.ts b/build/lib/tslint/abstractGlobalsRule.ts new file mode 100644 index 00000000000..8c80b3e0cfd --- /dev/null +++ b/build/lib/tslint/abstractGlobalsRule.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as ts from 'typescript'; +import * as Lint from 'tslint'; + +interface AbstractGlobalsRuleConfig { + target: string; + allowed: string[]; +} + +export abstract class AbstractGlobalsRuleWalker extends Lint.RuleWalker { + + constructor(file: ts.SourceFile, private program: ts.Program, opts: Lint.IOptions, private _config: AbstractGlobalsRuleConfig) { + super(file, opts); + } + + protected abstract getDisallowedGlobals(): string[]; + + protected abstract getDefinitionPattern(): string; + + visitIdentifier(node: ts.Identifier) { + if (this.getDisallowedGlobals().some(disallowedGlobal => disallowedGlobal === node.text)) { + if (this._config.allowed && this._config.allowed.some(allowed => allowed === node.text)) { + return; // override + } + + const checker = this.program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); + if (symbol) { + const valueDeclaration = symbol.valueDeclaration; + if (valueDeclaration) { + const parent = valueDeclaration.parent; + if (parent) { + const sourceFile = parent.getSourceFile(); + if (sourceFile) { + const fileName = sourceFile.fileName; + if (fileName && fileName.indexOf(this.getDefinitionPattern()) >= 0) { + this.addFailureAtNode(node, `Cannot use global '${node.text}' in '${this._config.target}'`); + } + } + } + } + } + } + + super.visitIdentifier(node); + } +} diff --git a/build/lib/tslint/noDOMGlobalsRule.js b/build/lib/tslint/noDOMGlobalsRule.js new file mode 100644 index 00000000000..a83ac8f7f59 --- /dev/null +++ b/build/lib/tslint/noDOMGlobalsRule.js @@ -0,0 +1,34 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const Lint = require("tslint"); +const minimatch = require("minimatch"); +const abstractGlobalsRule_1 = require("./abstractGlobalsRule"); +class Rule extends Lint.Rules.TypedRule { + applyWithProgram(sourceFile, program) { + const configs = this.getOptions().ruleArguments; + for (const config of configs) { + if (minimatch(sourceFile.fileName, config.target)) { + return this.applyWithWalker(new NoDOMGlobalsRuleWalker(sourceFile, program, this.getOptions(), config)); + } + } + return []; + } +} +exports.Rule = Rule; +class NoDOMGlobalsRuleWalker extends abstractGlobalsRule_1.AbstractGlobalsRuleWalker { + getDefinitionPattern() { + return 'lib.dom.d.ts'; + } + getDisallowedGlobals() { + // intentionally not complete + return [ + "window", + "document", + "HTMLElement" + ]; + } +} diff --git a/build/lib/tslint/noDOMGlobalsRule.ts b/build/lib/tslint/noDOMGlobalsRule.ts new file mode 100644 index 00000000000..df9e67bf78b --- /dev/null +++ b/build/lib/tslint/noDOMGlobalsRule.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + +import * as ts from 'typescript'; +import * as Lint from 'tslint'; +import * as minimatch from 'minimatch'; +import { AbstractGlobalsRuleWalker } from './abstractGlobalsRule'; + +interface NoDOMGlobalsRuleConfig { + target: string; + allowed: string[]; +} + +export class Rule extends Lint.Rules.TypedRule { + + applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + const configs = this.getOptions().ruleArguments; + + for (const config of configs) { + if (minimatch(sourceFile.fileName, config.target)) { + return this.applyWithWalker(new NoDOMGlobalsRuleWalker(sourceFile, program, this.getOptions(), config)); + } + } + + return []; + } +} + +class NoDOMGlobalsRuleWalker extends AbstractGlobalsRuleWalker { + + getDefinitionPattern(): string { + return 'lib.dom.d.ts'; + } + + getDisallowedGlobals(): string[] { + // intentionally not complete + return [ + "window", + "document", + "HTMLElement" + ]; + } +} diff --git a/build/lib/tslint/noNodeJSGlobalsRule.js b/build/lib/tslint/noNodeJSGlobalsRule.js new file mode 100644 index 00000000000..1e955a41e1b --- /dev/null +++ b/build/lib/tslint/noNodeJSGlobalsRule.js @@ -0,0 +1,40 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const Lint = require("tslint"); +const minimatch = require("minimatch"); +const abstractGlobalsRule_1 = require("./abstractGlobalsRule"); +class Rule extends Lint.Rules.TypedRule { + applyWithProgram(sourceFile, program) { + const configs = this.getOptions().ruleArguments; + for (const config of configs) { + if (minimatch(sourceFile.fileName, config.target)) { + return this.applyWithWalker(new NoNodejsGlobalsRuleWalker(sourceFile, program, this.getOptions(), config)); + } + } + return []; + } +} +exports.Rule = Rule; +class NoNodejsGlobalsRuleWalker extends abstractGlobalsRule_1.AbstractGlobalsRuleWalker { + getDefinitionPattern() { + return '@types/node'; + } + getDisallowedGlobals() { + // https://nodejs.org/api/globals.html#globals_global_objects + return [ + "Buffer", + "__dirname", + "__filename", + "clearImmediate", + "exports", + "global", + "module", + "process", + "setImmediate" + ]; + } +} diff --git a/build/lib/tslint/noNodeJSGlobalsRule.ts b/build/lib/tslint/noNodeJSGlobalsRule.ts new file mode 100644 index 00000000000..48b229333e9 --- /dev/null +++ b/build/lib/tslint/noNodeJSGlobalsRule.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as ts from 'typescript'; +import * as Lint from 'tslint'; +import * as minimatch from 'minimatch'; +import { AbstractGlobalsRuleWalker } from './abstractGlobalsRule'; + +interface NoNodejsGlobalsConfig { + target: string; + allowed: string[]; +} + +export class Rule extends Lint.Rules.TypedRule { + + applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] { + const configs = this.getOptions().ruleArguments; + + for (const config of configs) { + if (minimatch(sourceFile.fileName, config.target)) { + return this.applyWithWalker(new NoNodejsGlobalsRuleWalker(sourceFile, program, this.getOptions(), config)); + } + } + + return []; + } +} + +class NoNodejsGlobalsRuleWalker extends AbstractGlobalsRuleWalker { + + getDefinitionPattern(): string { + return '@types/node'; + } + + getDisallowedGlobals(): string[] { + // https://nodejs.org/api/globals.html#globals_global_objects + return [ + "Buffer", + "__dirname", + "__filename", + "clearImmediate", + "exports", + "global", + "module", + "process", + "setImmediate" + ]; + } +} diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index cd41092192c..6b1159bc65c 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -78,4 +78,42 @@ const processTreeDts = path.join('node_modules', 'windows-process-tree', 'typing if (fs.existsSync(processTreeDts)) { console.log('Removing windows-process-tree.d.ts'); fs.unlinkSync(processTreeDts); -} \ No newline at end of file +} + +function getInstalledVersion(packageName, cwd) { + const opts = {}; + if (cwd) { + opts.cwd = cwd; + } + + const result = cp.spawnSync(yarn, ['list', '--pattern', packageName], opts); + const stdout = result.stdout.toString(); + const match = stdout.match(new RegExp(packageName + '@(\\S+)')); + if (!match || !match[1]) { + throw new Error('Unexpected output from yarn list: ' + stdout); + } + + return match[1]; +} + +function assertSameVersionsBetweenFolders(packageName, otherFolder) { + const baseVersion = getInstalledVersion(packageName); + const otherVersion = getInstalledVersion(packageName, otherFolder); + + if (baseVersion !== otherVersion) { + throw new Error(`Mismatched versions installed for ${packageName}: root has ${baseVersion}, ./${otherFolder} has ${otherVersion}. These should be the same!`); + } +} + +// Check that modules in both the base package.json and remote/ have the same version installed +const requireSameVersionsInRemote = [ + 'xterm', + 'xterm-addon-search', + 'xterm-addon-web-links', + 'node-pty', + 'vscode-ripgrep' +]; + +requireSameVersionsInRemote.forEach(packageName => { + assertSameVersionsBetweenFolders(packageName, 'remote'); +}); diff --git a/build/npm/update-grammar.js b/build/npm/update-grammar.js index 5321b7b1dcd..ff568a613c8 100644 --- a/build/npm/update-grammar.js +++ b/build/npm/update-grammar.js @@ -132,7 +132,7 @@ exports.update = function (repoId, repoPath, dest, modifyGrammar, version = 'mas if (packageJsonPathOverride) { packageJsonPath += packageJsonPathOverride; } - packageJsonPath += '/package.json'; + packageJsonPath += 'package.json'; for (let i = 0; i < cgmanifestRead.registrations.length; i++) { if (cgmanifestRead.registrations[i].component.git.repositoryUrl.substr(cgmanifestRead.registrations[i].component.git.repositoryUrl.length - repoId.length, repoId.length) === repoId) { cgmanifestRead.registrations[i].component.git.commitHash = info.commitSha; diff --git a/build/package.json b/build/package.json index a382150c3f0..75629888620 100644 --- a/build/package.json +++ b/build/package.json @@ -42,7 +42,7 @@ "tslint": "^5.9.1", "typescript": "3.5.2", "vsce": "1.48.0", - "vscode-telemetry-extractor": "1.5.3", + "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" }, "scripts": { diff --git a/build/win32/code.iss b/build/win32/code.iss index 831b31a3c71..ee70efb974d 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -19,8 +19,8 @@ Compression=lzma SolidCompression=yes AppMutex={code:GetAppMutex} SetupMutex={#AppMutex}setup -WizardImageFile={#RepoDir}\resources\win32\inno-big.bmp -WizardSmallImageFile={#RepoDir}\resources\win32\inno-small.bmp +WizardImageFile="{#RepoDir}\resources\win32\inno-big-100.bmp,{#RepoDir}\resources\win32\inno-big-125.bmp,{#RepoDir}\resources\win32\inno-big-150.bmp,{#RepoDir}\resources\win32\inno-big-175.bmp,{#RepoDir}\resources\win32\inno-big-200.bmp,{#RepoDir}\resources\win32\inno-big-225.bmp,{#RepoDir}\resources\win32\inno-big-250.bmp" +WizardSmallImageFile="{#RepoDir}\resources\win32\inno-small-100.bmp,{#RepoDir}\resources\win32\inno-small-125.bmp,{#RepoDir}\resources\win32\inno-small-150.bmp,{#RepoDir}\resources\win32\inno-small-175.bmp,{#RepoDir}\resources\win32\inno-small-200.bmp,{#RepoDir}\resources\win32\inno-small-225.bmp,{#RepoDir}\resources\win32\inno-small-250.bmp" SetupIconFile={#RepoDir}\resources\win32\code.ico UninstallDisplayIcon={app}\{#ExeBasename}.exe ChangesEnvironment=true diff --git a/build/yarn.lock b/build/yarn.lock index 23ac4d9b84d..7e4f11d8c60 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2313,19 +2313,19 @@ vsce@1.48.0: yauzl "^2.3.1" yazl "^2.2.2" -vscode-ripgrep@^1.5.5: - version "1.5.5" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.5.tgz#24c0e9cb356cf889c98e15ecb58f9cf654a1d961" - integrity sha512-OrPrAmcun4+uZAuNcQvE6CCPskh+5AsjANod/Q3zRcJcGNxgoOSGlQN9RPtatkUNmkN8Nn8mZBnS1jMylu/dKg== +vscode-ripgrep@^1.5.6: + version "1.5.6" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.6.tgz#93bf5c99ca5f8248950a305e224f6ca153c30af4" + integrity sha512-WRIM9XpUj6dsfdAmuI3ANbmT1ysPUVsYy/2uCLDHJa9kbiB4T7uGvFnnc0Rgx2qQnyRAwL7PeWaFgUljPPxf2g== -vscode-telemetry-extractor@1.5.3: - version "1.5.3" - resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.5.3.tgz#c17f9065a47425edafd23ea161e80c23274e009d" - integrity sha512-feioJ1e1KyMa9rzblnLbSOduo+Ny0l62H3/bSDgfgCSnU/km+tTSYxPBvZHVr7iQfQGC95J61yC/ObqS9EbaQg== +vscode-telemetry-extractor@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/vscode-telemetry-extractor/-/vscode-telemetry-extractor-1.5.4.tgz#bcb0d17667fa1b77715e3a3bf372ade18f846782" + integrity sha512-MN9LNPo0Rc6cy3sIWTAG97PTWkEKdRnP0VeYoS8vjKSNtG9CAsrUxHgFfYoHm2vNK/ijd0a4NzETyVGO2kT6hw== dependencies: command-line-args "^5.1.1" ts-morph "^3.1.3" - vscode-ripgrep "^1.5.5" + vscode-ripgrep "^1.5.6" vso-node-api@6.1.2-preview: version "6.1.2-preview" diff --git a/cglicenses.json b/cglicenses.json index 469cdedc9ad..b7f408109bf 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -743,5 +743,31 @@ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", "THE SOFTWARE." ] + }, + { + "name": "atob", + "licenseDetail": [ + "The MIT License (MIT)", + "", + "Copyright (c) 2015 AJ ONeal", + "", + "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." + ] } ] diff --git a/cgmanifest.json b/cgmanifest.json index 638303702ae..5ba2e166267 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "36ea114ac0616e469e75ae94e6d53af48925e036" + "commitHash": "3d4d6454007f14fa9a5f0e1fa49206fb91b676cc" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "4.2.7" + "version": "4.2.9" }, { "component": { diff --git a/extensions/cpp/build/update-grammars.js b/extensions/cpp/build/update-grammars.js index 29761cdaf95..d02d53810ca 100644 --- a/extensions/cpp/build/update-grammars.js +++ b/extensions/cpp/build/update-grammars.js @@ -6,8 +6,8 @@ var updateGrammar = require('../../../build/npm/update-grammar'); -updateGrammar.update('jeff-hykin/cpp-textmate-grammar', '/syntaxes/c.tmLanguage.json', './syntaxes/c.tmLanguage.json', undefined, 'master', 'source/languages/cpp'); -updateGrammar.update('jeff-hykin/cpp-textmate-grammar', '/syntaxes/cpp.tmLanguage.json', './syntaxes/cpp.tmLanguage.json', undefined, 'master', 'source/languages/cpp'); +updateGrammar.update('jeff-hykin/cpp-textmate-grammar', '/syntaxes/c.tmLanguage.json', './syntaxes/c.tmLanguage.json', undefined, 'master', 'source/languages/cpp/'); +updateGrammar.update('jeff-hykin/cpp-textmate-grammar', '/syntaxes/cpp.tmLanguage.json', './syntaxes/cpp.tmLanguage.json', undefined, 'master', 'source/languages/cpp/'); // `source.c.platform` which is still included by other grammars updateGrammar.update('textmate/c.tmbundle', 'Syntaxes/Platform.tmLanguage', './syntaxes/platform.tmLanguage.json'); diff --git a/extensions/cpp/cgmanifest.json b/extensions/cpp/cgmanifest.json index 6716fa1a223..b8eb0579838 100644 --- a/extensions/cpp/cgmanifest.json +++ b/extensions/cpp/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jeff-hykin/cpp-textmate-grammar", "repositoryUrl": "https://github.com/jeff-hykin/cpp-textmate-grammar", - "commitHash": "992c5ba8789288707f2a13778452d644677d9699" + "commitHash": "031ef619bef4c5a1ca46e6fa69d7c913e0c32068" } }, "license": "MIT", - "version": "1.12.18", + "version": "1.13.2", "description": "The files syntaxes/c.json and syntaxes/c++.json were derived from https://github.com/atom/language-c which was originally converted from the C TextMate bundle https://github.com/textmate/c.tmbundle." }, { diff --git a/extensions/cpp/syntaxes/c.tmLanguage.json b/extensions/cpp/syntaxes/c.tmLanguage.json index 91be1a66bdf..fbfac34d19d 100644 --- a/extensions/cpp/syntaxes/c.tmLanguage.json +++ b/extensions/cpp/syntaxes/c.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/jeff-hykin/cpp-textmate-grammar/commit/1a24b4aa383169919f0d92cf2735ac35a3e7db3c", + "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/218448eb46260864352d569db13be6cb20767e92", "name": "C", "scopeName": "source.c", "patterns": [ @@ -67,32 +67,71 @@ "include": "#strings" }, { - "begin": "(?x)\n^\\s* ((\\#)\\s*define) \\s+\t# define\n((?[a-zA-Z_$][\\w$]*))\t # macro name\n(?:\n (\\()\n\t(\n\t \\s* \\g \\s*\t\t # first argument\n\t ((,) \\s* \\g \\s*)* # additional arguments\n\t (?:\\.\\.\\.)?\t\t\t# varargs ellipsis?\n\t)\n (\\))\n)?", + "name": "meta.preprocessor.macro.c", + "begin": "((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((#)\\s*define\\b)\\s+((?(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/))", + "captures": { + "1": { + "name": "comment.block.c punctuation.definition.comment.begin.c" + }, + "2": { + "name": "comment.block.c" + }, + "3": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.c punctuation.definition.comment.end.c" + }, + { + "match": "\\*", + "name": "comment.block.c" + } + ] + } + } + }, "default_statement": { "name": "meta.conditional.case.c", "begin": "((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(#)\\s*pragma\\s+mark)\\s+(.*)", + "captures": { + "1": { + "name": "keyword.control.directive.pragma.pragma-mark.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.definition.directive.cpp" + }, + "7": { + "name": "entity.name.tag.pragma-mark.cpp" + } + }, + "name": "meta.preprocessor.pragma.cpp" + }, + "pragma": { + "name": "meta.preprocessor.pragma.cpp", + "begin": "((?:^)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(#)\\s*pragma\\b)", + "beginCaptures": { + "1": { + "name": "keyword.control.directive.pragma.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.definition.directive.cpp" + } + }, + "end": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((#)\\s*((?:(?:include|include_next)|import))\\b)\\s*(?:(?:(?:((<)[^>]*(>?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:\\n|$)|(?=\\/\\/)))|((\\\")[^\\\"]*(\\\"?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:\\n|$)|(?=\\/\\/))))|((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:\\n|$)|(?=\\/\\/))))|((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:\\n|$)|(?=\\/\\/)))", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "5": { + "name": "keyword.control.directive.$7.cpp" + }, + "6": { + "name": "punctuation.definition.directive.cpp" + }, + "8": { + "name": "string.quoted.other.lt-gt.include.cpp" + }, + "9": { + "name": "punctuation.definition.string.begin.cpp" + }, + "10": { + "name": "punctuation.definition.string.end.cpp" + }, + "11": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "12": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "13": { + "name": "comment.block.cpp" + }, + "14": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "15": { + "name": "string.quoted.double.include.cpp" + }, + "16": { + "name": "punctuation.definition.string.begin.cpp" + }, + "17": { + "name": "punctuation.definition.string.end.cpp" + }, + "18": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "19": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "20": { + "name": "comment.block.cpp" + }, + "21": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "22": { + "name": "entity.name.other.preprocessor.macro.include.cpp" + }, + "23": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "24": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "25": { + "name": "comment.block.cpp" + }, + "26": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "27": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "28": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "29": { + "name": "comment.block.cpp" + }, + "30": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "meta.preprocessor.include.cpp" + }, + "line": { + "name": "meta.preprocessor.line.cpp", + "begin": "((?:^)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(#)\\s*line\\b)", + "beginCaptures": { + "1": { + "name": "keyword.control.directive.line.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.definition.directive.cpp" + } + }, + "end": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(#)\\s*((?:error|warning)))\\b\\s*", + "beginCaptures": { + "1": { + "name": "keyword.control.directive.diagnostic.$7.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.definition.directive.cpp" + } + }, + "end": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(#)\\s*undef\\b)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))#define.*(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(#)\\s*define\\b)\\s*((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(#)\\s*((?:(?:ifndef|ifdef)|if)))", + "beginCaptures": { + "1": { + "name": "keyword.control.directive.conditional.$7.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "6": { + "name": "punctuation.definition.directive.cpp" + } + }, + "patterns": [ + { + "name": "meta.preprocessor.conditional.cpp", + "begin": "\\G(?<=ifndef|ifdef|if)", + "end": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(#)\\s*((?]*|[^>]*+<[^>]*+>)++>\\s*", + "match": "((?(?:(?>[^<>]*)\\g<1>?)+)>)\\s*", "captures": { "0": { "name": "meta.template.call.cpp", @@ -2078,7 +3070,7 @@ "name": "storage.type.template.cpp" }, "9": { - "name": "ellipses.cpp punctuation.vararg-ellipses.template.definition.cpp" + "name": "punctuation.vararg-ellipses.template.definition.cpp" }, "10": { "name": "entity.name.type.template.cpp" @@ -2089,7 +3081,7 @@ } }, "scope_resolution": { - "match": "(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -2097,11 +3089,22 @@ "include": "#scope_resolution_inner_generated" } ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] } } }, "scope_resolution_inner_generated": { - "match": "((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -2111,9 +3114,9 @@ ] }, "2": { - "name": "entity.name.scope-resolution.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2121,13 +3124,24 @@ } ] }, - "4": { + "6": { + "name": "entity.name.scope-resolution.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" } } }, "scope_resolution_template_call": { - "match": "(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -2135,11 +3149,22 @@ "include": "#scope_resolution_template_call_inner_generated" } ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] } } }, "scope_resolution_template_call_inner_generated": { - "match": "((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -2149,9 +3174,9 @@ ] }, "2": { - "name": "entity.name.scope-resolution.template.call.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2159,13 +3184,24 @@ } ] }, - "4": { + "6": { + "name": "entity.name.scope-resolution.template.call.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" } } }, "scope_resolution_template_definition": { - "match": "(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -2173,11 +3209,22 @@ "include": "#scope_resolution_template_definition_inner_generated" } ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] } } }, "scope_resolution_template_definition_inner_generated": { - "match": "((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -2187,9 +3234,9 @@ ] }, "2": { - "name": "entity.name.scope-resolution.template.definition.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2197,13 +3244,24 @@ } ] }, - "4": { + "6": { + "name": "entity.name.scope-resolution.template.definition.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" } } }, "scope_resolution_function_call": { - "match": "(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -2211,11 +3269,22 @@ "include": "#scope_resolution_function_call_inner_generated" } ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] } } }, "scope_resolution_function_call_inner_generated": { - "match": "((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -2225,9 +3294,9 @@ ] }, "2": { - "name": "entity.name.scope-resolution.function.call.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2235,13 +3304,24 @@ } ] }, - "4": { + "6": { + "name": "entity.name.scope-resolution.function.call.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" } } }, "scope_resolution_function_definition": { - "match": "(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -2249,11 +3329,22 @@ "include": "#scope_resolution_function_definition_inner_generated" } ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] } } }, "scope_resolution_function_definition_inner_generated": { - "match": "((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -2263,9 +3354,9 @@ ] }, "2": { - "name": "entity.name.scope-resolution.function.definition.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2273,13 +3364,24 @@ } ] }, - "4": { + "6": { + "name": "entity.name.scope-resolution.function.definition.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.cpp" } } }, "scope_resolution_namespace_alias": { - "match": "(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -2287,11 +3389,22 @@ "include": "#scope_resolution_namespace_alias_inner_generated" } ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] } } }, "scope_resolution_namespace_alias_inner_generated": { - "match": "((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -2301,9 +3414,9 @@ ] }, "2": { - "name": "entity.name.scope-resolution.namespace.alias.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2311,13 +3424,24 @@ } ] }, - "4": { + "6": { + "name": "entity.name.scope-resolution.namespace.alias.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.alias.cpp" } } }, "scope_resolution_namespace_using": { - "match": "(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -2325,11 +3449,22 @@ "include": "#scope_resolution_namespace_using_inner_generated" } ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] } } }, "scope_resolution_namespace_using_inner_generated": { - "match": "((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -2339,9 +3474,9 @@ ] }, "2": { - "name": "entity.name.scope-resolution.namespace.using.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2349,13 +3484,24 @@ } ] }, - "4": { + "6": { + "name": "entity.name.scope-resolution.namespace.using.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.using.cpp" } } }, "scope_resolution_namespace_block": { - "match": "(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -2363,11 +3509,22 @@ "include": "#scope_resolution_namespace_block_inner_generated" } ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] } } }, "scope_resolution_namespace_block_inner_generated": { - "match": "((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -2377,9 +3534,9 @@ ] }, "2": { - "name": "entity.name.scope-resolution.namespace.block.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2387,13 +3544,24 @@ } ] }, - "4": { + "6": { + "name": "entity.name.scope-resolution.namespace.block.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.namespace.block.cpp" } } }, "scope_resolution_parameter": { - "match": "(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+", + "match": "(::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", "captures": { "0": { "patterns": [ @@ -2401,11 +3569,22 @@ "include": "#scope_resolution_parameter_inner_generated" } ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] } } }, "scope_resolution_parameter_inner_generated": { - "match": "((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -2415,8 +3594,45 @@ ] }, "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { "name": "entity.name.scope-resolution.parameter.cpp" }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" + } + } + }, + "scope_resolution_function_definition_operator_overload": { + "match": "(::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<4>?)+)>)\\s*)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_function_definition_operator_overload_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" + }, "3": { "name": "meta.template.call.cpp", "patterns": [ @@ -2424,26 +3640,11 @@ "include": "#template_call_range" } ] - }, - "4": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.parameter.cpp" - } - } - }, - "scope_resolution_function_definition_operator_overload": { - "match": "(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_function_definition_operator_overload_inner_generated" - } - ] } } }, "scope_resolution_function_definition_operator_overload_inner_generated": { - "match": "((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::)", + "match": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<8>?)+)>)\\s*)?(::)", "captures": { "1": { "patterns": [ @@ -2453,9 +3654,9 @@ ] }, "2": { - "name": "entity.name.scope-resolution.function.definition.operator-overload.cpp" + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" }, - "3": { + "4": { "name": "meta.template.call.cpp", "patterns": [ { @@ -2463,19 +3664,30 @@ } ] }, - "4": { + "6": { + "name": "entity.name.scope-resolution.function.definition.operator-overload.cpp" + }, + "7": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "9": { "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.definition.operator-overload.cpp" } } }, "qualified_type": { - "match": "\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])", + "match": "\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<26>?)+)>)\\s*)?(?![\\w<:.])", "captures": { "0": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?", + "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?", "captures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))\\s*(\\=)\\s*((?:typename)?)\\s*((?:(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))|(.*(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(\\[)(\\w*)(\\])\\s*)?\\s*(?:(;)|\\n)", + "match": "(using)\\s*(?!namespace)(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(?![\\w<:.]))\\s*(\\=)\\s*((?:typename)?)\\s*((?:(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<58>?)+)>)\\s*)?(?![\\w<:.]))|(.*(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:(\\[)(\\w*)(\\])\\s*)?\\s*(?:(;)|\\n)", "captures": { "1": { "name": "keyword.other.using.directive.cpp" @@ -2873,7 +4123,7 @@ "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))", + "match": "(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<36>?)+)>)\\s*)?(?![\\w<:.]))", "captures": { "1": { "name": "storage.modifier.cpp" @@ -3379,7 +4667,7 @@ "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\())", + "begin": "((?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<69>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<69>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<69>?)+)>)\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<69>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\())", "beginCaptures": { "1": { "name": "meta.head.function.definition.cpp" @@ -4635,7 +5942,7 @@ "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*)(operator)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*)(?:(?:((?:\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|\\-\\-|\\+|\\-|!|~|\\*|&|new|new\\[\\]|delete|delete\\[\\]|\\->\\*|\\*|\\/|%|\\+|\\-|<<|>>|<=>|<|<=|>|>=|==|!=|&|\\^|\\||&&|\\|\\||=|\\+=|\\-=|\\*=|\\/=|%=|<<=|>>=|&=|\\^=|\\|=|,))|((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\<|\\())", + "begin": "((?:(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(operator)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(?:(?:((?:\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|\\-\\-|\\+|\\-|!|~|\\*|&|new|new\\[\\]|delete|delete\\[\\]|\\->\\*|\\*|\\/|%|\\+|\\-|<<|>>|<=>|<|<=|>|>=|==|!=|&|\\^|\\||&&|\\|\\||=|\\+=|\\-=|\\*=|\\/=|%=|<<=|>>=|&=|\\^=|\\|=|,))|((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\<|\\())", "beginCaptures": { "1": { "name": "meta.head.function.definition.special.operator-overload.cpp" @@ -5071,7 +6408,7 @@ "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?]*|[^>]*+<[^>]*+>)++>\\s*)?(\\()", + "begin": "((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<12>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(((?(?:(?>[^<>]*)\\g<12>?)+)>)\\s*)?(\\()", "beginCaptures": { "1": { "patterns": [ @@ -5825,22 +7200,33 @@ ] }, "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" + }, + "4": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "6": { "name": "entity.name.function.call.cpp" }, - "3": { + "7": { "patterns": [ { "include": "#inline_comment" } ] }, - "4": { + "8": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "5": { + "9": { "name": "comment.block.cpp" }, - "6": { + "10": { "patterns": [ { "match": "\\*\\/", @@ -5852,7 +7238,7 @@ } ] }, - "7": { + "11": { "name": "meta.template.call.cpp", "patterns": [ { @@ -5860,7 +7246,7 @@ } ] }, - "8": { + "13": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.cpp" } }, @@ -5878,13 +7264,13 @@ }, "curly_initializer": { "name": "meta.initialization.cpp", - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\{)", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\{)", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", "beginCaptures": { "1": { "name": "entity.name.function.call.initializer.cpp" }, "2": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cpp" } }, @@ -6517,7 +7930,7 @@ ] }, { - "include": "$base" + "include": "$self" } ] }, @@ -6542,7 +7955,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -6550,7 +7963,7 @@ }, "constructor_root": { "name": "meta.function.definition.special.constructor.cpp", - "begin": "(\\s*+((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*)(((?>(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))::((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))\\13((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\()))", + "begin": "(\\s*+((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))::((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))\\16((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\()))", "beginCaptures": { "1": { "name": "meta.head.function.definition.special.constructor.cpp" @@ -6623,7 +8036,15 @@ } ] }, - "12": { + "13": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "15": { "patterns": [ { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?=:)", @@ -6639,45 +8060,20 @@ } ] }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "16": { - "name": "comment.block.cpp" - }, "17": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "18": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "19": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "20": { + "19": { "name": "comment.block.cpp" }, - "21": { + "20": { "patterns": [ { "match": "\\*\\/", @@ -6689,20 +8085,45 @@ } ] }, - "22": { + "21": { "patterns": [ { "include": "#inline_comment" } ] }, - "23": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "24": { + "23": { "name": "comment.block.cpp" }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { "patterns": [ { "match": "\\*\\/", @@ -6787,12 +8208,20 @@ "patterns": [ { "contentName": "meta.parameter.initialization.cpp", - "begin": "((?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", "beginCaptures": { "1": { "name": "entity.name.function.call.initializer.cpp" }, "2": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cpp" } }, @@ -6860,7 +8289,7 @@ ] }, { - "include": "$base" + "include": "$self" } ] }, @@ -6885,7 +8314,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -7086,7 +8515,7 @@ } }, { - "include": "$base" + "include": "$self" } ] }, @@ -7111,7 +8540,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -7119,7 +8548,7 @@ }, "destructor_root": { "name": "meta.function.definition.special.member.destructor.cpp", - "begin": "(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*)(((?>(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))::((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))~\\13((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\()))", + "begin": "(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))::((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))~\\16((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\()))", "beginCaptures": { "1": { "name": "meta.head.function.definition.special.member.destructor.cpp" @@ -7192,7 +8621,15 @@ } ] }, - "12": { + "13": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "15": { "patterns": [ { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?=:)", @@ -7208,45 +8645,20 @@ } ] }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "16": { - "name": "comment.block.cpp" - }, "17": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "18": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "19": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "20": { + "19": { "name": "comment.block.cpp" }, - "21": { + "20": { "patterns": [ { "match": "\\*\\/", @@ -7258,20 +8670,45 @@ } ] }, - "22": { + "21": { "patterns": [ { "include": "#inline_comment" } ] }, - "23": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "24": { + "23": { "name": "comment.block.cpp" }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { "patterns": [ { "match": "\\*\\/", @@ -7358,7 +8795,7 @@ } }, { - "include": "$base" + "include": "$self" } ] }, @@ -7383,7 +8820,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -7799,13 +9236,13 @@ ] }, "function_pointer": { - "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "beginCaptures": { + "1": { + "name": "meta.qualified_type.cpp", + "patterns": [ + { + "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))){2,}\\&", + "captures": { + "1": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "2": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "3": { + "name": "comment.block.cpp" + }, + "4": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "name": "invalid.illegal.reference-type.cpp" + }, + { + "match": "\\&", + "name": "storage.modifier.reference.cpp" + } + ] + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "38": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "39": { + "name": "comment.block.cpp" + }, + "40": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "41": { + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" + }, + "42": { + "name": "punctuation.definition.function.pointer.dereference.cpp" + }, + "43": { + "name": "entity.name.type.alias.cpp entity.name.type.pointer.function.cpp" + }, + "44": { + "name": "punctuation.definition.begin.bracket.square.cpp" + }, + "45": { + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "46": { + "name": "punctuation.definition.end.bracket.square.cpp" + }, + "47": { + "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" + }, + "48": { + "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" + } + }, + "end": "(\\))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=[{=,);]|\\n)(?!\\()", + "endCaptures": { + "1": { + "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cpp" + }, + "2": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + } + }, + "patterns": [ + { + "include": "#function_parameter_context" + } + ] + } + ] + }, "parameter_or_maybe_value": { "name": "meta.parameter.cpp", "begin": "((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\w)", @@ -8504,6 +10342,9 @@ { "include": "#ever_present_context" }, + { + "include": "#function_pointer_parameter" + }, { "include": "#memory_operators" }, @@ -8513,9 +10354,6 @@ { "include": "#curly_initializer" }, - { - "include": "#function_pointer_parameter" - }, { "include": "#decltype" }, @@ -8780,7 +10618,7 @@ ] }, { - "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\)|,|\\[|=|\\n)", + "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=(?:\\)|,|\\[|=|\\/\\/|(?:\\n|$)))", "captures": { "1": { "patterns": [ @@ -9758,7 +11596,7 @@ }, "using_namespace": { "name": "meta.using-namespace.cpp", - "begin": "(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)?((?(?:(?>[^<>]*)\\g<7>?)+)>)\\s*)?::)*\\s*+)?((?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)\\s*((?(?:(?>[^<>]*)\\g<9>?)+)>)\\s*)?::)*\\s*+)\\s*((?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)\\s*((?(?:(?>[^<>]*)\\g<5>?)+)>)\\s*)?::)*\\s*+)\\s*((?])|(?<=\\Wreturn|^return))\\s*(\\[(?!\\[))((?:[^\\]\\[]*\\[.*?\\](?!\\s*\\[)[^\\]\\[]*?)*[^\\]\\[]*?)(\\](?!\\[)))", "beginCaptures": { @@ -10011,7 +11878,7 @@ }, "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -10058,7 +11925,7 @@ }, "enum_block": { "name": "meta.block.enum.cpp", - "begin": "(((?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?\\s*((?(?:(?>[^<>]*)\\g<15>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<15>?)+)>)\\s*)?(::))?\\s*((?|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))", @@ -10131,7 +12009,7 @@ }, "patterns": [ { - "include": "$base" + "include": "$self" } ] }, @@ -10145,6 +12023,9 @@ } }, "patterns": [ + { + "include": "#ever_present_context" + }, { "include": "#enumerator_list" }, @@ -10165,7 +12046,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -10173,6 +12054,9 @@ }, "inheritance_context": { "patterns": [ + { + "include": "#ever_present_context" + }, { "match": ",", "name": "punctuation.separator.delimiter.comma.inheritance.cpp" @@ -10186,10 +12070,172 @@ "name": "storage.type.modifier.virtual.cpp" }, { - "match": "(?<=virtual|private|protected|public|,|:)\\s*(?!(?:(?:private|protected|public)|virtual))((?:\\s*+(?:(?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))", + "match": "(?<=virtual|private|protected|public|,|:)\\s*(?!(?:(?:private|protected|public)|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))", "captures": { "1": { - "name": "entity.name.type.inherited.cpp" + "name": "meta.qualified_type.cpp", + "patterns": [ + { + "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.class.cpp" @@ -10208,67 +12254,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -10278,16 +12286,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -10349,7 +12520,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -10359,7 +12530,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -10367,7 +12538,7 @@ }, "struct_block": { "name": "meta.block.struct.cpp", - "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.struct.cpp" @@ -10378,67 +12549,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -10448,16 +12581,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -10519,7 +12815,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -10529,7 +12825,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -10537,7 +12833,7 @@ }, "union_block": { "name": "meta.block.union.cpp", - "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.union.cpp" @@ -10548,67 +12844,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -10618,16 +12876,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -10689,7 +13110,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -10699,7 +13120,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -10762,7 +13183,7 @@ }, "patterns": [ { - "include": "$base" + "include": "$self" } ] }, @@ -10777,7 +13198,7 @@ }, "patterns": [ { - "include": "$base" + "include": "$self" } ] }, @@ -10787,12 +13208,12 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] }, { - "include": "$base" + "include": "$self" } ] }, @@ -10807,7 +13228,7 @@ "patterns": [ { "name": "meta.block.class.cpp", - "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.class.cpp" @@ -10818,67 +13239,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -10888,16 +13271,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -10959,7 +13505,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -11114,7 +13660,7 @@ "patterns": [ { "name": "meta.block.struct.cpp", - "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.struct.cpp" @@ -11125,67 +13671,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -11195,16 +13703,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -11266,7 +13937,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -11421,7 +14092,7 @@ "patterns": [ { "name": "meta.block.union.cpp", - "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.union.cpp" @@ -11432,67 +14103,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -11502,16 +14135,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -11573,7 +14369,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -13505,62 +16301,6 @@ } ] }, - "hacky_fix_for_stray_directive": { - "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))#define.*(?(?-mix:[a-zA-Z_$][\\w$]*)))\t # macro name\n(?:\n (\\()\n\t(\n\t \\s* \\g \\s*\t\t # first argument\n\t ((,) \\s* \\g \\s*)* # additional arguments\n\t (?:\\.\\.\\.)?\t\t\t# varargs ellipsis?\n\t)\n (\\))\n)?", - "beginCaptures": { - "1": { - "name": "keyword.control.directive.define.cpp" - }, - "2": { - "name": "punctuation.definition.directive.cpp" - }, - "3": { - "name": "entity.name.function.preprocessor.cpp" - }, - "5": { - "name": "punctuation.definition.parameters.begin.cpp" - }, - "6": { - "name": "variable.parameter.preprocessor.cpp" - }, - "8": { - "name": "punctuation.separator.parameters.cpp" - }, - "9": { - "name": "punctuation.definition.parameters.end.cpp" - } - }, - "end": "(?=(?://|/\\*))|(?", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.cpp" - } - }, - "name": "string.quoted.other.lt-gt.include.cpp" - } - ] - }, - "meta_preprocessor_line": { - "name": "meta.preprocessor.cpp", - "begin": "^\\s*((#)\\s*line)\\b", - "beginCaptures": { - "1": { - "name": "keyword.control.directive.line.cpp" - }, - "2": { - "name": "punctuation.definition.directive.cpp" - } - }, - "end": "(?=(?://|/\\*))|(?=+!]+|\\(\\)|\\[\\]))\\s*\\(\n)", - "end": "(?<=\\))(?!\\w)|(?=+!]+|\\(\\)|\\[\\]))\n)\n\\s*(\\()", - "beginCaptures": { - "1": { - "name": "entity.name.function.cpp" - }, - "2": { - "name": "punctuation.section.arguments.begin.bracket.round.cpp" - } - }, - "end": "(\\))|(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*)(((?>(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))::((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))\\13((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\()))", + "begin": "(\\s*+((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))::((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))\\16((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\()))", "beginCaptures": { "1": { "name": "meta.head.function.definition.special.constructor.cpp" @@ -15277,7 +16768,15 @@ } ] }, - "12": { + "13": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "15": { "patterns": [ { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?=:)", @@ -15293,45 +16792,20 @@ } ] }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "16": { - "name": "comment.block.cpp" - }, "17": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "18": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "19": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "20": { + "19": { "name": "comment.block.cpp" }, - "21": { + "20": { "patterns": [ { "match": "\\*\\/", @@ -15343,20 +16817,45 @@ } ] }, - "22": { + "21": { "patterns": [ { "include": "#inline_comment" } ] }, - "23": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "24": { + "23": { "name": "comment.block.cpp" }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { "patterns": [ { "match": "\\*\\/", @@ -15441,12 +16940,20 @@ "patterns": [ { "contentName": "meta.parameter.initialization.cpp", - "begin": "((?(?:(?>[^<>]*)\\g<3>?)+)>)\\s*)?(\\()", "beginCaptures": { "1": { "name": "entity.name.function.call.initializer.cpp" }, "2": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "4": { "name": "punctuation.section.arguments.begin.bracket.round.function.call.initializer.cpp" } }, @@ -15514,7 +17021,7 @@ ] }, { - "include": "$base" + "include": "$self" } ] }, @@ -15539,7 +17046,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -15547,7 +17054,7 @@ }, "macro_safe_destructor_root": { "name": "meta.function.definition.special.member.destructor.cpp", - "begin": "(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*)(((?>(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))::((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))~\\13((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\()))", + "begin": "(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<14>?)+)>)\\s*)?::)*)(((?>(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))::((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))~\\16((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\()))", "beginCaptures": { "1": { "name": "meta.head.function.definition.special.member.destructor.cpp" @@ -15620,7 +17127,15 @@ } ] }, - "12": { + "13": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range" + } + ] + }, + "15": { "patterns": [ { "match": "(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?=:)", @@ -15636,45 +17151,20 @@ } ] }, - "14": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "15": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "16": { - "name": "comment.block.cpp" - }, "17": { "patterns": [ { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" + "include": "#inline_comment" } ] }, "18": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "19": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "20": { + "19": { "name": "comment.block.cpp" }, - "21": { + "20": { "patterns": [ { "match": "\\*\\/", @@ -15686,20 +17176,45 @@ } ] }, - "22": { + "21": { "patterns": [ { "include": "#inline_comment" } ] }, - "23": { + "22": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "24": { + "23": { "name": "comment.block.cpp" }, + "24": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, "25": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "26": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "27": { + "name": "comment.block.cpp" + }, + "28": { "patterns": [ { "match": "\\*\\/", @@ -15786,7 +17301,7 @@ } }, { - "include": "$base" + "include": "$self" } ] }, @@ -15811,7 +17326,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -15819,7 +17334,7 @@ }, "macro_safe_function_definition": { "name": "meta.function.definition.cpp", - "begin": "((?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\())", + "begin": "((?:(?:^|\\G|(?<=;|\\}))|(?<=>))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<69>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<69>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<69>?)+)>)\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<69>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\())", "beginCaptures": { "1": { "name": "meta.head.function.definition.cpp" @@ -15909,7 +17424,7 @@ "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*)(operator)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*)(?:(?:((?:\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|\\-\\-|\\+|\\-|!|~|\\*|&|new|new\\[\\]|delete|delete\\[\\]|\\->\\*|\\*|\\/|%|\\+|\\-|<<|>>|<=>|<|<=|>|>=|==|!=|&|\\^|\\||&&|\\|\\||=|\\+=|\\-=|\\*=|\\/=|%=|<<=|>>=|&=|\\^=|\\|=|,))|((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\<|\\())", + "begin": "((?:(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(operator)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<67>?)+)>)\\s*)?::)*)(?:(?:((?:\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|\\-\\-|\\+|\\-|!|~|\\*|&|new|new\\[\\]|delete|delete\\[\\]|\\->\\*|\\*|\\/|%|\\+|\\-|<<|>>|<=>|<|<=|>|>=|==|!=|&|\\^|\\||&&|\\|\\||=|\\+=|\\-=|\\*=|\\/=|%=|<<=|>>=|&=|\\^=|\\|=|,))|((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:\\[\\])?)))|(\"\")((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?=\\<|\\())", "beginCaptures": { "1": { "name": "meta.head.function.definition.special.operator-overload.cpp" @@ -16345,7 +17890,7 @@ "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)?((?(?:(?>[^<>]*)\\g<7>?)+)>)\\s*)?::)*\\s*+)?((?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)\\s*((?(?:(?>[^<>]*)\\g<5>?)+)>)\\s*)?::)*\\s*+)\\s*((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.class.cpp" @@ -17218,67 +18823,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -17288,16 +18855,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -17359,7 +19089,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -17514,7 +19244,7 @@ "patterns": [ { "name": "meta.block.struct.cpp", - "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.struct.cpp" @@ -17525,67 +19255,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -17595,16 +19287,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -17666,7 +19521,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -17821,7 +19676,7 @@ "patterns": [ { "name": "meta.block.union.cpp", - "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.union.cpp" @@ -17832,67 +19687,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -17902,16 +19719,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -17973,7 +19953,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -18119,7 +20099,7 @@ }, "macro_safe_class_block": { "name": "meta.block.class.cpp", - "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.class.cpp" @@ -18130,67 +20110,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -18200,16 +20142,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -18271,7 +20376,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -18281,7 +20386,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -18289,7 +20394,7 @@ }, "macro_safe_struct_block": { "name": "meta.block.struct.cpp", - "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.struct.cpp" @@ -18300,67 +20405,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -18370,16 +20437,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -18441,7 +20671,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -18451,7 +20681,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -18459,7 +20689,7 @@ }, "macro_safe_union_block": { "name": "meta.block.union.cpp", - "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?\\s*((?:(?\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:(?:(?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?:::))?(?:(?:(?:(?>\\s+)|(?:\\/\\*)(?:(?>(?:[^\\*]|(?>\\*+)[^\\/])*)(?:(?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.])))+)*))?))", + "begin": "((((?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))|((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\))))|(?={))(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(DLLEXPORT)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))((?:(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(final)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))?(?:((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(:)((?>[^{]*)))?))", "beginCaptures": { "1": { "name": "meta.head.union.cpp" @@ -18470,67 +20700,29 @@ "4": { "patterns": [ { - "include": "#attributes_context" - }, - { - "include": "#number_literal" + "include": "#inline_comment" } ] }, "5": { - "patterns": [ - { - "include": "#inline_comment" - } - ] + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "6": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + "name": "comment.block.cpp" }, "7": { - "name": "comment.block.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "8": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "9": { - "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" - }, - "10": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "11": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "12": { - "name": "comment.block.cpp" - }, - "13": { - "patterns": [ - { - "match": "\\*\\/", - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - { - "match": "\\*", - "name": "comment.block.cpp" - } - ] - }, - "14": { "patterns": [ { "include": "#attributes_context" @@ -18540,16 +20732,179 @@ } ] }, + "9": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "11": { + "name": "comment.block.cpp" + }, + "12": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "13": { + "name": "entity.name.other.preprocessor.macro.predefined.DLLEXPORT.cpp" + }, + "14": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, "15": { - "name": "entity.name.type.$3.cpp" + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, "16": { - "name": "storage.type.modifier.final.cpp" + "name": "comment.block.cpp" }, "17": { - "name": "punctuation.separator.colon.inheritance.cpp" + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] }, "18": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "19": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "20": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "21": { + "name": "comment.block.cpp" + }, + "22": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "23": { + "name": "entity.name.type.$3.cpp" + }, + "24": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "25": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "26": { + "name": "comment.block.cpp" + }, + "27": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "28": { + "name": "storage.type.modifier.final.cpp" + }, + "29": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "30": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "31": { + "name": "comment.block.cpp" + }, + "32": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "33": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "34": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "35": { + "name": "comment.block.cpp" + }, + "36": { + "patterns": [ + { + "match": "\\*\\/", + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + { + "match": "\\*", + "name": "comment.block.cpp" + } + ] + }, + "37": { + "name": "punctuation.separator.colon.inheritance.cpp" + }, + "38": { "patterns": [ { "include": "#inheritance_context" @@ -18611,7 +20966,7 @@ "include": "#destructor_inline" }, { - "include": "$base" + "include": "$self" } ] }, @@ -18621,7 +20976,7 @@ "end": "[\\s\\n]*(?=;)", "patterns": [ { - "include": "$base" + "include": "$self" } ] } @@ -18629,7 +20984,7 @@ }, "macro_safe_enum_block": { "name": "meta.block.enum.cpp", - "begin": "(((?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?\\s*((?(?:(?>[^<>]*)\\g<15>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<15>?)+)>)\\s*)?(::))?\\s*((?|\\?\\?>)\\s*(;)|(;))|(?=[;>\\[\\]=]))|(?\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((?:::)?(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\s*+(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+((?]*|[^>]*+<[^>]*+>)++>\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?:(?]*|[^>]*+<[^>]*+>)++>\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\(\\(.*?\\)\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?:(?:(?:short|signed|unsigned|long)|(?:class|struct|union|enum))((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(((::)?(?:((?-mix:(?!\\b(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|class|struct|union|enum|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\s*+(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(::))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(?!(?:not|compl|sizeof|new|delete|not_eq|bitand|xor|bitor|and|or|throw|and_eq|xor_eq|or_eq|alignof|alignas|typeid|noexcept|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default|NULL|true|false|nullptr|const|static|volatile|register|restrict|extern|inline|constexpr|mutable|friend|explicit|virtual|final|override|volatile|const|noexcept|constexpr|mutable|constexpr|consteval|private|protected|public|if|elif|else|endif|ifdef|ifndef|define|undef|include|line|error|warning|pragma|_Pragma|defined|__has_include|__has_cpp_attribute|this|template|namespace|using|operator|typedef|decltype|typename|asm|__asm__|concept|requires|export|thread_local|atomic_cancel|atomic_commit|atomic_noexcept|co_await|co_return|co_yield|import|module|reflexpr|synchronized|audit|axiom|transaction_safe|transaction_safe_dynamic)\\b)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(((?(?:(?>[^<>]*)\\g<27>?)+)>)\\s*)?(?![\\w<:.]))(((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))?(?:(?:\\&|\\*)((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z))))*(?:\\&|\\*))?((?:(?:(?>\\s+)|(\\/\\*)((?>(?:[^\\*]|(?>\\*+)[^\\/])*)((?>\\*+)\\/)))+?|(?:(?:(?:(?:\\b|(?<=\\W))|(?=\\W))|\\A)|\\Z)))(\\()(\\*)\\s*((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", "beginCaptures": { "1": { "name": "meta.qualified_type.cpp", "patterns": [ { - "match": "(?:class|struct|union|enum)", + "match": "(? | undefined { const completionResult = this.provideCompletionItemsInternal(document, position, context); if (!completionResult) { @@ -76,20 +79,55 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi } if (validateLocation) { - rootNode = parseDocument(document, false); - currentNode = getNode(rootNode, position, true); - if (isStyleAttribute(currentNode, position)) { + const lsDoc = TextDocument.create(document.uri.toString(), 'html', 0, document.getText()); + const parsedLsDoc = this.htmlLS.parseHTMLDocument(lsDoc); + const positionOffset = document.offsetAt(position); + const node = parsedLsDoc.findNodeAt(positionOffset); + + if (node.tag === 'script') { + if (node.attributes && 'type' in node.attributes) { + const rawTypeAttrValue = node.attributes['type']; + if (rawTypeAttrValue) { + const typeAttrValue = trimQuotes(rawTypeAttrValue); + if (typeAttrValue === 'application/javascript' || typeAttrValue === 'text/javascript') { + if (!getSyntaxFromArgs({ language: 'javascript' })) { + return; + } else { + validateLocation = false; + } + } + + else if (allowedMimeTypesInScriptTag.indexOf(trimQuotes(rawTypeAttrValue)) > -1) { + validateLocation = false; + } + } + } else { + return; + } + } + else if (node.tag === 'style') { syntax = 'css'; validateLocation = false; } else { - const embeddedCssNode = getEmbeddedCssNodeIfAny(document, currentNode, position); - if (embeddedCssNode) { - currentNode = getNode(embeddedCssNode, position, true); - syntax = 'css'; + if (node.attributes && node.attributes['style']) { + const scanner = this.htmlLS.createScanner(document.getText(), node.start); + let tokenType = scanner.scan(); + let prevAttr = undefined; + while (tokenType !== TokenType.EOS && (scanner.getTokenEnd() <= positionOffset)) { + tokenType = scanner.scan(); + if (tokenType === TokenType.AttributeName) { + prevAttr = scanner.getTokenText(); + } + } + if (prevAttr === 'style') { + syntax = 'css'; + validateLocation = false; + } } } } + } const extractAbbreviationResults = helper.extractAbbreviation(document, position, !isStyleSheet(syntax)); diff --git a/extensions/emmet/src/test/abbreviationAction.test.ts b/extensions/emmet/src/test/abbreviationAction.test.ts index 0a99bae1862..31f46891965 100644 --- a/extensions/emmet/src/test/abbreviationAction.test.ts +++ b/extensions/emmet/src/test/abbreviationAction.test.ts @@ -264,16 +264,6 @@ suite('Tests for Expand Abbreviations (HTML)', () => { }); }); - test('No expanding text in completion list inside style tag if position is not for property name (HTML)', () => { - return withRandomFileEditor(htmlContents, 'html', (editor, _doc) => { - editor.selection = new Selection(13, 14, 13, 14); - const cancelSrc = new CancellationTokenSource(); - const completionPromise = completionProvider.provideCompletionItems(editor.document, editor.selection.active, cancelSrc.token, { triggerKind: CompletionTriggerKind.Invoke }); - assert.equal(!completionPromise, true, `Got unexpected comapletion promise instead of undefined`); - return Promise.resolve(); - }); - }); - test('Expand css when inside style attribute (HTML)', () => { const styleAttributeContent = '
'; return withRandomFileEditor(styleAttributeContent, 'html', async (editor, _doc) => { diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts index 8ce65ca04b3..b6281272851 100644 --- a/extensions/emmet/src/util.ts +++ b/extensions/emmet/src/util.ts @@ -608,3 +608,18 @@ export function isStyleAttribute(currentNode: Node | null, position: vscode.Posi } +export function trimQuotes(s: string) { + if (s.length <= 1) { + return s.replace(/['"]/, ''); + } + + if (s[0] === `'` || s[0] === `"`) { + s = s.slice(1); + } + + if (s[s.length - 1] === `'` || s[s.length - 1] === `"`) { + s = s.slice(0, -1); + } + + return s; +} \ No newline at end of file diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index a417831756a..7b22c533426 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -2478,11 +2478,35 @@ vscode-emmet-helper@^1.2.15: jsonc-parser "^1.0.0" vscode-languageserver-types "^3.6.0-next.1" +vscode-html-languageservice@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.3.tgz#0aeae18a59000e317447ea34965f72680a2140ef" + integrity sha512-U+upM3gHp3HaF3wXAnUduA6IDKcz6frWS/dTAju3cZVIyZwOLBBFElQVlLH0ycHyMzqUFrjvdv+kEyPAEWfQ/g== + dependencies: + vscode-languageserver-types "^3.15.0-next.2" + vscode-nls "^4.1.1" + vscode-uri "^2.0.3" + +vscode-languageserver-types@^3.15.0-next.2: + version "3.15.0-next.2" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.2.tgz#a0601332cdaafac21931f497bb080cfb8d73f254" + integrity sha512-2JkrMWWUi2rlVLSo9OFR2PIGUzdiowEM8NgNYiwLKnXTjpwpjjIrJbNNxDik7Rv4oo9KtikcFQZKXbrKilL/MQ== + vscode-languageserver-types@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" integrity sha512-n4G+hCgZwAhtcJSCkwJP153TLdcEBWwrIrb3Su/SpOkhmU7KjDgxaQBLA45hf+QbhB8uKQb+TVStPvbuYFHSMA== +vscode-nls@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" + integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== + +vscode-uri@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.3.tgz#25e5f37f552fbee3cec7e5f80cef8469cefc6543" + integrity sha512-4D3DI3F4uRy09WNtDGD93H9q034OHImxiIcSq664Hq1Y1AScehlP3qqZyTkX/RWxeu0MRMHGkrxYqm2qlDF/aw== + vscode@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.0.1.tgz#3d161200615fe2af1d92ddc650751159411a513b" diff --git a/extensions/fsharp/cgmanifest.json b/extensions/fsharp/cgmanifest.json index b093d908572..47351a1a0b4 100644 --- a/extensions/fsharp/cgmanifest.json +++ b/extensions/fsharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "ionide/ionide-fsgrammar", "repositoryUrl": "https://github.com/ionide/ionide-fsgrammar", - "commitHash": "b57388e5a0971412c081cf0cea8b50b9c24ea4e8" + "commitHash": "687070defaf355022a5d82d091258eccfea7317c" } }, "license": "MIT", diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index 82bd02d54ab..20cfc9ecde7 100644 --- a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/extensions/fsharp/syntaxes/fsharp.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/ionide/ionide-fsgrammar/commit/b57388e5a0971412c081cf0cea8b50b9c24ea4e8", + "version": "https://github.com/ionide/ionide-fsgrammar/commit/687070defaf355022a5d82d091258eccfea7317c", "name": "fsharp", "scopeName": "source.fsharp", "patterns": [ @@ -238,7 +238,7 @@ }, { "name": "keyword.fsharp", - "match": "\\b(private|to|public|internal|function|yield!|yield|class|exception|match|delegate|of|new|in|as|if|then|else|elif|for|begin|end|inherit|do|let\\!|return\\!|return|interface|with|abstract|property|union|enum|member|try|finally|and|when|use|use\\!|struct|while|mutable)(?!')\\b" + "match": "\\b(private|to|public|internal|function|yield!|yield|class|exception|match|delegate|of|new|in|as|if|then|else|elif|for|begin|end|inherit|do|let\\!|return\\!|return|interface|with|abstract|enum|member|try|finally|and|when|or|use|use\\!|struct|while|mutable|assert|base|done|downcast|downto|extern|fixed|global|lazy|upcast|not)(?!')\\b" }, { "name": "keyword.fsharp", @@ -601,7 +601,7 @@ }, { "name": "constant.others.fsharp", - "match": "\\b(true|false|null|unit)\\b" + "match": "\\b(true|false|null|unit|void)\\b" } ] }, @@ -826,15 +826,26 @@ ] }, { - "match": "(\\*)(\\s*([?[:alpha:]0-9'`^._ ]+))*", - "captures": { + "begin": "(\\*)(\\s*([?[:alpha:]0-9'`^._ ]+))*", + "beginCaptures": { "1": { "name": "keyword.symbol.fsharp" }, "2": { "name": "entity.name.type.fsharp" } - } + }, + "end": "(?==)|(?=\\))", + "endCaptures": { + "1": { + "name": "keyword.symbol.fsharp" + } + }, + "patterns": [ + { + "include": "#tuple_signature" + } + ] }, { "begin": "(<+(?![[:space:]]*\\)))", @@ -882,14 +893,6 @@ { "include": "#definition" }, - { - "match": "(?<=>)\\s*(``([[:alpha:]0-9'^._ ]+)``|[[:alpha:]0-9'`^._]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, { "include": "#variables" }, @@ -978,6 +981,29 @@ "include": "#common_binding_definition" } ] + }, + { + "name": "binding.fsharp", + "begin": "\\b(new)\\b\\s+(\\()", + "end": "(\\))", + "beginCaptures": { + "1": { + "name": "keyword.fsharp" + }, + "2": { + "name": "keyword.fsharp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.fsharp" + } + }, + "patterns": [ + { + "include": "#common_binding_definition" + } + ] } ] }, @@ -1037,7 +1063,7 @@ "patterns": [ { "name": "keyword.fsharp", - "match": "\\b(private|to|public|internal|function|yield!|yield|class|exception|match|delegate|of|new|in|as|if|then|else|elif|for|begin|end|inherit|do|let\\!|return\\!|return|interface|with|abstract|property|union|enum|member|try|finally|and|when|or|use|use\\!|struct|while|mutable)(?!')\\b" + "match": "\\b(private|to|public|internal|function|yield!|yield|class|exception|match|delegate|of|new|in|as|if|then|else|elif|for|begin|end|inherit|do|let\\!|return\\!|return|interface|with|abstract|enum|member|try|finally|and|when|or|use|use\\!|struct|while|mutable|assert|base|done|downcast|downto|extern|fixed|global|lazy|upcast|not)(?!')\\b" }, { "name": "keyword.symbol.fsharp", @@ -1049,7 +1075,7 @@ "patterns": [ { "name": "entity.name.section.fsharp", - "begin": "\\b(namespace|module)\\s*(public|internal|private)?\\s+([[:alpha:]][[:alpha:]0-9'_. ]*)", + "begin": "\\b(namespace global)|(namespace|module)\\s*(public|internal|private|rec)?\\s+([[:alpha:]][[:alpha:]0-9'_. ]*)", "end": "(\\s?=|\\s|$)", "beginCaptures": { "1": { @@ -1059,6 +1085,9 @@ "name": "keyword.fsharp" }, "3": { + "name": "keyword.fsharp" + }, + "4": { "name": "entity.name.section.fsharp" } }, diff --git a/extensions/git/package.json b/extensions/git/package.json index 4a789aaa6ad..66b8731c6d9 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -1148,6 +1148,26 @@ "description": "%config.enableSmartCommit%", "default": false }, + "git.smartCommitChanges": { + "type": "string", + "enum": [ + "all", + "tracked" + ], + "enumDescriptions": [ + "%config.smartCommitChanges.all%", + "%config.smartCommitChanges.tracked%" + ], + "scope": "resource", + "description": "%config.smartCommitChanges%", + "default": "all" + }, + "git.suggestSmartCommit": { + "type": "boolean", + "scope": "resource", + "description": "%config.suggestSmartCommit%", + "default": true + }, "git.enableCommitSigning": { "type": "boolean", "scope": "resource", @@ -1165,10 +1185,26 @@ "default": true, "description": "%config.decorations.enabled%" }, - "git.promptToSaveFilesBeforeCommit": { + "git.enableStatusBarSync": { "type": "boolean", - "scope": "resource", "default": true, + "description": "%config.enableStatusBarSync%", + "scope": "resource" + }, + "git.promptToSaveFilesBeforeCommit": { + "type": "string", + "enum": [ + "always", + "staged", + "never" + ], + "enumDescriptions": [ + "%config.promptToSaveFilesBeforeCommit.always%", + "%config.promptToSaveFilesBeforeCommit.staged%", + "%config.promptToSaveFilesBeforeCommit.never%" + ], + "scope": "resource", + "default": "always", "description": "%config.promptToSaveFilesBeforeCommit%" }, "git.postCommitCommand": { @@ -1312,6 +1348,21 @@ "scope": "resource", "default": true, "description": "%config.openDiffOnClick%" + }, + "git.supportCancellation": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.supportCancellation%" + }, + "git.branchSortOrder": { + "type": "string", + "enum": [ + "committerdate", + "alphabetically" + ], + "default": "committerdate", + "description": "%config.branchSortOrder%" } } }, @@ -1477,4 +1528,4 @@ "@types/which": "^1.0.28", "mocha": "^3.2.0" } -} \ No newline at end of file +} diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index adff0134ed9..7d90b4b15f2 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -89,10 +89,18 @@ "config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository.", "config.defaultCloneDirectory": "The default location to clone a git repository.", "config.enableSmartCommit": "Commit all changes when there are no staged changes.", + "config.smartCommitChanges": "Control which changes are automatically staged by Smart Commit.", + "config.smartCommitChanges.all": "Automatically stage all changes.", + "config.smartCommitChanges.tracked": "Automatically staged tracked changes only.", + "config.suggestSmartCommit": "Suggests to enable smart commit (commit all changes when there are no staged changes).", "config.enableCommitSigning": "Enables commit signing with GPG.", "config.discardAllScope": "Controls what changes are discarded by the `Discard all changes` command. `all` discards all changes. `tracked` discards only tracked files. `prompt` shows a prompt dialog every time the action is run.", "config.decorations.enabled": "Controls whether Git contributes colors and badges to the explorer and the open editors view.", + "config.enableStatusBarSync": "Controls whether the Git Sync command appears in the status bar.", "config.promptToSaveFilesBeforeCommit": "Controls whether Git should check for unsaved files before committing.", + "config.promptToSaveFilesBeforeCommit.always": "Check for any unsaved files.", + "config.promptToSaveFilesBeforeCommit.staged": "Check only for unsaved staged files.", + "config.promptToSaveFilesBeforeCommit.never": "Disable this check.", "config.postCommitCommand": "Runs a git command after a successful commit.", "config.postCommitCommand.none": "Don't run any command after a commit.", "config.postCommitCommand.push": "Run 'Git Push' after a successful commit.", @@ -118,6 +126,8 @@ "config.useForcePushWithLease": "Controls whether force pushing uses the safer force-with-lease variant.", "config.confirmForcePush": "Controls whether to ask for confirmation before force-pushing.", "config.openDiffOnClick": "Controls whether the diff editor should be opened when clicking a change. Otherwise the regular editor will be opened.", + "config.supportCancellation": "Controls whether a notification comes up when running the Sync action, which allows the user to cancel the operation.", + "config.branchSortOrder": "Controls the sort order for branches.", "colors.added": "Color for added resources.", "colors.modified": "Color for modified resources.", "colors.deleted": "Color for deleted resources.", @@ -125,4 +135,4 @@ "colors.ignored": "Color for ignored resources.", "colors.conflict": "Color for resources with conflicts.", "colors.submodule": "Color for submodule resources." -} \ No newline at end of file +} diff --git a/extensions/git/resources/icons/dark/check.svg b/extensions/git/resources/icons/dark/check.svg index 865cc83c347..2d16f390078 100644 --- a/extensions/git/resources/icons/dark/check.svg +++ b/extensions/git/resources/icons/dark/check.svg @@ -1,3 +1,3 @@ - + diff --git a/extensions/git/resources/icons/light/check.svg b/extensions/git/resources/icons/light/check.svg index e1a546660ed..a9f8aa131b5 100644 --- a/extensions/git/resources/icons/light/check.svg +++ b/extensions/git/resources/icons/light/check.svg @@ -1,3 +1,3 @@ - + diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 21195974fc1..a0b2d3dad7f 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -238,5 +238,6 @@ export const enum GitErrorCodes { CantLockRef = 'CantLockRef', CantRebaseMultipleBranches = 'CantRebaseMultipleBranches', PatchDoesNotApply = 'PatchDoesNotApply', - NoPathFound = 'NoPathFound' + NoPathFound = 'NoPathFound', + UnknownPath = 'UnknownPath', } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index d75e6f9bc45..9aa8d292255 100755 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -353,9 +353,11 @@ export class CommandCenter { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: + case Status.INDEX_ADDED: return this.getURI(resource.original, 'HEAD'); case Status.MODIFIED: + case Status.UNTRACKED: return this.getURI(resource.resourceUri, '~'); case Status.DELETED_BY_THEM: @@ -414,6 +416,7 @@ export class CommandCenter { switch (resource.type) { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: + case Status.INDEX_ADDED: return `${basename} (Index)`; case Status.MODIFIED: @@ -426,6 +429,10 @@ export class CommandCenter { case Status.DELETED_BY_THEM: return `${basename} (Ours)`; + + case Status.UNTRACKED: + + return `${basename} (Untracked)`; } return ''; @@ -450,6 +457,8 @@ export class CommandCenter { return; } + url = url.trim().replace(/^git\s+clone\s+/, ''); + const config = workspace.getConfiguration('git'); let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); @@ -698,7 +707,13 @@ export class CommandCenter { viewColumn: ViewColumn.Active }; - const document = await workspace.openTextDocument(uri); + let document; + try { + document = await workspace.openTextDocument(uri); + } catch (error) { + await commands.executeCommand('vscode.open', uri, opts); + continue; + } // Check if active text editor has same path as other editor. we cannot compare via // URI.toString() here because the schemas can be different. Instead we just go by path. @@ -1225,23 +1240,35 @@ export class CommandCenter { opts?: CommitOptions ): Promise { const config = workspace.getConfiguration('git', Uri.file(repository.root)); - const promptToSaveFilesBeforeCommit = config.get('promptToSaveFilesBeforeCommit') === true; + let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit'); - if (promptToSaveFilesBeforeCommit) { - const unsavedTextDocuments = workspace.textDocuments + // migration + if (promptToSaveFilesBeforeCommit as any === true) { + promptToSaveFilesBeforeCommit = 'always'; + } else if (promptToSaveFilesBeforeCommit as any === false) { + promptToSaveFilesBeforeCommit = 'never'; + } + + if (promptToSaveFilesBeforeCommit !== 'never') { + let documents = workspace.textDocuments .filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath)); - if (unsavedTextDocuments.length > 0) { - const message = unsavedTextDocuments.length === 1 - ? localize('unsaved files single', "The following file is unsaved: {0}.\n\nWould you like to save it before committing?", path.basename(unsavedTextDocuments[0].uri.fsPath)) - : localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before committing?", unsavedTextDocuments.length); + if (promptToSaveFilesBeforeCommit === 'staged') { + documents = documents + .filter(d => repository.indexGroup.resourceStates.some(s => s.resourceUri.path === d.uri.fsPath)); + } + + if (documents.length > 0) { + const message = documents.length === 1 + ? localize('unsaved files single', "The following file is unsaved and will not be included in the commit if you proceed: {0}.\n\nWould you like to save it before committing?", path.basename(documents[0].uri.fsPath)) + : localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before committing?", documents.length); const saveAndCommit = localize('save and commit', "Save All & Commit"); const commit = localize('commit', "Commit Anyway"); const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit); if (pick === saveAndCommit) { - await Promise.all(unsavedTextDocuments.map(d => d.save())); - await repository.status(); + await Promise.all(documents.map(d => d.save())); + await repository.add(documents.map(d => d.uri)); } else if (pick !== commit) { return false; // do not commit on cancel } @@ -1255,15 +1282,24 @@ export class CommandCenter { // no changes, and the user has not configured to commit all in this case if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit) { + const suggestSmartCommit = config.get('suggestSmartCommit') === true; + + if (!suggestSmartCommit) { + return false; + } // prompt the user if we want to commit all or not const message = localize('no staged changes', "There are no staged changes to commit.\n\nWould you like to automatically stage all your changes and commit them directly?"); const yes = localize('yes', "Yes"); const always = localize('always', "Always"); - const pick = await window.showWarningMessage(message, { modal: true }, yes, always); + const never = localize('never', "Never"); + const pick = await window.showWarningMessage(message, { modal: true }, yes, always, never); if (pick === always) { config.update('enableSmartCommit', true, true); + } else if (pick === never) { + config.update('suggestSmartCommit', false, true); + return false; } else if (pick !== yes) { return false; // do not commit on cancel } @@ -1301,6 +1337,10 @@ export class CommandCenter { return false; } + if (opts.all && config.get<'all' | 'tracked'>('smartCommitChanges') === 'tracked') { + opts.all = 'tracked'; + } + await repository.commit(message, opts); const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand'); @@ -1350,19 +1390,6 @@ export class CommandCenter { await this.commitWithAnyInput(repository); } - @command('git.commitWithInput', { repository: true }) - async commitWithInput(repository: Repository): Promise { - if (!repository.inputBox.value) { - return; - } - - const didCommit = await this.smartCommit(repository, async () => repository.inputBox.value); - - if (didCommit) { - repository.inputBox.value = await repository.getCommitTemplate(); - } - } - @command('git.commitStaged', { repository: true }) async commitStaged(repository: Repository): Promise { await this.commitWithAnyInput(repository, { all: false }); @@ -1487,12 +1514,12 @@ export class CommandCenter { await this._branch(repository, undefined, true); } - private async _branch(repository: Repository, defaultName?: string, from = false): Promise { + private async promptForBranchName(defaultName?: string): Promise { const config = workspace.getConfiguration('git'); const branchWhitespaceChar = config.get('branchWhitespaceChar')!; const branchValidationRegex = config.get('branchValidationRegex')!; const sanitize = (name: string) => name ? - name.trim().replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar) + name.trim().replace(/^-+/, '').replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar) : name; const rawBranchName = defaultName || await window.showInputBox({ @@ -1509,7 +1536,11 @@ export class CommandCenter { } }); - const branchName = sanitize(rawBranchName || ''); + return sanitize(rawBranchName || ''); + } + + private async _branch(repository: Repository, defaultName?: string, from = false): Promise { + const branchName = await this.promptForBranchName(defaultName); if (!branchName) { return; @@ -1571,25 +1602,21 @@ export class CommandCenter { @command('git.renameBranch', { repository: true }) async renameBranch(repository: Repository): Promise { - const name = await window.showInputBox({ - placeHolder: localize('branch name', "Branch name"), - prompt: localize('provide branch name', "Please provide a branch name"), - value: repository.HEAD && repository.HEAD.name - }); + const branchName = await this.promptForBranchName(); - if (!name || name.trim().length === 0) { + if (!branchName) { return; } try { - await repository.renameBranch(name); + await repository.renameBranch(branchName); } catch (err) { switch (err.gitErrorCode) { case GitErrorCodes.InvalidBranchName: window.showErrorMessage(localize('invalid branch name', 'Invalid branch name')); return; case GitErrorCodes.BranchAlreadyExists: - window.showErrorMessage(localize('branch already exists', "A branch named '{0}' already exists", name)); + window.showErrorMessage(localize('branch already exists', "A branch named '{0}' already exists", branchName)); return; default: throw err; @@ -1913,7 +1940,17 @@ export class CommandCenter { private async _sync(repository: Repository, rebase: boolean): Promise { const HEAD = repository.HEAD; - if (!HEAD || !HEAD.upstream) { + if (!HEAD) { + return; + } else if (!HEAD.upstream) { + const branchName = HEAD.name; + const message = localize('confirm publish branch', "The branch '{0}' has no upstream branch. Would you like to publish this branch?", branchName); + const yes = localize('ok', "OK"); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + + if (pick === yes) { + await this.publish(repository); + } return; } @@ -1945,8 +1982,16 @@ export class CommandCenter { } @command('git.sync', { repository: true }) - sync(repository: Repository): Promise { - return this._sync(repository, false); + async sync(repository: Repository): Promise { + try { + await this._sync(repository, false); + } catch (err) { + if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { + return; + } + + throw err; + } } @command('git._syncAll') @@ -1963,8 +2008,16 @@ export class CommandCenter { } @command('git.syncRebase', { repository: true }) - syncRebase(repository: Repository): Promise { - return this._sync(repository, true); + async syncRebase(repository: Repository): Promise { + try { + await this._sync(repository, true); + } catch (err) { + if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { + return; + } + + throw err; + } } @command('git.publish', { repository: true }) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 30348608dd0..412cc5df92f 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -11,12 +11,15 @@ import * as which from 'which'; import { EventEmitter } from 'events'; import iconv = require('iconv-lite'); import * as filetype from 'file-type'; -import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent } from './util'; +import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util'; import { CancellationToken } from 'vscode'; import { URI } from 'vscode-uri'; import { detectEncoding } from './encoding'; import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git'; +// https://github.com/microsoft/vscode/issues/65693 +const MAX_CLI_LENGTH = 30000; + const readfile = denodeify(fs.readFile); export interface IGit { @@ -339,7 +342,7 @@ export class Git { } async clone(url: string, parentPath: string, cancellationToken?: CancellationToken): Promise { - let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*\//, '').replace(/\.git$/, '') || 'repository'; + let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*[\/\\]/, '').replace(/\.git$/, '') || 'repository'; let folderName = baseFolderName; let folderPath = path.join(parentPath, folderName); let count = 1; @@ -598,13 +601,13 @@ export function parseGitmodules(raw: string): Submodule[] { } export function parseGitCommit(raw: string): Commit | null { - const match = /^([0-9a-f]{40})\n(.*)\n(.*)\n([^]*)$/m.exec(raw.trim()); + const match = /^([0-9a-f]{40})\n(.*)\n(.*)(\n([^]*))?$/m.exec(raw.trim()); if (!match) { return null; } const parents = match[3] ? match[3].split(' ') : []; - return { hash: match[1], message: match[4], parents, authorEmail: match[2] }; + return { hash: match[1], message: match[5], parents, authorEmail: match[2] }; } interface LsTreeElement { @@ -639,7 +642,7 @@ export function parseLsFiles(raw: string): LsFilesElement[] { } export interface CommitOptions { - all?: boolean; + all?: boolean | 'tracked'; amend?: boolean; signoff?: boolean; signCommit?: boolean; @@ -649,6 +652,7 @@ export interface CommitOptions { export interface PullOptions { unshallow?: boolean; tags?: boolean; + readonly cancellationToken?: CancellationToken; } export enum ForcePushMode { @@ -797,7 +801,7 @@ export class Repository { const elements = await this.lsfiles(path); if (elements.length === 0) { - throw new GitError({ message: 'Error running ls-files' }); + throw new GitError({ message: 'Path not known by git', gitErrorCode: GitErrorCodes.UnknownPath }); } const { mode, object } = elements[0]; @@ -810,7 +814,7 @@ export class Repository { const elements = await this.lstree(treeish, path); if (elements.length === 0) { - throw new GitError({ message: 'Error running ls-files' }); + throw new GitError({ message: 'Path not known by git', gitErrorCode: GitErrorCodes.UnknownPath }); } const { mode, object, size } = elements[0]; @@ -1077,8 +1081,16 @@ export class Repository { return result.stdout.trim(); } - async add(paths: string[]): Promise { - const args = ['add', '-A', '--']; + async add(paths: string[], opts?: { update?: boolean }): Promise { + const args = ['add']; + + if (opts && opts.update) { + args.push('-u'); + } else { + args.push('-A'); + } + + args.push('--'); if (paths && paths.length) { args.push.apply(args, paths); @@ -1116,15 +1128,21 @@ export class Repository { } let mode: string; + let add: string = ''; try { const details = await this.getObjectDetails('HEAD', path); mode = details.mode; } catch (err) { + if (err.gitErrorCode !== GitErrorCodes.UnknownPath) { + throw err; + } + mode = '100644'; + add = '--add'; } - await this.run(['update-index', '--cacheinfo', mode, hash, path]); + await this.run(['update-index', add, '--cacheinfo', mode, hash, path]); } async checkout(treeish: string, paths: string[], opts: { track?: boolean } = Object.create(null)): Promise { @@ -1138,13 +1156,14 @@ export class Repository { args.push(treeish); } - if (paths && paths.length) { - args.push('--'); - args.push.apply(args, paths); - } - try { - await this.run(args); + if (paths && paths.length > 0) { + for (const chunk of splitInChunks(paths, MAX_CLI_LENGTH)) { + await this.run([...args, '--', ...chunk]); + } + } else { + await this.run(args); + } } catch (err) { if (/Please,? commit your changes or stash them/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.DirtyWorkTree; @@ -1275,11 +1294,17 @@ export class Repository { async clean(paths: string[]): Promise { const pathsByGroup = groupBy(paths, p => path.dirname(p)); const groups = Object.keys(pathsByGroup).map(k => pathsByGroup[k]); - const tasks = groups.map(paths => () => this.run(['clean', '-f', '-q', '--'].concat(paths))); - for (let task of tasks) { - await task(); + const limiter = new Limiter(5); + const promises: Promise[] = []; + + for (const paths of groups) { + for (const chunk of splitInChunks(paths, MAX_CLI_LENGTH)) { + promises.push(limiter.queue(() => this.run(['clean', '-f', '-q', '--', ...chunk]))); + } } + + await Promise.all(promises); } async undo(): Promise { @@ -1396,7 +1421,7 @@ export class Repository { } try { - await this.run(args); + await this.run(args, options); } catch (err) { if (/^CONFLICT \([^)]+\): \b/m.test(err.stdout || '')) { err.gitErrorCode = GitErrorCodes.Conflict; @@ -1599,8 +1624,14 @@ export class Repository { .map(([ref]) => ({ name: ref, type: RefType.Head } as Branch)); } - async getRefs(): Promise { - const result = await this.run(['for-each-ref', '--format', '%(refname) %(objectname)', '--sort', '-committerdate']); + async getRefs(opts?: { sort?: 'alphabetically' | 'committerdate' }): Promise { + const args = ['for-each-ref', '--format', '%(refname) %(objectname)']; + + if (opts && opts.sort && opts.sort !== 'alphabetically') { + args.push('--sort', opts.sort); + } + + const result = await this.run(args); const fn = (line: string): Ref | null => { let match: RegExpExecArray | null; @@ -1669,13 +1700,16 @@ export class Repository { async getBranch(name: string): Promise { if (name === 'HEAD') { return this.getHEAD(); - } else if (/^@/.test(name)) { - const symbolicFullNameResult = await this.run(['rev-parse', '--symbolic-full-name', name]); - const symbolicFullName = symbolicFullNameResult.stdout.trim(); - name = symbolicFullName || name; } - const result = await this.run(['rev-parse', name]); + let result = await this.run(['rev-parse', name]); + + if (!result.stdout && /^@/.test(name)) { + const symbolicFullNameResult = await this.run(['rev-parse', '--symbolic-full-name', name]); + name = symbolicFullNameResult.stdout.trim(); + + result = await this.run(['rev-parse', name]); + } if (!result.stdout) { return Promise.reject(new Error('No such branch')); @@ -1732,7 +1766,7 @@ export class Repository { } const raw = await readfile(templatePath, 'utf8'); - return raw.replace(/^\s*#.*$\n?/gm, '').trim(); + return raw.replace(/\n?#.*/g, ''); } catch (err) { return ''; @@ -1745,8 +1779,11 @@ export class Repository { } async updateSubmodules(paths: string[]): Promise { - const args = ['submodule', 'update', '--', ...paths]; - await this.run(args); + const args = ['submodule', 'update', '--']; + + for (const chunk of splitInChunks(paths, MAX_CLI_LENGTH)) { + await this.run([...args, ...chunk]); + } } async getSubmodules(): Promise { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 196a1bddade..bf37d036d5c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env } from 'vscode'; +import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env, ProgressOptions, CancellationToken } from 'vscode'; import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git'; -import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, combinedDisposable, watch, IFileWatcher } from './util'; +import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, combinedDisposable } from './util'; import { memoize, throttle, debounce } from './decorators'; import { toGitUri } from './uri'; import { AutoFetcher } from './autofetch'; @@ -14,6 +14,7 @@ import * as nls from 'vscode-nls'; import * as fs from 'fs'; import { StatusBarCommands } from './statusbar'; import { Branch, Ref, Remote, RefType, GitErrorCodes, Status, LogOptions, Change } from './api/git'; +import { IFileWatcher, watch } from './watch'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -678,12 +679,15 @@ export class Repository implements Disposable { const root = Uri.file(repository.root); this._sourceControl = scm.createSourceControl('git', 'Git', root); - this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message (press {0} to commit)"); - this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit"), arguments: [this._sourceControl] }; + + this._sourceControl.acceptInputCommand = { command: 'git.commit', title: localize('commit', "Commit"), arguments: [this._sourceControl] }; this._sourceControl.quickDiffProvider = this; this._sourceControl.inputBox.validateInput = this.validateInput.bind(this); this.disposables.push(this._sourceControl); + this.updateInputBoxPlaceholder(); + this.disposables.push(this.onDidRunGitStatus(() => this.updateInputBoxPlaceholder())); + this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "MERGE CHANGES")); this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "STAGED CHANGES")); this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "CHANGES")); @@ -697,6 +701,9 @@ export class Repository implements Disposable { onConfigListener(updateIndexGroupVisibility, this, this.disposables); updateIndexGroupVisibility(); + const onConfigListenerForBranchSortOrder = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchSortOrder', root)); + onConfigListenerForBranchSortOrder(this.updateModelState, this, this.disposables); + this.mergeGroup.hideWhenEmpty = true; this.disposables.push(this.mergeGroup); @@ -723,6 +730,10 @@ export class Repository implements Disposable { const progressManager = new ProgressManager(this); this.disposables.push(progressManager); + const onDidChangeCountBadge = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.countBadge', root)); + onDidChangeCountBadge(this.setCountBadge, this, this.disposables); + this.setCountBadge(); + this.updateCommitTemplate(); } @@ -905,7 +916,8 @@ export class Repository implements Disposable { if (this.rebaseCommit) { await this.run(Operation.RebaseContinue, async () => { if (opts.all) { - await this.repository.add([]); + const addOpts = opts.all === 'tracked' ? { update: true } : {}; + await this.repository.add([], addOpts); } await this.repository.rebaseContinue(); @@ -913,9 +925,11 @@ export class Repository implements Disposable { } else { await this.run(Operation.Commit, async () => { if (opts.all) { - await this.repository.add([]); + const addOpts = opts.all === 'tracked' ? { update: true } : {}; + await this.repository.add([], addOpts); } + delete opts.all; await this.repository.commit(message, opts); }); } @@ -956,21 +970,9 @@ export class Repository implements Disposable { } }); - const promises: Promise[] = []; - - if (toClean.length > 0) { - promises.push(this.repository.clean(toClean)); - } - - if (toCheckout.length > 0) { - promises.push(this.repository.checkout('', toCheckout)); - } - - if (submodulesToUpdate.length > 0) { - promises.push(this.repository.updateSubmodules(submodulesToUpdate)); - } - - await Promise.all(promises); + await this.repository.clean(toClean); + await this.repository.checkout('', toCheckout); + await this.repository.updateSubmodules(submodulesToUpdate); }); } @@ -1146,11 +1148,22 @@ export class Repository implements Disposable { const config = workspace.getConfiguration('git', Uri.file(this.root)); const fetchOnPull = config.get('fetchOnPull'); const tags = config.get('pullTags'); + const supportCancellation = config.get('supportCancellation'); - if (fetchOnPull) { - await this.repository.pull(rebase, undefined, undefined, { tags }); + const fn = fetchOnPull + ? async (cancellationToken?: CancellationToken) => await this.repository.pull(rebase, undefined, undefined, { tags, cancellationToken }) + : async (cancellationToken?: CancellationToken) => await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken }); + + if (supportCancellation) { + const opts: ProgressOptions = { + location: ProgressLocation.Notification, + title: localize('sync is unpredictable', "Syncing. Cancelling may cause serious damages to the repository"), + cancellable: true + }; + + await window.withProgress(opts, (_, token) => fn(token)); } else { - await this.repository.pull(rebase, remoteName, pullBranch, { tags }); + await fn(); } const remote = this.remotes.find(r => r.name === remoteName); @@ -1395,7 +1408,6 @@ export class Repository implements Disposable { const config = workspace.getConfiguration('git'); const shouldIgnore = config.get('ignoreLimitWarning') === true; const useIcons = !config.get('decorations.enabled', true); - this.isRepositoryHuge = didHitLimit; if (didHitLimit && !shouldIgnore && !this.didWarnAboutLimit) { @@ -1445,7 +1457,8 @@ export class Repository implements Disposable { // noop } - const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]); + const sort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder') || 'alphabetically'; + const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs({ sort }), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]); this._HEAD = HEAD; this._refs = refs; @@ -1495,15 +1508,7 @@ export class Repository implements Disposable { this.workingTreeGroup.resourceStates = workingTree; // set count badge - const countBadge = workspace.getConfiguration('git').get('countBadge'); - let count = merge.length + index.length + workingTree.length; - - switch (countBadge) { - case 'off': count = 0; break; - case 'tracked': count = count - workingTree.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; break; - } - - this._sourceControl.count = count; + this.setCountBadge(); // Disable `Discard All Changes` for "fresh" repositories // https://github.com/Microsoft/vscode/issues/43066 @@ -1517,6 +1522,18 @@ export class Repository implements Disposable { this._onDidChangeStatus.fire(); } + private setCountBadge(): void { + const countBadge = workspace.getConfiguration('git').get('countBadge'); + let count = this.mergeGroup.resourceStates.length + this.indexGroup.resourceStates.length + this.workingTreeGroup.resourceStates.length; + + switch (countBadge) { + case 'off': count = 0; break; + case 'tracked': count = count - this.workingTreeGroup.resourceStates.filter(r => r.type === Status.UNTRACKED || r.type === Status.IGNORED).length; break; + } + + this._sourceControl.count = count; + } + private async getRebaseCommit(): Promise { const rebaseHeadPath = path.join(this.repository.root, '.git', 'REBASE_HEAD'); const rebaseApplyPath = path.join(this.repository.root, '.git', 'rebase-apply'); @@ -1638,6 +1655,21 @@ export class Repository implements Disposable { return `${this.HEAD.behind}↓ ${this.HEAD.ahead}↑`; } + private updateInputBoxPlaceholder(): void { + const HEAD = this.HEAD; + + if (HEAD) { + const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; + const tagName = tag && tag.name; + const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8); + + // '{0}' will be replaced by the corresponding key-command later in the process, which is why it needs to stay. + this._sourceControl.inputBox.placeholder = localize('commitMessageWithHeadLabel', "Message ({0} to commit on '{1}')", "{0}", head); + } else { + this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message ({0} to commit)"); + } + } + dispose(): void { this.disposables = dispose(this.disposables); } diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index cc007172d5f..fb89aca7264 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -5,7 +5,7 @@ import { Disposable, Command, EventEmitter, Event, workspace, Uri } from 'vscode'; import { Repository, Operation } from './repository'; -import { anyEvent, dispose } from './util'; +import { anyEvent, dispose, filterEvent } from './util'; import * as nls from 'vscode-nls'; import { Branch } from './api/git'; @@ -27,7 +27,7 @@ class CheckoutStatusBar { return { command: 'git.checkout', - tooltip: localize('checkout', 'Checkout...'), + tooltip: `${this.repository.headLabel}`, title, arguments: [this.repository.sourceControl] }; @@ -39,6 +39,7 @@ class CheckoutStatusBar { } interface SyncStatusBarState { + enabled: boolean; isSyncRunning: boolean; hasRemotes: boolean; HEAD: Branch | undefined; @@ -47,6 +48,7 @@ interface SyncStatusBarState { class SyncStatusBar { private static StartState: SyncStatusBarState = { + enabled: true, isSyncRunning: false, hasRemotes: false, HEAD: undefined @@ -66,9 +68,20 @@ class SyncStatusBar { constructor(private repository: Repository) { repository.onDidRunGitStatus(this.onModelChange, this, this.disposables); repository.onDidChangeOperations(this.onOperationsChange, this, this.disposables); + + const onEnablementChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.enableStatusBarSync')); + onEnablementChange(this.updateEnablement, this, this.disposables); + this._onDidChange.fire(); } + private updateEnablement(): void { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const enabled = config.get('enableStatusBarSync', true); + + this.state = { ... this.state, enabled }; + } + private onOperationsChange(): void { const isSyncRunning = this.repository.operations.isRunning(Operation.Sync) || this.repository.operations.isRunning(Operation.Push) || @@ -86,7 +99,7 @@ class SyncStatusBar { } get command(): Command | undefined { - if (!this.state.hasRemotes) { + if (!this.state.enabled || !this.state.hasRemotes) { return undefined; } diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index f0444ce5795..a860c82352a 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -6,6 +6,7 @@ import 'mocha'; import { GitStatusParser, parseGitCommit, parseGitmodules, parseLsTree, parseLsFiles } from '../git'; import * as assert from 'assert'; +import { splitInChunks } from '../util'; suite('git', () => { suite('GitStatusParser', () => { @@ -292,4 +293,78 @@ This is a commit message.`; ]); }); }); -}); \ No newline at end of file + + suite('splitInChunks', () => { + test('unit tests', function () { + assert.deepEqual( + [...splitInChunks(['hello', 'there', 'cool', 'stuff'], 6)], + [['hello'], ['there'], ['cool'], ['stuff']] + ); + + assert.deepEqual( + [...splitInChunks(['hello', 'there', 'cool', 'stuff'], 10)], + [['hello', 'there'], ['cool', 'stuff']] + ); + + assert.deepEqual( + [...splitInChunks(['hello', 'there', 'cool', 'stuff'], 12)], + [['hello', 'there'], ['cool', 'stuff']] + ); + + assert.deepEqual( + [...splitInChunks(['hello', 'there', 'cool', 'stuff'], 14)], + [['hello', 'there', 'cool'], ['stuff']] + ); + + assert.deepEqual( + [...splitInChunks(['hello', 'there', 'cool', 'stuff'], 2000)], + [['hello', 'there', 'cool', 'stuff']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 1)], + [['0'], ['01'], ['012'], ['0'], ['01'], ['012'], ['0'], ['01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 2)], + [['0'], ['01'], ['012'], ['0'], ['01'], ['012'], ['0'], ['01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 3)], + [['0', '01'], ['012'], ['0', '01'], ['012'], ['0', '01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 4)], + [['0', '01'], ['012', '0'], ['01'], ['012', '0'], ['01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 5)], + [['0', '01'], ['012', '0'], ['01', '012'], ['0', '01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 6)], + [['0', '01', '012'], ['0', '01', '012'], ['0', '01', '012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 7)], + [['0', '01', '012', '0'], ['01', '012', '0'], ['01', '012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 8)], + [['0', '01', '012', '0'], ['01', '012', '0', '01'], ['012']] + ); + + assert.deepEqual( + [...splitInChunks(['0', '01', '012', '0', '01', '012', '0', '01', '012'], 9)], + [['0', '01', '012', '0', '01'], ['012', '0', '01', '012']] + ); + }); + }); +}); diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index c4e93850619..d2e201ca01d 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, EventEmitter, Uri } from 'vscode'; -import { dirname, sep, join } from 'path'; +import { Event } from 'vscode'; +import { dirname, sep } from 'path'; import { Readable } from 'stream'; import * as fs from 'fs'; import * as byline from 'byline'; @@ -345,18 +345,69 @@ export function pathEquals(a: string, b: string): boolean { return a === b; } -export interface IFileWatcher extends IDisposable { - readonly event: Event; +export function* splitInChunks(array: string[], maxChunkLength: number): IterableIterator { + let current: string[] = []; + let length = 0; + + for (const value of array) { + let newLength = length + value.length; + + if (newLength > maxChunkLength && current.length > 0) { + yield current; + current = []; + newLength = value.length; + } + + current.push(value); + length = newLength; + } + + if (current.length > 0) { + yield current; + } } -export function watch(location: string): IFileWatcher { - const dotGitWatcher = fs.watch(location); - const onDotGitFileChangeEmitter = new EventEmitter(); - dotGitWatcher.on('change', (_, e) => onDotGitFileChangeEmitter.fire(Uri.file(join(location, e as string)))); - dotGitWatcher.on('error', err => console.error(err)); - - return new class implements IFileWatcher { - event = onDotGitFileChangeEmitter.event; - dispose() { dotGitWatcher.close(); } - }; +interface ILimitedTaskFactory { + factory: () => Promise; + c: (value?: T | Promise) => void; + e: (error?: any) => void; +} + +export class Limiter { + + private runningPromises: number; + private maxDegreeOfParalellism: number; + private outstandingPromises: ILimitedTaskFactory[]; + + constructor(maxDegreeOfParalellism: number) { + this.maxDegreeOfParalellism = maxDegreeOfParalellism; + this.outstandingPromises = []; + this.runningPromises = 0; + } + + queue(factory: () => Promise): Promise { + return new Promise((c, e) => { + this.outstandingPromises.push({ factory, c, e }); + this.consume(); + }); + } + + private consume(): void { + while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) { + const iLimitedTask = this.outstandingPromises.shift()!; + this.runningPromises++; + + const promise = iLimitedTask.factory(); + promise.then(iLimitedTask.c, iLimitedTask.e); + promise.then(() => this.consumed(), () => this.consumed()); + } + } + + private consumed(): void { + this.runningPromises--; + + if (this.outstandingPromises.length > 0) { + this.consume(); + } + } } diff --git a/extensions/git/src/watch.ts b/extensions/git/src/watch.ts new file mode 100644 index 00000000000..c6670cfa884 --- /dev/null +++ b/extensions/git/src/watch.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, EventEmitter, Uri } from 'vscode'; +import { join } from 'path'; +import * as fs from 'fs'; +import { IDisposable } from './util'; + +export interface IFileWatcher extends IDisposable { + readonly event: Event; +} + +export function watch(location: string): IFileWatcher { + const dotGitWatcher = fs.watch(location); + const onDotGitFileChangeEmitter = new EventEmitter(); + dotGitWatcher.on('change', (_, e) => onDotGitFileChangeEmitter.fire(Uri.file(join(location, e as string)))); + dotGitWatcher.on('error', err => console.error(err)); + + return new class implements IFileWatcher { + event = onDotGitFileChangeEmitter.event; + dispose() { dotGitWatcher.close(); } + }; +} diff --git a/extensions/grunt/src/main.ts b/extensions/grunt/src/main.ts index deec1587cc2..22b6e9e41cd 100644 --- a/extensions/grunt/src/main.ts +++ b/extensions/grunt/src/main.ts @@ -70,12 +70,27 @@ interface GruntTaskDefinition extends vscode.TaskDefinition { file?: string; } +async function findGruntCommand(rootPath: string): Promise { + let command: string; + let platform = process.platform; + if (platform === 'win32' && await exists(path.join(rootPath!, 'node_modules', '.bin', 'grunt.cmd'))) { + command = path.join('.', 'node_modules', '.bin', 'grunt.cmd'); + } else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(rootPath!, 'node_modules', '.bin', 'grunt'))) { + command = path.join('.', 'node_modules', '.bin', 'grunt'); + } else { + command = 'grunt'; + } + return command; +} + class FolderDetector { private fileWatcher: vscode.FileSystemWatcher | undefined; private promise: Thenable | undefined; - constructor(private _workspaceFolder: vscode.WorkspaceFolder) { + constructor( + private _workspaceFolder: vscode.WorkspaceFolder, + private _gruntCommand: Promise) { } public get workspaceFolder(): vscode.WorkspaceFolder { @@ -95,10 +110,28 @@ class FolderDetector { } public async getTasks(): Promise { - if (!this.promise) { - this.promise = this.computeTasks(); + if (this.isEnabled()) { + if (!this.promise) { + this.promise = this.computeTasks(); + } + return this.promise; + } else { + return []; } - return this.promise; + } + + public async getTask(_task: vscode.Task): Promise { + const gruntTask = (_task.definition).task; + if (gruntTask) { + let kind: GruntTaskDefinition = (_task.definition); + let options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; + let source = 'grunt'; + let task = gruntTask.indexOf(' ') === -1 + ? new vscode.Task(kind, this.workspaceFolder, gruntTask, source, new vscode.ShellExecution(`${await this._gruntCommand} ${name}`, options)) + : new vscode.Task(kind, this.workspaceFolder, gruntTask, source, new vscode.ShellExecution(`${await this._gruntCommand} "${name}"`, options)); + return task; + } + return undefined; } private async computeTasks(): Promise { @@ -111,17 +144,7 @@ class FolderDetector { return emptyTasks; } - let command: string; - let platform = process.platform; - if (platform === 'win32' && await exists(path.join(rootPath!, 'node_modules', '.bin', 'grunt.cmd'))) { - command = path.join('.', 'node_modules', '.bin', 'grunt.cmd'); - } else if ((platform === 'linux' || platform === 'darwin') && await exists(path.join(rootPath!, 'node_modules', '.bin', 'grunt'))) { - command = path.join('.', 'node_modules', '.bin', 'grunt'); - } else { - command = 'grunt'; - } - - let commandLine = `${command} --help --no-color`; + let commandLine = `${await this._gruntCommand} --help --no-color`; try { let { stdout, stderr } = await exec(commandLine, { cwd: rootPath }); if (stderr) { @@ -168,8 +191,8 @@ class FolderDetector { let source = 'grunt'; let options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; let task = name.indexOf(' ') === -1 - ? new vscode.Task(kind, this.workspaceFolder, name, source, new vscode.ShellExecution(`${command} ${name}`, options)) - : new vscode.Task(kind, this.workspaceFolder, name, source, new vscode.ShellExecution(`${command} "${name}"`, options)); + ? new vscode.Task(kind, this.workspaceFolder, name, source, new vscode.ShellExecution(`${await this._gruntCommand} ${name}`, options)) + : new vscode.Task(kind, this.workspaceFolder, name, source, new vscode.ShellExecution(`${await this._gruntCommand} "${name}"`, options)); result.push(task); let lowerCaseTaskName = name.toLowerCase(); if (isBuildTask(lowerCaseTaskName)) { @@ -239,9 +262,9 @@ class TaskDetector { } } for (let add of added) { - let detector = new FolderDetector(add); + let detector = new FolderDetector(add, findGruntCommand(add.uri.fsPath)); + this.detectors.set(add.uri.toString(), detector); if (detector.isEnabled()) { - this.detectors.set(add.uri.toString(), detector); detector.start(); } } @@ -250,18 +273,16 @@ class TaskDetector { private updateConfiguration(): void { for (let detector of this.detectors.values()) { - if (!detector.isEnabled()) { - detector.dispose(); - this.detectors.delete(detector.workspaceFolder.uri.toString()); - } + detector.dispose(); + this.detectors.delete(detector.workspaceFolder.uri.toString()); } let folders = vscode.workspace.workspaceFolders; if (folders) { for (let folder of folders) { if (!this.detectors.has(folder.uri.toString())) { - let detector = new FolderDetector(folder); + let detector = new FolderDetector(folder, findGruntCommand(folder.uri.fsPath)); + this.detectors.set(folder.uri.toString(), detector); if (detector.isEnabled()) { - this.detectors.set(folder.uri.toString(), detector); detector.start(); } } @@ -272,12 +293,13 @@ class TaskDetector { private updateProvider(): void { if (!this.taskProvider && this.detectors.size > 0) { + const thisCapture = this; this.taskProvider = vscode.workspace.registerTaskProvider('grunt', { - provideTasks: () => { - return this.getTasks(); + provideTasks: (): Promise => { + return thisCapture.getTasks(); }, - resolveTask(_task: vscode.Task): vscode.Task | undefined { - return undefined; + resolveTask(_task: vscode.Task): Promise { + return thisCapture.getTask(_task); } }); } @@ -312,6 +334,24 @@ class TaskDetector { }); } } + + public async getTask(task: vscode.Task): Promise { + if (this.detectors.size === 0) { + return undefined; + } else if (this.detectors.size === 1) { + return this.detectors.values().next().value.getTask(task); + } else { + if ((task.scope === vscode.TaskScope.Workspace) || (task.scope === vscode.TaskScope.Global)) { + return undefined; + } else if (task.scope) { + const detector = this.detectors.get(task.scope.uri.toString()); + if (detector) { + return detector.getTask(task); + } + } + return undefined; + } + } } let detector: TaskDetector; diff --git a/extensions/gulp/src/main.ts b/extensions/gulp/src/main.ts index ddc07a2f5c5..6f9a9e49334 100644 --- a/extensions/gulp/src/main.ts +++ b/extensions/gulp/src/main.ts @@ -130,10 +130,7 @@ class FolderDetector { public async getTask(_task: vscode.Task): Promise { const gulpTask = (_task.definition).task; if (gulpTask) { - let kind: GulpTaskDefinition = { - type: 'gulp', - task: gulpTask - }; + let kind: GulpTaskDefinition = (_task.definition); let options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; let task = new vscode.Task(kind, this.workspaceFolder, gulpTask, 'gulp', new vscode.ShellExecution(await this._gulpCommand, [gulpTask], options)); return task; @@ -342,4 +339,4 @@ export function activate(_context: vscode.ExtensionContext): void { export function deactivate(): void { detector.dispose(); -} \ No newline at end of file +} diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 95a97f1110f..9b26165edbd 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,7 +9,7 @@ }, "main": "./out/htmlServerMain", "dependencies": { - "vscode-css-languageservice": "^4.0.3-next.1", + "vscode-css-languageservice": "^4.0.3-next.3", "vscode-html-languageservice": "^3.0.4-next.0", "vscode-languageserver": "^5.3.0-next.8", "vscode-languageserver-types": "3.15.0-next.2", diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 9ea59b6077c..f8687c97f63 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -229,10 +229,10 @@ supports-color@5.4.0: dependencies: has-flag "^3.0.0" -vscode-css-languageservice@^4.0.3-next.1: - version "4.0.3-next.1" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.1.tgz#e89d01ce0d79b3e6c2642f5e3ad73cb8160d38d9" - integrity sha512-Zrm5TeraVUJ8vRikWhFt259dQu+WK+Ie3K5UA8BB4kqcanoM+1mcnIt8fPkTXlZLbiEWElrkJ9yuYbDNkufeBg== +vscode-css-languageservice@^4.0.3-next.3: + version "4.0.3-next.3" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.0.3-next.3.tgz#eb7f642f2785d388d74a1a98fd14f7736a11e316" + integrity sha512-6j/y9ccecrq7/APLPEijx+uWHsEdTFH5ZQHG4ZMKjZx6euny27B1wvLCjpxKnZCWcHgmi7cMDLWpUdElvHjjPQ== dependencies: vscode-languageserver-types "^3.15.0-next.2" vscode-nls "^4.1.1" diff --git a/extensions/jake/src/main.ts b/extensions/jake/src/main.ts index 5a68447f94b..b8c6d5e7344 100644 --- a/extensions/jake/src/main.ts +++ b/extensions/jake/src/main.ts @@ -124,10 +124,7 @@ class FolderDetector { public async getTask(_task: vscode.Task): Promise { const jakeTask = (_task.definition).task; if (jakeTask) { - let kind: JakeTaskDefinition = { - type: 'jake', - task: jakeTask - }; + let kind: JakeTaskDefinition = (_task.definition); let options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; let task = new vscode.Task(kind, this.workspaceFolder, jakeTask, 'jake', new vscode.ShellExecution(await this._jakeCommand, [jakeTask], options)); return task; @@ -341,4 +338,4 @@ export function activate(_context: vscode.ExtensionContext): void { export function deactivate(): void { detector.dispose(); -} \ No newline at end of file +} diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index a90d452bddf..ed16712a061 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/cf7b1ec2c20b5fe28695249596128ff514e1a659", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/84238ef0fa1c7e4e2c9fe77875d832dab9f57e5d", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -605,7 +605,7 @@ "include": "#comment" }, { - "begin": "(?x)(?=((\\b(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.js" @@ -1350,7 +1350,7 @@ }, "object-literal-method-declaration": { "name": "meta.method.declaration.js", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.async.js" @@ -1371,7 +1371,7 @@ "include": "#function-body" }, { - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.async.js" @@ -1393,7 +1393,7 @@ ] }, "method-declaration-name": { - "begin": "(?x)(?=((\\b(?\\s*$)", + "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`)))+\\s*/>\\s*$)", "beginCaptures": { "1": { "name": "punctuation.definition.comment.js" diff --git a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json index 067eda2d5e6..ee685716166 100644 --- a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScriptReact.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/cf7b1ec2c20b5fe28695249596128ff514e1a659", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/84238ef0fa1c7e4e2c9fe77875d832dab9f57e5d", "name": "JavaScript (with React support)", "scopeName": "source.js.jsx", "patterns": [ @@ -605,7 +605,7 @@ "include": "#comment" }, { - "begin": "(?x)(?=((\\b(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.js.jsx" @@ -1350,7 +1350,7 @@ }, "object-literal-method-declaration": { "name": "meta.method.declaration.js.jsx", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.async.js.jsx" @@ -1371,7 +1371,7 @@ "include": "#function-body" }, { - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.async.js.jsx" @@ -1393,7 +1393,7 @@ ] }, "method-declaration-name": { - "begin": "(?x)(?=((\\b(?\\s*$)", + "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`)))+\\s*/>\\s*$)", "beginCaptures": { "1": { "name": "punctuation.definition.comment.js.jsx" diff --git a/extensions/json-language-features/.vscode/launch.json b/extensions/json-language-features/.vscode/launch.json index b2d26ad6c35..bb5cde48bfd 100644 --- a/extensions/json-language-features/.vscode/launch.json +++ b/extensions/json-language-features/.vscode/launch.json @@ -33,6 +33,14 @@ "sourceMaps": true, "outFiles": ["${workspaceFolder}/server/out"] } - + ], + "compounds": [ + { + "name": "Launch Extension and Attach Language Server", + "configurations": [ + "Launch Extension", + "Attach Language Server" + ] + } ] } \ No newline at end of file diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index 323e5f0ed94..6f1ca02d580 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -223,7 +223,7 @@ export function activate(context: ExtensionContext) { let languageConfiguration: LanguageConfiguration = { wordPattern: /("(?:[^\\\"]*(?:\\.)?)*"?)|[^\s{}\[\],:]+/, indentationRules: { - increaseIndentPattern: /^.*(\{[^}]*|\[[^\]]*)$/, + increaseIndentPattern: /({+(?=([^"]*"[^"]*")*[^"}]*$))|(\[+(?=([^"]*"[^"]*")*[^"\]]*$))/, decreaseIndentPattern: /^\s*[}\]],?\s*$/ } }; diff --git a/extensions/json/package.json b/extensions/json/package.json index 1606f6a3d64..e23e86b14a6 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -50,7 +50,8 @@ ".babelrc", ".jsonc", ".eslintrc", - ".eslintrc.json" + ".eslintrc.json", + "tslint.json" ], "configuration": "./language-configuration.json" } @@ -74,4 +75,4 @@ } ] } -} \ No newline at end of file +} diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index b94e9eba4a3..3fe94d8cf7e 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/a595d8ba2ae9ce8864435d33db2afa0fe68b1487", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/eb3898715b50d7911377a650e383a768a3a21f25", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -716,7 +716,7 @@ { "begin": "(^|\\G)(\\s*)(.*)", "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", - "contentName": "meta.embedded.block.cpp source.cpp", + "contentName": "meta.embedded.block.cpp", "patterns": [ { "include": "source.cpp" @@ -1855,12 +1855,12 @@ "name": "markup.fenced_code.block.markdown" }, "heading": { - "match": "(?:^|\\G)[ ]{0,3}((#{1,6})\\s*(?=[\\S[^#]]).*?\\s*(#{1,6})?)$\\n?", + "match": "(?:^|\\G)[ ]{0,3}((#{1,6})\\s+(?=[\\S[^#]]).*?\\s*(#{1,6})?)$\\n?", "captures": { "1": { "patterns": [ { - "match": "(#{6})\\s*(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", + "match": "(#{6})\\s+(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", "name": "heading.6.markdown", "captures": { "1": { @@ -1875,7 +1875,7 @@ } }, { - "match": "(#{5})\\s*(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", + "match": "(#{5})\\s+(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", "name": "heading.5.markdown", "captures": { "1": { @@ -1890,7 +1890,7 @@ } }, { - "match": "(#{4})\\s*(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", + "match": "(#{4})\\s+(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", "name": "heading.4.markdown", "captures": { "1": { @@ -1905,7 +1905,7 @@ } }, { - "match": "(#{3})\\s*(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", + "match": "(#{3})\\s+(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", "name": "heading.3.markdown", "captures": { "1": { @@ -1920,7 +1920,7 @@ } }, { - "match": "(#{2})\\s*(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", + "match": "(#{2})\\s+(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", "name": "heading.2.markdown", "captures": { "1": { @@ -1935,7 +1935,7 @@ } }, { - "match": "(#{1})\\s*(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", + "match": "(#{1})\\s+(?=[\\S[^#]])(.*?)\\s*(\\s+#+)?$\\n?", "name": "heading.1.markdown", "captures": { "1": { @@ -1987,25 +1987,46 @@ "name": "comment.block.html" }, { - "begin": "(^|\\G)\\s*(?=<(script|style|pre)(\\s|$|>)(?!.*?))", - "end": "(?=.*)", + "begin": "(?i)(^|\\G)\\s*(?=<(script|style|pre)(\\s|$|>)(?!.*?))", + "end": "(?i)(.*)(())", + "endCaptures": { + "1": { + "patterns": [ + { + "include": "text.html.derivative" + } + ] + }, + "2": { + "name": "meta.tag.structure.$4.end.html" + }, + "3": { + "name": "punctuation.definition.tag.begin.html" + }, + "4": { + "name": "entity.name.tag.html" + }, + "5": { + "name": "punctuation.definition.tag.end.html" + } + }, "patterns": [ { "begin": "(\\s*|$)", "patterns": [ { - "include": "text.html.basic" + "include": "text.html.derivative" } ], - "while": "^(?!.*)" + "while": "(?i)^(?!.*)" } ] }, { - "begin": "(^|\\G)\\s*(?=))", + "begin": "(?i)(^|\\G)\\s*(?=))", "patterns": [ { - "include": "text.html.basic" + "include": "text.html.derivative" } ], "while": "^(?!\\s*$)" @@ -2014,7 +2035,7 @@ "begin": "(^|\\G)\\s*(?=(<[a-zA-Z0-9\\-](/?>|\\s.*?>)|)\\s*$)", "patterns": [ { - "include": "text.html.basic" + "include": "text.html.derivative" } ], "while": "^(?!\\s*$)" @@ -2074,7 +2095,7 @@ "include": "#inline" }, { - "include": "text.html.basic" + "include": "text.html.derivative" }, { "include": "#heading-setext" @@ -2131,7 +2152,7 @@ "include": "#inline" }, { - "include": "text.html.basic" + "include": "text.html.derivative" }, { "include": "#heading-setext" @@ -2225,7 +2246,7 @@ "end": "(?<=>)", "patterns": [ { - "include": "text.html.basic" + "include": "text.html.derivative" } ] }, @@ -2275,7 +2296,7 @@ }, "bracket": { "comment": "Markdown will convert this for us. We match it so that the HTML grammar will not mark it up as invalid.", - "match": "<(?![a-z/?\\$!])", + "match": "<(?![a-zA-Z/?\\$!])", "name": "meta.other.valid-bracket.markdown" }, "escape": { @@ -2370,7 +2391,7 @@ "end": "(?<=>)", "patterns": [ { - "include": "text.html.basic" + "include": "text.html.derivative" } ] }, diff --git a/extensions/markdown-basics/test/colorize-results/test-33886_md.json b/extensions/markdown-basics/test/colorize-results/test-33886_md.json index 179172a5738..25799e89a36 100644 --- a/extensions/markdown-basics/test/colorize-results/test-33886_md.json +++ b/extensions/markdown-basics/test/colorize-results/test-33886_md.json @@ -111,7 +111,7 @@ }, { "c": "", - "t": "text.html.markdown meta.paragraph.markdown meta.tag.inline.code.end.html punctuation.definition.tag.end.html", + "t": "text.html.markdown meta.tag.inline.code.end.html punctuation.definition.tag.end.html", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -144,7 +144,7 @@ }, { "c": "", - "t": "text.html.markdown meta.paragraph.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.end.html", + "t": "text.html.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.end.html", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", @@ -254,7 +254,7 @@ }, { "c": "a", - "t": "text.html.markdown meta.paragraph.markdown", + "t": "text.html.markdown", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -265,7 +265,7 @@ }, { "c": "", - "t": "text.html.markdown meta.paragraph.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.end.html", + "t": "text.html.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.end.html", "r": { "dark_plus": "punctuation.definition.tag: #808080", "light_plus": "punctuation.definition.tag: #800000", diff --git a/extensions/markdown-language-features/media/index.js b/extensions/markdown-language-features/media/index.js index 00afdf196af..9a0fd91b45a 100644 --- a/extensions/markdown-language-features/media/index.js +++ b/extensions/markdown-language-features/media/index.js @@ -659,10 +659,20 @@ window.onload = () => { events_1.onceDocumentLoaded(() => { if (settings.scrollPreviewWithEditor) { setTimeout(() => { - const initialLine = +settings.line; - if (!isNaN(initialLine)) { - scrollDisabled = true; - scroll_sync_1.scrollToRevealSourceLine(initialLine); + // Try to scroll to fragment if available + if (state.fragment) { + const element = scroll_sync_1.getLineElementForFragment(state.fragment); + if (element) { + scrollDisabled = true; + scroll_sync_1.scrollToRevealSourceLine(element.line); + } + } + else { + const initialLine = +settings.line; + if (!isNaN(initialLine)) { + scrollDisabled = true; + scroll_sync_1.scrollToRevealSourceLine(initialLine); + } } }, 0); } @@ -940,6 +950,15 @@ function getEditorLineNumberForPageOffset(offset) { return null; } exports.getEditorLineNumberForPageOffset = getEditorLineNumberForPageOffset; +/** + * Try to find the html element by using a fragment id + */ +function getLineElementForFragment(fragment) { + return getCodeLineElements().find((element) => { + return element.element.id === fragment; + }); +} +exports.getLineElementForFragment = getLineElementForFragment; /***/ }), @@ -986,4 +1005,4 @@ exports.getSettings = getSettings; /***/ }) /******/ }); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vbm9kZV9tb2R1bGVzL2xvZGFzaC50aHJvdHRsZS9pbmRleC5qcyIsIndlYnBhY2s6Ly8vKHdlYnBhY2spL2J1aWxkaW4vZ2xvYmFsLmpzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2FjdGl2ZUxpbmVNYXJrZXIudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvZXZlbnRzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2luZGV4LnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL21lc3NhZ2luZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zY3JvbGwtc3luYy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zZXR0aW5ncy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQSx5REFBaUQsY0FBYztBQUMvRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBMkIsMEJBQTBCLEVBQUU7QUFDdkQseUNBQWlDLGVBQWU7QUFDaEQ7QUFDQTtBQUNBOztBQUVBO0FBQ0EsOERBQXNELCtEQUErRDs7QUFFckg7QUFDQTs7O0FBR0E7QUFDQTs7Ozs7Ozs7Ozs7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsT0FBTztBQUNsQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBLDhDQUE4QyxrQkFBa0I7QUFDaEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtREFBbUQsb0JBQW9CO0FBQ3ZFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsRUFBRTtBQUNiLGFBQWEsUUFBUTtBQUNyQjtBQUNBO0FBQ0Esb0JBQW9CO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVyxFQUFFO0FBQ2IsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOzs7Ozs7Ozs7Ozs7O0FDdGJBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLENBQUM7O0FBRUQ7QUFDQTtBQUNBO0FBQ0EsQ0FBQztBQUNEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsNENBQTRDOztBQUU1Qzs7Ozs7Ozs7Ozs7Ozs7O0FDbkJBOzs7Z0dBR2dHO0FBQ2hHLCtGQUF5RDtBQUV6RCxNQUFhLGdCQUFnQjtJQUc1Qiw4QkFBOEIsQ0FBQyxJQUFZO1FBQzFDLE1BQU0sRUFBRSxRQUFRLEVBQUUsR0FBRyxzQ0FBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNwRCxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFBSSxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUVELE9BQU8sQ0FBQyxNQUErQjtRQUN0QyxJQUFJLENBQUMsb0JBQW9CLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3pDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNoQyxJQUFJLENBQUMsUUFBUSxHQUFHLE1BQU0sQ0FBQztJQUN4QixDQUFDO0lBRUQsb0JBQW9CLENBQUMsT0FBZ0M7UUFDcEQsSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNiLE9BQU87U0FDUDtRQUNELE9BQU8sQ0FBQyxTQUFTLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsdUJBQXVCLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDNUUsQ0FBQztJQUVELGtCQUFrQixDQUFDLE9BQWdDO1FBQ2xELElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDYixPQUFPO1NBQ1A7UUFDRCxPQUFPLENBQUMsU0FBUyxJQUFJLG1CQUFtQixDQUFDO0lBQzFDLENBQUM7Q0FDRDtBQTNCRCw0Q0EyQkM7Ozs7Ozs7Ozs7Ozs7O0FDakNEOzs7Z0dBR2dHOztBQUVoRyxTQUFnQixrQkFBa0IsQ0FBQyxDQUFhO0lBQy9DLElBQUksUUFBUSxDQUFDLFVBQVUsS0FBSyxTQUFTLElBQUksUUFBUSxDQUFDLFVBQW9CLEtBQUssZUFBZSxFQUFFO1FBQzNGLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDLENBQUMsQ0FBQztLQUNqRDtTQUFNO1FBQ04sQ0FBQyxFQUFFLENBQUM7S0FDSjtBQUNGLENBQUM7QUFORCxnREFNQzs7Ozs7Ozs7Ozs7Ozs7QUNYRDs7O2dHQUdnRzs7QUFFaEcsOEdBQXNEO0FBQ3RELGdGQUE4QztBQUM5Qyx5RkFBb0Q7QUFDcEQsK0ZBQTJGO0FBQzNGLHNGQUFrRDtBQUNsRCx1R0FBNkM7QUFJN0MsSUFBSSxjQUFjLEdBQUcsSUFBSSxDQUFDO0FBQzFCLE1BQU0sTUFBTSxHQUFHLElBQUksbUNBQWdCLEVBQUUsQ0FBQztBQUN0QyxNQUFNLFFBQVEsR0FBRyxzQkFBVyxFQUFFLENBQUM7QUFFL0IsTUFBTSxNQUFNLEdBQUcsZ0JBQWdCLEVBQUUsQ0FBQztBQUVsQyxvQkFBb0I7QUFDcEIsSUFBSSxLQUFLLEdBQUcsa0JBQU8sQ0FBbUIsWUFBWSxDQUFDLENBQUM7QUFDcEQsTUFBTSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUV2QixNQUFNLFNBQVMsR0FBRyxpQ0FBcUIsQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUVoRCxNQUFNLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUN2QyxNQUFNLENBQUMsbUJBQW1CLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBRWhELE1BQU0sQ0FBQyxNQUFNLEdBQUcsR0FBRyxFQUFFO0lBQ3BCLGdCQUFnQixFQUFFLENBQUM7QUFDcEIsQ0FBQyxDQUFDO0FBRUYsMkJBQWtCLENBQUMsR0FBRyxFQUFFO0lBQ3ZCLElBQUksUUFBUSxDQUFDLHVCQUF1QixFQUFFO1FBQ3JDLFVBQVUsQ0FBQyxHQUFHLEVBQUU7WUFDZixNQUFNLFdBQVcsR0FBRyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7WUFDbkMsSUFBSSxDQUFDLEtBQUssQ0FBQyxXQUFXLENBQUMsRUFBRTtnQkFDeEIsY0FBYyxHQUFHLElBQUksQ0FBQztnQkFDdEIsc0NBQXdCLENBQUMsV0FBVyxDQUFDLENBQUM7YUFDdEM7UUFDRixDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7S0FDTjtBQUNGLENBQUMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxZQUFZLEdBQUcsQ0FBQyxHQUFHLEVBQUU7SUFDMUIsTUFBTSxRQUFRLEdBQUcsUUFBUSxDQUFDLENBQUMsSUFBWSxFQUFFLEVBQUU7UUFDMUMsY0FBYyxHQUFHLElBQUksQ0FBQztRQUN0QixzQ0FBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNoQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFFUCxPQUFPLENBQUMsSUFBWSxFQUFFLFFBQWEsRUFBRSxFQUFFO1FBQ3RDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUU7WUFDakIsUUFBUSxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7WUFDckIsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO1NBQ2Y7SUFDRixDQUFDLENBQUM7QUFDSCxDQUFDLENBQUMsRUFBRSxDQUFDO0FBRUwsSUFBSSxnQkFBZ0IsR0FBRyxRQUFRLENBQUMsR0FBRyxFQUFFO0lBQ3BDLE1BQU0sU0FBUyxHQUFvRCxFQUFFLENBQUM7SUFDdEUsSUFBSSxNQUFNLEdBQUcsUUFBUSxDQUFDLG9CQUFvQixDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2xELElBQUksTUFBTSxFQUFFO1FBQ1gsSUFBSSxDQUFDLENBQUM7UUFDTixLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUU7WUFDbkMsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBRXRCLElBQUksR0FBRyxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLEVBQUU7Z0JBQ3RDLEdBQUcsQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDO2FBQ2hDO1lBRUQsU0FBUyxDQUFDLElBQUksQ0FBQztnQkFDZCxFQUFFLEVBQUUsR0FBRyxDQUFDLEVBQUU7Z0JBQ1YsTUFBTSxFQUFFLEdBQUcsQ0FBQyxNQUFNO2dCQUNsQixLQUFLLEVBQUUsR0FBRyxDQUFDLEtBQUs7YUFDaEIsQ0FBQyxDQUFDO1NBQ0g7UUFFRCxTQUFTLENBQUMsV0FBVyxDQUFDLGlCQUFpQixFQUFFLFNBQVMsQ0FBQyxDQUFDO0tBQ3BEO0FBQ0YsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBRVAsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxHQUFHLEVBQUU7SUFDdEMsY0FBYyxHQUFHLElBQUksQ0FBQztJQUN0QixnQkFBZ0IsRUFBRSxDQUFDO0FBQ3BCLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUVULE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLEVBQUU7SUFDMUMsSUFBSSxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sS0FBSyxRQUFRLENBQUMsTUFBTSxFQUFFO1FBQzFDLE9BQU87S0FDUDtJQUVELFFBQVEsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUU7UUFDeEIsS0FBSyxnQ0FBZ0M7WUFDcEMsTUFBTSxDQUFDLDhCQUE4QixDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDdkQsTUFBTTtRQUVQLEtBQUssWUFBWTtZQUNoQixZQUFZLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDeEMsTUFBTTtLQUNQO0FBQ0YsQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBRVYsUUFBUSxDQUFDLGdCQUFnQixDQUFDLFVBQVUsRUFBRSxLQUFLLENBQUMsRUFBRTtJQUM3QyxJQUFJLENBQUMsUUFBUSxDQUFDLDJCQUEyQixFQUFFO1FBQzFDLE9BQU87S0FDUDtJQUVELHlCQUF5QjtJQUN6QixLQUFLLElBQUksSUFBSSxHQUFHLEtBQUssQ0FBQyxNQUFxQixFQUFFLElBQUksRUFBRSxJQUFJLEdBQUcsSUFBSSxDQUFDLFVBQXlCLEVBQUU7UUFDekYsSUFBSSxJQUFJLENBQUMsT0FBTyxLQUFLLEdBQUcsRUFBRTtZQUN6QixPQUFPO1NBQ1A7S0FDRDtJQUVELE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUM7SUFDM0IsTUFBTSxJQUFJLEdBQUcsOENBQWdDLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDdEQsSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUU7UUFDN0MsU0FBUyxDQUFDLFdBQVcsQ0FBQyxVQUFVLEVBQUUsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7S0FDOUQ7QUFDRixDQUFDLENBQUMsQ0FBQztBQUVILFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsS0FBSyxDQUFDLEVBQUU7SUFDMUMsSUFBSSxDQUFDLEtBQUssRUFBRTtRQUNYLE9BQU87S0FDUDtJQUVELElBQUksSUFBSSxHQUFRLEtBQUssQ0FBQyxNQUFNLENBQUM7SUFDN0IsT0FBTyxJQUFJLEVBQUU7UUFDWixJQUFJLElBQUksQ0FBQyxPQUFPLElBQUksSUFBSSxDQUFDLE9BQU8sS0FBSyxHQUFHLElBQUksSUFBSSxDQUFDLElBQUksRUFBRTtZQUN0RCxJQUFJLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxFQUFFO2dCQUM5QyxNQUFNO2FBQ047WUFDRCxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLGtCQUFrQixDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLG1CQUFtQixDQUFDLEVBQUU7Z0JBQ3RJLE1BQU0sQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsZ0NBQWdDLEVBQUUsRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksTUFBTSxDQUFDLElBQUksWUFBWSxDQUFDLFFBQVEsQ0FBQyxtQkFBbUIsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztnQkFDbEssU0FBUyxDQUFDLFdBQVcsQ0FBQyxXQUFXLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztnQkFDdkQsS0FBSyxDQUFDLGNBQWMsRUFBRSxDQUFDO2dCQUN2QixLQUFLLENBQUMsZUFBZSxFQUFFLENBQUM7Z0JBQ3hCLE1BQU07YUFDTjtZQUNELE1BQU07U0FDTjtRQUNELElBQUksR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDO0tBQ3ZCO0FBQ0YsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBRVQsSUFBSSxRQUFRLENBQUMsdUJBQXVCLEVBQUU7SUFDckMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxRQUFRLENBQUMsR0FBRyxFQUFFO1FBQy9DLElBQUksY0FBYyxFQUFFO1lBQ25CLGNBQWMsR0FBRyxLQUFLLENBQUM7U0FDdkI7YUFBTTtZQUNOLE1BQU0sSUFBSSxHQUFHLDhDQUFnQyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUM5RCxJQUFJLE9BQU8sSUFBSSxLQUFLLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRTtnQkFDN0MsU0FBUyxDQUFDLFdBQVcsQ0FBQyxZQUFZLEVBQUUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO2dCQUM5QyxLQUFLLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztnQkFDbEIsTUFBTSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQzthQUN2QjtTQUNEO0lBQ0YsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7Q0FDUjtBQUVELFNBQVMsWUFBWSxDQUFDLElBQVk7SUFDakMsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLDBCQUEwQixFQUFFLE1BQU0sQ0FBQyxDQUFDO0FBQ3pELENBQUM7Ozs7Ozs7Ozs7Ozs7O0FDbktEOzs7Z0dBR2dHOztBQUVoRyxzRkFBeUM7QUFTNUIsNkJBQXFCLEdBQUcsQ0FBQyxNQUFXLEVBQUUsRUFBRTtJQUNwRCxPQUFPLElBQUk7UUFDVixXQUFXLENBQUMsSUFBWSxFQUFFLElBQVk7WUFDckMsTUFBTSxDQUFDLFdBQVcsQ0FBQztnQkFDbEIsSUFBSTtnQkFDSixNQUFNLEVBQUUsc0JBQVcsRUFBRSxDQUFDLE1BQU07Z0JBQzVCLElBQUk7YUFDSixDQUFDLENBQUM7UUFDSixDQUFDO0tBQ0QsQ0FBQztBQUNILENBQUMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7QUN4QkY7OztnR0FHZ0c7O0FBRWhHLHNGQUF5QztBQUd6QyxTQUFTLEtBQUssQ0FBQyxHQUFXLEVBQUUsR0FBVyxFQUFFLEtBQWE7SUFDckQsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDO0FBQzVDLENBQUM7QUFFRCxTQUFTLFNBQVMsQ0FBQyxJQUFZO0lBQzlCLE9BQU8sS0FBSyxDQUFDLENBQUMsRUFBRSxzQkFBVyxFQUFFLENBQUMsU0FBUyxHQUFHLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUNwRCxDQUFDO0FBUUQsTUFBTSxtQkFBbUIsR0FBRyxDQUFDLEdBQUcsRUFBRTtJQUNqQyxJQUFJLFFBQTJCLENBQUM7SUFDaEMsT0FBTyxHQUFHLEVBQUU7UUFDWCxJQUFJLENBQUMsUUFBUSxFQUFFO1lBQ2QsUUFBUSxHQUFHLENBQUMsRUFBRSxPQUFPLEVBQUUsUUFBUSxDQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNqRCxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsQ0FBQyxzQkFBc0IsQ0FBQyxXQUFXLENBQUMsRUFBRTtnQkFDbkUsTUFBTSxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLFdBQVcsQ0FBRSxDQUFDO2dCQUNqRCxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFO29CQUNqQixRQUFRLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLE9BQXNCLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztpQkFDekQ7YUFDRDtTQUNEO1FBQ0QsT0FBTyxRQUFRLENBQUM7SUFDakIsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUVMOzs7OztHQUtHO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsVUFBa0I7SUFDMUQsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUMxQyxNQUFNLEtBQUssR0FBRyxtQkFBbUIsRUFBRSxDQUFDO0lBQ3BDLElBQUksUUFBUSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUM7SUFDaEMsS0FBSyxNQUFNLEtBQUssSUFBSSxLQUFLLEVBQUU7UUFDMUIsSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLFVBQVUsRUFBRTtZQUM5QixPQUFPLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUM7U0FDNUM7YUFBTSxJQUFJLEtBQUssQ0FBQyxJQUFJLEdBQUcsVUFBVSxFQUFFO1lBQ25DLE9BQU8sRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDO1NBQ2pDO1FBQ0QsUUFBUSxHQUFHLEtBQUssQ0FBQztLQUNqQjtJQUNELE9BQU8sRUFBRSxRQUFRLEVBQUUsQ0FBQztBQUNyQixDQUFDO0FBYkQsNERBYUM7QUFFRDs7R0FFRztBQUNILFNBQWdCLDJCQUEyQixDQUFDLE1BQWM7SUFDekQsTUFBTSxLQUFLLEdBQUcsbUJBQW1CLEVBQUUsQ0FBQztJQUNwQyxNQUFNLFFBQVEsR0FBRyxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQztJQUN6QyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNaLElBQUksRUFBRSxHQUFHLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBQzFCLE9BQU8sRUFBRSxHQUFHLENBQUMsR0FBRyxFQUFFLEVBQUU7UUFDbkIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUN0QyxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUM7UUFDMUQsSUFBSSxNQUFNLENBQUMsR0FBRyxHQUFHLE1BQU0sQ0FBQyxNQUFNLElBQUksUUFBUSxFQUFFO1lBQzNDLEVBQUUsR0FBRyxHQUFHLENBQUM7U0FDVDthQUNJO1lBQ0osRUFBRSxHQUFHLEdBQUcsQ0FBQztTQUNUO0tBQ0Q7SUFDRCxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDNUIsTUFBTSxRQUFRLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO0lBQzNELElBQUksRUFBRSxJQUFJLENBQUMsSUFBSSxRQUFRLENBQUMsR0FBRyxHQUFHLFFBQVEsRUFBRTtRQUN2QyxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDNUIsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFDO0tBQ2hEO0lBQ0QsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsQ0FBQztBQUNoQyxDQUFDO0FBdEJELGtFQXNCQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsSUFBWTtJQUNwRCxJQUFJLENBQUMsc0JBQVcsRUFBRSxDQUFDLHVCQUF1QixFQUFFO1FBQzNDLE9BQU87S0FDUDtJQUVELElBQUksSUFBSSxJQUFJLENBQUMsRUFBRTtRQUNkLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQztRQUNqQyxPQUFPO0tBQ1A7SUFFRCxNQUFNLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxHQUFHLHdCQUF3QixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFELElBQUksQ0FBQyxRQUFRLEVBQUU7UUFDZCxPQUFPO0tBQ1A7SUFDRCxJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUM7SUFDakIsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO0lBQ3RELE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUM7SUFDN0IsSUFBSSxJQUFJLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxRQUFRLENBQUMsSUFBSSxFQUFFO1FBQ3hDLDhEQUE4RDtRQUM5RCxNQUFNLGVBQWUsR0FBRyxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM3RSxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUMsR0FBRyxHQUFHLFdBQVcsQ0FBQztRQUM3RSxRQUFRLEdBQUcsV0FBVyxHQUFHLGVBQWUsR0FBRyxhQUFhLENBQUM7S0FDekQ7U0FBTTtRQUNOLE1BQU0saUJBQWlCLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDbEQsUUFBUSxHQUFHLFdBQVcsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsaUJBQWlCLENBQUMsQ0FBQztLQUMzRDtJQUNELE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsT0FBTyxHQUFHLFFBQVEsQ0FBQyxDQUFDLENBQUM7QUFDdkUsQ0FBQztBQTNCRCw0REEyQkM7QUFFRCxTQUFnQixnQ0FBZ0MsQ0FBQyxNQUFjO0lBQzlELE1BQU0sRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLEdBQUcsMkJBQTJCLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDL0QsSUFBSSxRQUFRLEVBQUU7UUFDYixNQUFNLGNBQWMsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUM7UUFDaEUsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUMsT0FBTyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMxRSxJQUFJLElBQUksRUFBRTtZQUNULE1BQU0sdUJBQXVCLEdBQUcsa0JBQWtCLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUMsR0FBRyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNySCxNQUFNLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxHQUFHLHVCQUF1QixHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDbkYsT0FBTyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDdkI7YUFDSTtZQUNKLE1BQU0scUJBQXFCLEdBQUcsa0JBQWtCLEdBQUcsQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDM0UsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksR0FBRyxxQkFBcUIsQ0FBQztZQUNuRCxPQUFPLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN2QjtLQUNEO0lBQ0QsT0FBTyxJQUFJLENBQUM7QUFDYixDQUFDO0FBakJELDRFQWlCQzs7Ozs7Ozs7Ozs7Ozs7QUN2SUQ7OztnR0FHZ0c7O0FBYWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsU0FBZ0IsT0FBTyxDQUFTLEdBQVc7SUFDMUMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLElBQUksT0FBTyxFQUFFO1FBQ1osTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QyxJQUFJLElBQUksRUFBRTtZQUNULE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN4QjtLQUNEO0lBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsR0FBRyxFQUFFLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBVkQsMEJBVUM7QUFFRCxTQUFnQixXQUFXO0lBQzFCLElBQUksY0FBYyxFQUFFO1FBQ25CLE9BQU8sY0FBYyxDQUFDO0tBQ3RCO0lBRUQsY0FBYyxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMxQyxJQUFJLGNBQWMsRUFBRTtRQUNuQixPQUFPLGNBQWMsQ0FBQztLQUN0QjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBWEQsa0NBV0MiLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IFwiLi9wcmV2aWV3LXNyYy9pbmRleC50c1wiKTtcbiIsIi8qKlxuICogbG9kYXNoIChDdXN0b20gQnVpbGQpIDxodHRwczovL2xvZGFzaC5jb20vPlxuICogQnVpbGQ6IGBsb2Rhc2ggbW9kdWxhcml6ZSBleHBvcnRzPVwibnBtXCIgLW8gLi9gXG4gKiBDb3B5cmlnaHQgalF1ZXJ5IEZvdW5kYXRpb24gYW5kIG90aGVyIGNvbnRyaWJ1dG9ycyA8aHR0cHM6Ly9qcXVlcnkub3JnLz5cbiAqIFJlbGVhc2VkIHVuZGVyIE1JVCBsaWNlbnNlIDxodHRwczovL2xvZGFzaC5jb20vbGljZW5zZT5cbiAqIEJhc2VkIG9uIFVuZGVyc2NvcmUuanMgMS44LjMgPGh0dHA6Ly91bmRlcnNjb3JlanMub3JnL0xJQ0VOU0U+XG4gKiBDb3B5cmlnaHQgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIGFuZCBJbnZlc3RpZ2F0aXZlIFJlcG9ydGVycyAmIEVkaXRvcnNcbiAqL1xuXG4vKiogVXNlZCBhcyB0aGUgYFR5cGVFcnJvcmAgbWVzc2FnZSBmb3IgXCJGdW5jdGlvbnNcIiBtZXRob2RzLiAqL1xudmFyIEZVTkNfRVJST1JfVEVYVCA9ICdFeHBlY3RlZCBhIGZ1bmN0aW9uJztcblxuLyoqIFVzZWQgYXMgcmVmZXJlbmNlcyBmb3IgdmFyaW91cyBgTnVtYmVyYCBjb25zdGFudHMuICovXG52YXIgTkFOID0gMCAvIDA7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBzeW1ib2xUYWcgPSAnW29iamVjdCBTeW1ib2xdJztcblxuLyoqIFVzZWQgdG8gbWF0Y2ggbGVhZGluZyBhbmQgdHJhaWxpbmcgd2hpdGVzcGFjZS4gKi9cbnZhciByZVRyaW0gPSAvXlxccyt8XFxzKyQvZztcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJhZCBzaWduZWQgaGV4YWRlY2ltYWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmFkSGV4ID0gL15bLStdMHhbMC05YS1mXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBiaW5hcnkgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmluYXJ5ID0gL14wYlswMV0rJC9pO1xuXG4vKiogVXNlZCB0byBkZXRlY3Qgb2N0YWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzT2N0YWwgPSAvXjBvWzAtN10rJC9pO1xuXG4vKiogQnVpbHQtaW4gbWV0aG9kIHJlZmVyZW5jZXMgd2l0aG91dCBhIGRlcGVuZGVuY3kgb24gYHJvb3RgLiAqL1xudmFyIGZyZWVQYXJzZUludCA9IHBhcnNlSW50O1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYGdsb2JhbGAgZnJvbSBOb2RlLmpzLiAqL1xudmFyIGZyZWVHbG9iYWwgPSB0eXBlb2YgZ2xvYmFsID09ICdvYmplY3QnICYmIGdsb2JhbCAmJiBnbG9iYWwuT2JqZWN0ID09PSBPYmplY3QgJiYgZ2xvYmFsO1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYHNlbGZgLiAqL1xudmFyIGZyZWVTZWxmID0gdHlwZW9mIHNlbGYgPT0gJ29iamVjdCcgJiYgc2VsZiAmJiBzZWxmLk9iamVjdCA9PT0gT2JqZWN0ICYmIHNlbGY7XG5cbi8qKiBVc2VkIGFzIGEgcmVmZXJlbmNlIHRvIHRoZSBnbG9iYWwgb2JqZWN0LiAqL1xudmFyIHJvb3QgPSBmcmVlR2xvYmFsIHx8IGZyZWVTZWxmIHx8IEZ1bmN0aW9uKCdyZXR1cm4gdGhpcycpKCk7XG5cbi8qKiBVc2VkIGZvciBidWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKlxuICogVXNlZCB0byByZXNvbHZlIHRoZVxuICogW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmplY3RUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXgsXG4gICAgbmF0aXZlTWluID0gTWF0aC5taW47XG5cbi8qKlxuICogR2V0cyB0aGUgdGltZXN0YW1wIG9mIHRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRoYXQgaGF2ZSBlbGFwc2VkIHNpbmNlXG4gKiB0aGUgVW5peCBlcG9jaCAoMSBKYW51YXJ5IDE5NzAgMDA6MDA6MDAgVVRDKS5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDIuNC4wXG4gKiBAY2F0ZWdvcnkgRGF0ZVxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgdGltZXN0YW1wLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmRlZmVyKGZ1bmN0aW9uKHN0YW1wKSB7XG4gKiAgIGNvbnNvbGUubG9nKF8ubm93KCkgLSBzdGFtcCk7XG4gKiB9LCBfLm5vdygpKTtcbiAqIC8vID0+IExvZ3MgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgaXQgdG9vayBmb3IgdGhlIGRlZmVycmVkIGludm9jYXRpb24uXG4gKi9cbnZhciBub3cgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIHJvb3QuRGF0ZS5ub3coKTtcbn07XG5cbi8qKlxuICogQ3JlYXRlcyBhIGRlYm91bmNlZCBmdW5jdGlvbiB0aGF0IGRlbGF5cyBpbnZva2luZyBgZnVuY2AgdW50aWwgYWZ0ZXIgYHdhaXRgXG4gKiBtaWxsaXNlY29uZHMgaGF2ZSBlbGFwc2VkIHNpbmNlIHRoZSBsYXN0IHRpbWUgdGhlIGRlYm91bmNlZCBmdW5jdGlvbiB3YXNcbiAqIGludm9rZWQuIFRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgIG1ldGhvZCB0byBjYW5jZWxcbiAqIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLlxuICogUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2Agc2hvdWxkIGJlIGludm9rZWQgb24gdGhlXG4gKiBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGAgdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkXG4gKiB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50XG4gKiBjYWxscyB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHJldHVybiB0aGUgcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYFxuICogaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIGRlYm91bmNlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy5kZWJvdW5jZWAgYW5kIGBfLnRocm90dGxlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGRlYm91bmNlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIGRlbGF5LlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9ZmFsc2VdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgbGVhZGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHBhcmFtIHtudW1iZXJ9IFtvcHRpb25zLm1heFdhaXRdXG4gKiAgVGhlIG1heGltdW0gdGltZSBgZnVuY2AgaXMgYWxsb3dlZCB0byBiZSBkZWxheWVkIGJlZm9yZSBpdCdzIGludm9rZWQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGRlYm91bmNlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgY29zdGx5IGNhbGN1bGF0aW9ucyB3aGlsZSB0aGUgd2luZG93IHNpemUgaXMgaW4gZmx1eC5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdyZXNpemUnLCBfLmRlYm91bmNlKGNhbGN1bGF0ZUxheW91dCwgMTUwKSk7XG4gKlxuICogLy8gSW52b2tlIGBzZW5kTWFpbGAgd2hlbiBjbGlja2VkLCBkZWJvdW5jaW5nIHN1YnNlcXVlbnQgY2FsbHMuXG4gKiBqUXVlcnkoZWxlbWVudCkub24oJ2NsaWNrJywgXy5kZWJvdW5jZShzZW5kTWFpbCwgMzAwLCB7XG4gKiAgICdsZWFkaW5nJzogdHJ1ZSxcbiAqICAgJ3RyYWlsaW5nJzogZmFsc2VcbiAqIH0pKTtcbiAqXG4gKiAvLyBFbnN1cmUgYGJhdGNoTG9nYCBpcyBpbnZva2VkIG9uY2UgYWZ0ZXIgMSBzZWNvbmQgb2YgZGVib3VuY2VkIGNhbGxzLlxuICogdmFyIGRlYm91bmNlZCA9IF8uZGVib3VuY2UoYmF0Y2hMb2csIDI1MCwgeyAnbWF4V2FpdCc6IDEwMDAgfSk7XG4gKiB2YXIgc291cmNlID0gbmV3IEV2ZW50U291cmNlKCcvc3RyZWFtJyk7XG4gKiBqUXVlcnkoc291cmNlKS5vbignbWVzc2FnZScsIGRlYm91bmNlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyBkZWJvdW5jZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIGRlYm91bmNlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiBkZWJvdW5jZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsYXN0QXJncyxcbiAgICAgIGxhc3RUaGlzLFxuICAgICAgbWF4V2FpdCxcbiAgICAgIHJlc3VsdCxcbiAgICAgIHRpbWVySWQsXG4gICAgICBsYXN0Q2FsbFRpbWUsXG4gICAgICBsYXN0SW52b2tlVGltZSA9IDAsXG4gICAgICBsZWFkaW5nID0gZmFsc2UsXG4gICAgICBtYXhpbmcgPSBmYWxzZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICB3YWl0ID0gdG9OdW1iZXIod2FpdCkgfHwgMDtcbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICEhb3B0aW9ucy5sZWFkaW5nO1xuICAgIG1heGluZyA9ICdtYXhXYWl0JyBpbiBvcHRpb25zO1xuICAgIG1heFdhaXQgPSBtYXhpbmcgPyBuYXRpdmVNYXgodG9OdW1iZXIob3B0aW9ucy5tYXhXYWl0KSB8fCAwLCB3YWl0KSA6IG1heFdhaXQ7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuXG4gIGZ1bmN0aW9uIGludm9rZUZ1bmModGltZSkge1xuICAgIHZhciBhcmdzID0gbGFzdEFyZ3MsXG4gICAgICAgIHRoaXNBcmcgPSBsYXN0VGhpcztcblxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIHJlc3VsdCA9IGZ1bmMuYXBwbHkodGhpc0FyZywgYXJncyk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGxlYWRpbmdFZGdlKHRpbWUpIHtcbiAgICAvLyBSZXNldCBhbnkgYG1heFdhaXRgIHRpbWVyLlxuICAgIGxhc3RJbnZva2VUaW1lID0gdGltZTtcbiAgICAvLyBTdGFydCB0aGUgdGltZXIgZm9yIHRoZSB0cmFpbGluZyBlZGdlLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgLy8gSW52b2tlIHRoZSBsZWFkaW5nIGVkZ2UuXG4gICAgcmV0dXJuIGxlYWRpbmcgPyBpbnZva2VGdW5jKHRpbWUpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gcmVtYWluaW5nV2FpdCh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZSxcbiAgICAgICAgcmVzdWx0ID0gd2FpdCAtIHRpbWVTaW5jZUxhc3RDYWxsO1xuXG4gICAgcmV0dXJuIG1heGluZyA/IG5hdGl2ZU1pbihyZXN1bHQsIG1heFdhaXQgLSB0aW1lU2luY2VMYXN0SW52b2tlKSA6IHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIHNob3VsZEludm9rZSh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZTtcblxuICAgIC8vIEVpdGhlciB0aGlzIGlzIHRoZSBmaXJzdCBjYWxsLCBhY3Rpdml0eSBoYXMgc3RvcHBlZCBhbmQgd2UncmUgYXQgdGhlXG4gICAgLy8gdHJhaWxpbmcgZWRnZSwgdGhlIHN5c3RlbSB0aW1lIGhhcyBnb25lIGJhY2t3YXJkcyBhbmQgd2UncmUgdHJlYXRpbmdcbiAgICAvLyBpdCBhcyB0aGUgdHJhaWxpbmcgZWRnZSwgb3Igd2UndmUgaGl0IHRoZSBgbWF4V2FpdGAgbGltaXQuXG4gICAgcmV0dXJuIChsYXN0Q2FsbFRpbWUgPT09IHVuZGVmaW5lZCB8fCAodGltZVNpbmNlTGFzdENhbGwgPj0gd2FpdCkgfHxcbiAgICAgICh0aW1lU2luY2VMYXN0Q2FsbCA8IDApIHx8IChtYXhpbmcgJiYgdGltZVNpbmNlTGFzdEludm9rZSA+PSBtYXhXYWl0KSk7XG4gIH1cblxuICBmdW5jdGlvbiB0aW1lckV4cGlyZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKTtcbiAgICBpZiAoc2hvdWxkSW52b2tlKHRpbWUpKSB7XG4gICAgICByZXR1cm4gdHJhaWxpbmdFZGdlKHRpbWUpO1xuICAgIH1cbiAgICAvLyBSZXN0YXJ0IHRoZSB0aW1lci5cbiAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHJlbWFpbmluZ1dhaXQodGltZSkpO1xuICB9XG5cbiAgZnVuY3Rpb24gdHJhaWxpbmdFZGdlKHRpbWUpIHtcbiAgICB0aW1lcklkID0gdW5kZWZpbmVkO1xuXG4gICAgLy8gT25seSBpbnZva2UgaWYgd2UgaGF2ZSBgbGFzdEFyZ3NgIHdoaWNoIG1lYW5zIGBmdW5jYCBoYXMgYmVlblxuICAgIC8vIGRlYm91bmNlZCBhdCBsZWFzdCBvbmNlLlxuICAgIGlmICh0cmFpbGluZyAmJiBsYXN0QXJncykge1xuICAgICAgcmV0dXJuIGludm9rZUZ1bmModGltZSk7XG4gICAgfVxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGNhbmNlbCgpIHtcbiAgICBpZiAodGltZXJJZCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBjbGVhclRpbWVvdXQodGltZXJJZCk7XG4gICAgfVxuICAgIGxhc3RJbnZva2VUaW1lID0gMDtcbiAgICBsYXN0QXJncyA9IGxhc3RDYWxsVGltZSA9IGxhc3RUaGlzID0gdGltZXJJZCA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGZsdXNoKCkge1xuICAgIHJldHVybiB0aW1lcklkID09PSB1bmRlZmluZWQgPyByZXN1bHQgOiB0cmFpbGluZ0VkZ2Uobm93KCkpO1xuICB9XG5cbiAgZnVuY3Rpb24gZGVib3VuY2VkKCkge1xuICAgIHZhciB0aW1lID0gbm93KCksXG4gICAgICAgIGlzSW52b2tpbmcgPSBzaG91bGRJbnZva2UodGltZSk7XG5cbiAgICBsYXN0QXJncyA9IGFyZ3VtZW50cztcbiAgICBsYXN0VGhpcyA9IHRoaXM7XG4gICAgbGFzdENhbGxUaW1lID0gdGltZTtcblxuICAgIGlmIChpc0ludm9raW5nKSB7XG4gICAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBsZWFkaW5nRWRnZShsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgICAgaWYgKG1heGluZykge1xuICAgICAgICAvLyBIYW5kbGUgaW52b2NhdGlvbnMgaW4gYSB0aWdodCBsb29wLlxuICAgICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgICAgICByZXR1cm4gaW52b2tlRnVuYyhsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIGRlYm91bmNlZC5jYW5jZWwgPSBjYW5jZWw7XG4gIGRlYm91bmNlZC5mbHVzaCA9IGZsdXNoO1xuICByZXR1cm4gZGVib3VuY2VkO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSB0aHJvdHRsZWQgZnVuY3Rpb24gdGhhdCBvbmx5IGludm9rZXMgYGZ1bmNgIGF0IG1vc3Qgb25jZSBwZXJcbiAqIGV2ZXJ5IGB3YWl0YCBtaWxsaXNlY29uZHMuIFRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgXG4gKiBtZXRob2QgdG8gY2FuY2VsIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvXG4gKiBpbW1lZGlhdGVseSBpbnZva2UgdGhlbS4gUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2BcbiAqIHNob3VsZCBiZSBpbnZva2VkIG9uIHRoZSBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGBcbiAqIHRpbWVvdXQuIFRoZSBgZnVuY2AgaXMgaW52b2tlZCB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGVcbiAqIHRocm90dGxlZCBmdW5jdGlvbi4gU3Vic2VxdWVudCBjYWxscyB0byB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uIHJldHVybiB0aGVcbiAqIHJlc3VsdCBvZiB0aGUgbGFzdCBgZnVuY2AgaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIHRocm90dGxlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy50aHJvdHRsZWAgYW5kIGBfLmRlYm91bmNlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIHRocm90dGxlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIHRocm90dGxlIGludm9jYXRpb25zIHRvLlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IHRocm90dGxlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgZXhjZXNzaXZlbHkgdXBkYXRpbmcgdGhlIHBvc2l0aW9uIHdoaWxlIHNjcm9sbGluZy5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdzY3JvbGwnLCBfLnRocm90dGxlKHVwZGF0ZVBvc2l0aW9uLCAxMDApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHJlbmV3VG9rZW5gIHdoZW4gdGhlIGNsaWNrIGV2ZW50IGlzIGZpcmVkLCBidXQgbm90IG1vcmUgdGhhbiBvbmNlIGV2ZXJ5IDUgbWludXRlcy5cbiAqIHZhciB0aHJvdHRsZWQgPSBfLnRocm90dGxlKHJlbmV3VG9rZW4sIDMwMDAwMCwgeyAndHJhaWxpbmcnOiBmYWxzZSB9KTtcbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCB0aHJvdHRsZWQpO1xuICpcbiAqIC8vIENhbmNlbCB0aGUgdHJhaWxpbmcgdGhyb3R0bGVkIGludm9jYXRpb24uXG4gKiBqUXVlcnkod2luZG93KS5vbigncG9wc3RhdGUnLCB0aHJvdHRsZWQuY2FuY2VsKTtcbiAqL1xuZnVuY3Rpb24gdGhyb3R0bGUoZnVuYywgd2FpdCwgb3B0aW9ucykge1xuICB2YXIgbGVhZGluZyA9IHRydWUsXG4gICAgICB0cmFpbGluZyA9IHRydWU7XG5cbiAgaWYgKHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKEZVTkNfRVJST1JfVEVYVCk7XG4gIH1cbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICdsZWFkaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLmxlYWRpbmcgOiBsZWFkaW5nO1xuICAgIHRyYWlsaW5nID0gJ3RyYWlsaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLnRyYWlsaW5nIDogdHJhaWxpbmc7XG4gIH1cbiAgcmV0dXJuIGRlYm91bmNlKGZ1bmMsIHdhaXQsIHtcbiAgICAnbGVhZGluZyc6IGxlYWRpbmcsXG4gICAgJ21heFdhaXQnOiB3YWl0LFxuICAgICd0cmFpbGluZyc6IHRyYWlsaW5nXG4gIH0pO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIHRoZVxuICogW2xhbmd1YWdlIHR5cGVdKGh0dHA6Ly93d3cuZWNtYS1pbnRlcm5hdGlvbmFsLm9yZy9lY21hLTI2Mi83LjAvI3NlYy1lY21hc2NyaXB0LWxhbmd1YWdlLXR5cGVzKVxuICogb2YgYE9iamVjdGAuIChlLmcuIGFycmF5cywgZnVuY3Rpb25zLCBvYmplY3RzLCByZWdleGVzLCBgbmV3IE51bWJlcigwKWAsIGFuZCBgbmV3IFN0cmluZygnJylgKVxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGFuIG9iamVjdCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0KHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KFsxLCAyLCAzXSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChfLm5vb3ApO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QobnVsbCk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc09iamVjdCh2YWx1ZSkge1xuICB2YXIgdHlwZSA9IHR5cGVvZiB2YWx1ZTtcbiAgcmV0dXJuICEhdmFsdWUgJiYgKHR5cGUgPT0gJ29iamVjdCcgfHwgdHlwZSA9PSAnZnVuY3Rpb24nKTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZS4gQSB2YWx1ZSBpcyBvYmplY3QtbGlrZSBpZiBpdCdzIG5vdCBgbnVsbGBcbiAqIGFuZCBoYXMgYSBgdHlwZW9mYCByZXN1bHQgb2YgXCJvYmplY3RcIi5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZSwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZSh7fSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdExpa2UoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShfLm5vb3ApO1xuICogLy8gPT4gZmFsc2VcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0TGlrZSh2YWx1ZSkge1xuICByZXR1cm4gISF2YWx1ZSAmJiB0eXBlb2YgdmFsdWUgPT0gJ29iamVjdCc7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgY2xhc3NpZmllZCBhcyBhIGBTeW1ib2xgIHByaW1pdGl2ZSBvciBvYmplY3QuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSA0LjAuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYSBzeW1ib2wsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc1N5bWJvbChTeW1ib2wuaXRlcmF0b3IpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNTeW1ib2woJ2FiYycpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNTeW1ib2wodmFsdWUpIHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PSAnc3ltYm9sJyB8fFxuICAgIChpc09iamVjdExpa2UodmFsdWUpICYmIG9iamVjdFRvU3RyaW5nLmNhbGwodmFsdWUpID09IHN5bWJvbFRhZyk7XG59XG5cbi8qKlxuICogQ29udmVydHMgYHZhbHVlYCB0byBhIG51bWJlci5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gcHJvY2Vzcy5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIG51bWJlci5cbiAqIEBleGFtcGxlXG4gKlxuICogXy50b051bWJlcigzLjIpO1xuICogLy8gPT4gMy4yXG4gKlxuICogXy50b051bWJlcihOdW1iZXIuTUlOX1ZBTFVFKTtcbiAqIC8vID0+IDVlLTMyNFxuICpcbiAqIF8udG9OdW1iZXIoSW5maW5pdHkpO1xuICogLy8gPT4gSW5maW5pdHlcbiAqXG4gKiBfLnRvTnVtYmVyKCczLjInKTtcbiAqIC8vID0+IDMuMlxuICovXG5mdW5jdGlvbiB0b051bWJlcih2YWx1ZSkge1xuICBpZiAodHlwZW9mIHZhbHVlID09ICdudW1iZXInKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIGlmIChpc1N5bWJvbCh2YWx1ZSkpIHtcbiAgICByZXR1cm4gTkFOO1xuICB9XG4gIGlmIChpc09iamVjdCh2YWx1ZSkpIHtcbiAgICB2YXIgb3RoZXIgPSB0eXBlb2YgdmFsdWUudmFsdWVPZiA9PSAnZnVuY3Rpb24nID8gdmFsdWUudmFsdWVPZigpIDogdmFsdWU7XG4gICAgdmFsdWUgPSBpc09iamVjdChvdGhlcikgPyAob3RoZXIgKyAnJykgOiBvdGhlcjtcbiAgfVxuICBpZiAodHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHZhbHVlID09PSAwID8gdmFsdWUgOiArdmFsdWU7XG4gIH1cbiAgdmFsdWUgPSB2YWx1ZS5yZXBsYWNlKHJlVHJpbSwgJycpO1xuICB2YXIgaXNCaW5hcnkgPSByZUlzQmluYXJ5LnRlc3QodmFsdWUpO1xuICByZXR1cm4gKGlzQmluYXJ5IHx8IHJlSXNPY3RhbC50ZXN0KHZhbHVlKSlcbiAgICA/IGZyZWVQYXJzZUludCh2YWx1ZS5zbGljZSgyKSwgaXNCaW5hcnkgPyAyIDogOClcbiAgICA6IChyZUlzQmFkSGV4LnRlc3QodmFsdWUpID8gTkFOIDogK3ZhbHVlKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSB0aHJvdHRsZTtcbiIsInZhciBnO1xyXG5cclxuLy8gVGhpcyB3b3JrcyBpbiBub24tc3RyaWN0IG1vZGVcclxuZyA9IChmdW5jdGlvbigpIHtcclxuXHRyZXR1cm4gdGhpcztcclxufSkoKTtcclxuXHJcbnRyeSB7XHJcblx0Ly8gVGhpcyB3b3JrcyBpZiBldmFsIGlzIGFsbG93ZWQgKHNlZSBDU1ApXHJcblx0ZyA9IGcgfHwgRnVuY3Rpb24oXCJyZXR1cm4gdGhpc1wiKSgpIHx8ICgxLCBldmFsKShcInRoaXNcIik7XHJcbn0gY2F0Y2ggKGUpIHtcclxuXHQvLyBUaGlzIHdvcmtzIGlmIHRoZSB3aW5kb3cgcmVmZXJlbmNlIGlzIGF2YWlsYWJsZVxyXG5cdGlmICh0eXBlb2Ygd2luZG93ID09PSBcIm9iamVjdFwiKSBnID0gd2luZG93O1xyXG59XHJcblxyXG4vLyBnIGNhbiBzdGlsbCBiZSB1bmRlZmluZWQsIGJ1dCBub3RoaW5nIHRvIGRvIGFib3V0IGl0Li4uXHJcbi8vIFdlIHJldHVybiB1bmRlZmluZWQsIGluc3RlYWQgb2Ygbm90aGluZyBoZXJlLCBzbyBpdCdzXHJcbi8vIGVhc2llciB0byBoYW5kbGUgdGhpcyBjYXNlLiBpZighZ2xvYmFsKSB7IC4uLn1cclxuXHJcbm1vZHVsZS5leHBvcnRzID0gZztcclxuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5pbXBvcnQgeyBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcblxuZXhwb3J0IGNsYXNzIEFjdGl2ZUxpbmVNYXJrZXIge1xuXHRwcml2YXRlIF9jdXJyZW50OiBhbnk7XG5cblx0b25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmU6IG51bWJlcikge1xuXHRcdGNvbnN0IHsgcHJldmlvdXMgfSA9IGdldEVsZW1lbnRzRm9yU291cmNlTGluZShsaW5lKTtcblx0XHR0aGlzLl91cGRhdGUocHJldmlvdXMgJiYgcHJldmlvdXMuZWxlbWVudCk7XG5cdH1cblxuXHRfdXBkYXRlKGJlZm9yZTogSFRNTEVsZW1lbnQgfCB1bmRlZmluZWQpIHtcblx0XHR0aGlzLl91bm1hcmtBY3RpdmVFbGVtZW50KHRoaXMuX2N1cnJlbnQpO1xuXHRcdHRoaXMuX21hcmtBY3RpdmVFbGVtZW50KGJlZm9yZSk7XG5cdFx0dGhpcy5fY3VycmVudCA9IGJlZm9yZTtcblx0fVxuXG5cdF91bm1hcmtBY3RpdmVFbGVtZW50KGVsZW1lbnQ6IEhUTUxFbGVtZW50IHwgdW5kZWZpbmVkKSB7XG5cdFx0aWYgKCFlbGVtZW50KSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdGVsZW1lbnQuY2xhc3NOYW1lID0gZWxlbWVudC5jbGFzc05hbWUucmVwbGFjZSgvXFxiY29kZS1hY3RpdmUtbGluZVxcYi9nLCAnJyk7XG5cdH1cblxuXHRfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudDogSFRNTEVsZW1lbnQgfCB1bmRlZmluZWQpIHtcblx0XHRpZiAoIWVsZW1lbnQpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0ZWxlbWVudC5jbGFzc05hbWUgKz0gJyBjb2RlLWFjdGl2ZS1saW5lJztcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGY6ICgpID0+IHZvaWQpIHtcblx0aWYgKGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdsb2FkaW5nJyB8fCBkb2N1bWVudC5yZWFkeVN0YXRlIGFzIHN0cmluZyA9PT0gJ3VuaW5pdGlhbGl6ZWQnKSB7XG5cdFx0ZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGYpO1xuXHR9IGVsc2Uge1xuXHRcdGYoKTtcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBBY3RpdmVMaW5lTWFya2VyIH0gZnJvbSAnLi9hY3RpdmVMaW5lTWFya2VyJztcbmltcG9ydCB7IG9uY2VEb2N1bWVudExvYWRlZCB9IGZyb20gJy4vZXZlbnRzJztcbmltcG9ydCB7IGNyZWF0ZVBvc3RlckZvclZzQ29kZSB9IGZyb20gJy4vbWVzc2FnaW5nJztcbmltcG9ydCB7IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0LCBzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcbmltcG9ydCB7IGdldFNldHRpbmdzLCBnZXREYXRhIH0gZnJvbSAnLi9zZXR0aW5ncyc7XG5pbXBvcnQgdGhyb3R0bGUgPSByZXF1aXJlKCdsb2Rhc2gudGhyb3R0bGUnKTtcblxuZGVjbGFyZSB2YXIgYWNxdWlyZVZzQ29kZUFwaTogYW55O1xuXG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IEFjdGl2ZUxpbmVNYXJrZXIoKTtcbmNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3MoKTtcblxuY29uc3QgdnNjb2RlID0gYWNxdWlyZVZzQ29kZUFwaSgpO1xuXG4vLyBTZXQgVlMgQ29kZSBzdGF0ZVxubGV0IHN0YXRlID0gZ2V0RGF0YTx7IGxpbmU6IG51bWJlciB9PignZGF0YS1zdGF0ZScpO1xudnNjb2RlLnNldFN0YXRlKHN0YXRlKTtcblxuY29uc3QgbWVzc2FnaW5nID0gY3JlYXRlUG9zdGVyRm9yVnNDb2RlKHZzY29kZSk7XG5cbndpbmRvdy5jc3BBbGVydGVyLnNldFBvc3RlcihtZXNzYWdpbmcpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3Iuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG5cbndpbmRvdy5vbmxvYWQgPSAoKSA9PiB7XG5cdHVwZGF0ZUltYWdlU2l6ZXMoKTtcbn07XG5cbm9uY2VEb2N1bWVudExvYWRlZCgoKSA9PiB7XG5cdGlmIChzZXR0aW5ncy5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuXHRcdHNldFRpbWVvdXQoKCkgPT4ge1xuXHRcdFx0Y29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcblx0XHRcdGlmICghaXNOYU4oaW5pdGlhbExpbmUpKSB7XG5cdFx0XHRcdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0XHRcdFx0c2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcblx0XHRcdH1cblx0XHR9LCAwKTtcblx0fVxufSk7XG5cbmNvbnN0IG9uVXBkYXRlVmlldyA9ICgoKSA9PiB7XG5cdGNvbnN0IGRvU2Nyb2xsID0gdGhyb3R0bGUoKGxpbmU6IG51bWJlcikgPT4ge1xuXHRcdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0XHRzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSk7XG5cdH0sIDUwKTtcblxuXHRyZXR1cm4gKGxpbmU6IG51bWJlciwgc2V0dGluZ3M6IGFueSkgPT4ge1xuXHRcdGlmICghaXNOYU4obGluZSkpIHtcblx0XHRcdHNldHRpbmdzLmxpbmUgPSBsaW5lO1xuXHRcdFx0ZG9TY3JvbGwobGluZSk7XG5cdFx0fVxuXHR9O1xufSkoKTtcblxubGV0IHVwZGF0ZUltYWdlU2l6ZXMgPSB0aHJvdHRsZSgoKSA9PiB7XG5cdGNvbnN0IGltYWdlSW5mbzogeyBpZDogc3RyaW5nLCBoZWlnaHQ6IG51bWJlciwgd2lkdGg6IG51bWJlciB9W10gPSBbXTtcblx0bGV0IGltYWdlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdpbWcnKTtcblx0aWYgKGltYWdlcykge1xuXHRcdGxldCBpO1xuXHRcdGZvciAoaSA9IDA7IGkgPCBpbWFnZXMubGVuZ3RoOyBpKyspIHtcblx0XHRcdGNvbnN0IGltZyA9IGltYWdlc1tpXTtcblxuXHRcdFx0aWYgKGltZy5jbGFzc0xpc3QuY29udGFpbnMoJ2xvYWRpbmcnKSkge1xuXHRcdFx0XHRpbWcuY2xhc3NMaXN0LnJlbW92ZSgnbG9hZGluZycpO1xuXHRcdFx0fVxuXG5cdFx0XHRpbWFnZUluZm8ucHVzaCh7XG5cdFx0XHRcdGlkOiBpbWcuaWQsXG5cdFx0XHRcdGhlaWdodDogaW1nLmhlaWdodCxcblx0XHRcdFx0d2lkdGg6IGltZy53aWR0aFxuXHRcdFx0fSk7XG5cdFx0fVxuXG5cdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdjYWNoZUltYWdlU2l6ZXMnLCBpbWFnZUluZm8pO1xuXHR9XG59LCA1MCk7XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdyZXNpemUnLCAoKSA9PiB7XG5cdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0dXBkYXRlSW1hZ2VTaXplcygpO1xufSwgdHJ1ZSk7XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgZXZlbnQgPT4ge1xuXHRpZiAoZXZlbnQuZGF0YS5zb3VyY2UgIT09IHNldHRpbmdzLnNvdXJjZSkge1xuXHRcdHJldHVybjtcblx0fVxuXG5cdHN3aXRjaCAoZXZlbnQuZGF0YS50eXBlKSB7XG5cdFx0Y2FzZSAnb25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uJzpcblx0XHRcdG1hcmtlci5vbkRpZENoYW5nZVRleHRFZGl0b3JTZWxlY3Rpb24oZXZlbnQuZGF0YS5saW5lKTtcblx0XHRcdGJyZWFrO1xuXG5cdFx0Y2FzZSAndXBkYXRlVmlldyc6XG5cdFx0XHRvblVwZGF0ZVZpZXcoZXZlbnQuZGF0YS5saW5lLCBzZXR0aW5ncyk7XG5cdFx0XHRicmVhaztcblx0fVxufSwgZmFsc2UpO1xuXG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdkYmxjbGljaycsIGV2ZW50ID0+IHtcblx0aWYgKCFzZXR0aW5ncy5kb3VibGVDbGlja1RvU3dpdGNoVG9FZGl0b3IpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHQvLyBJZ25vcmUgY2xpY2tzIG9uIGxpbmtzXG5cdGZvciAobGV0IG5vZGUgPSBldmVudC50YXJnZXQgYXMgSFRNTEVsZW1lbnQ7IG5vZGU7IG5vZGUgPSBub2RlLnBhcmVudE5vZGUgYXMgSFRNTEVsZW1lbnQpIHtcblx0XHRpZiAobm9kZS50YWdOYW1lID09PSAnQScpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdH1cblxuXHRjb25zdCBvZmZzZXQgPSBldmVudC5wYWdlWTtcblx0Y29uc3QgbGluZSA9IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCk7XG5cdGlmICh0eXBlb2YgbGluZSA9PT0gJ251bWJlcicgJiYgIWlzTmFOKGxpbmUpKSB7XG5cdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdkaWRDbGljaycsIHsgbGluZTogTWF0aC5mbG9vcihsaW5lKSB9KTtcblx0fVxufSk7XG5cbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgZXZlbnQgPT4ge1xuXHRpZiAoIWV2ZW50KSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0bGV0IG5vZGU6IGFueSA9IGV2ZW50LnRhcmdldDtcblx0d2hpbGUgKG5vZGUpIHtcblx0XHRpZiAobm9kZS50YWdOYW1lICYmIG5vZGUudGFnTmFtZSA9PT0gJ0EnICYmIG5vZGUuaHJlZikge1xuXHRcdFx0aWYgKG5vZGUuZ2V0QXR0cmlidXRlKCdocmVmJykuc3RhcnRzV2l0aCgnIycpKSB7XG5cdFx0XHRcdGJyZWFrO1xuXHRcdFx0fVxuXHRcdFx0aWYgKG5vZGUuaHJlZi5zdGFydHNXaXRoKCdmaWxlOi8vJykgfHwgbm9kZS5ocmVmLnN0YXJ0c1dpdGgoJ3ZzY29kZS1yZXNvdXJjZTonKSB8fCBub2RlLmhyZWYuc3RhcnRzV2l0aChzZXR0aW5ncy53ZWJ2aWV3UmVzb3VyY2VSb290KSkge1xuXHRcdFx0XHRjb25zdCBbcGF0aCwgZnJhZ21lbnRdID0gbm9kZS5ocmVmLnJlcGxhY2UoL14oZmlsZTpcXC9cXC98dnNjb2RlLXJlc291cmNlOikvaSwgJycpLnJlcGxhY2UobmV3IFJlZ0V4cChgXiR7ZXNjYXBlUmVnRXhwKHNldHRpbmdzLndlYnZpZXdSZXNvdXJjZVJvb3QpfWApKS5zcGxpdCgnIycpO1xuXHRcdFx0XHRtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NsaWNrTGluaycsIHsgcGF0aCwgZnJhZ21lbnQgfSk7XG5cdFx0XHRcdGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG5cdFx0XHRcdGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuXHRcdFx0XHRicmVhaztcblx0XHRcdH1cblx0XHRcdGJyZWFrO1xuXHRcdH1cblx0XHRub2RlID0gbm9kZS5wYXJlbnROb2RlO1xuXHR9XG59LCB0cnVlKTtcblxuaWYgKHNldHRpbmdzLnNjcm9sbEVkaXRvcldpdGhQcmV2aWV3KSB7XG5cdHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdzY3JvbGwnLCB0aHJvdHRsZSgoKSA9PiB7XG5cdFx0aWYgKHNjcm9sbERpc2FibGVkKSB7XG5cdFx0XHRzY3JvbGxEaXNhYmxlZCA9IGZhbHNlO1xuXHRcdH0gZWxzZSB7XG5cdFx0XHRjb25zdCBsaW5lID0gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQod2luZG93LnNjcm9sbFkpO1xuXHRcdFx0aWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcblx0XHRcdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdyZXZlYWxMaW5lJywgeyBsaW5lIH0pO1xuXHRcdFx0XHRzdGF0ZS5saW5lID0gbGluZTtcblx0XHRcdFx0dnNjb2RlLnNldFN0YXRlKHN0YXRlKTtcblx0XHRcdH1cblx0XHR9XG5cdH0sIDUwKSk7XG59XG5cbmZ1bmN0aW9uIGVzY2FwZVJlZ0V4cCh0ZXh0OiBzdHJpbmcpIHtcblx0cmV0dXJuIHRleHQucmVwbGFjZSgvWy1bXFxde30oKSorPy4sXFxcXF4kfCNcXHNdL2csICdcXFxcJCYnKTtcbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgZ2V0U2V0dGluZ3MgfSBmcm9tICcuL3NldHRpbmdzJztcblxuZXhwb3J0IGludGVyZmFjZSBNZXNzYWdlUG9zdGVyIHtcblx0LyoqXG5cdCAqIFBvc3QgYSBtZXNzYWdlIHRvIHRoZSBtYXJrZG93biBleHRlbnNpb25cblx0ICovXG5cdHBvc3RNZXNzYWdlKHR5cGU6IHN0cmluZywgYm9keTogb2JqZWN0KTogdm9pZDtcbn1cblxuZXhwb3J0IGNvbnN0IGNyZWF0ZVBvc3RlckZvclZzQ29kZSA9ICh2c2NvZGU6IGFueSkgPT4ge1xuXHRyZXR1cm4gbmV3IGNsYXNzIGltcGxlbWVudHMgTWVzc2FnZVBvc3RlciB7XG5cdFx0cG9zdE1lc3NhZ2UodHlwZTogc3RyaW5nLCBib2R5OiBvYmplY3QpOiB2b2lkIHtcblx0XHRcdHZzY29kZS5wb3N0TWVzc2FnZSh7XG5cdFx0XHRcdHR5cGUsXG5cdFx0XHRcdHNvdXJjZTogZ2V0U2V0dGluZ3MoKS5zb3VyY2UsXG5cdFx0XHRcdGJvZHlcblx0XHRcdH0pO1xuXHRcdH1cblx0fTtcbn07XG5cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBnZXRTZXR0aW5ncyB9IGZyb20gJy4vc2V0dGluZ3MnO1xuXG5cbmZ1bmN0aW9uIGNsYW1wKG1pbjogbnVtYmVyLCBtYXg6IG51bWJlciwgdmFsdWU6IG51bWJlcikge1xuXHRyZXR1cm4gTWF0aC5taW4obWF4LCBNYXRoLm1heChtaW4sIHZhbHVlKSk7XG59XG5cbmZ1bmN0aW9uIGNsYW1wTGluZShsaW5lOiBudW1iZXIpIHtcblx0cmV0dXJuIGNsYW1wKDAsIGdldFNldHRpbmdzKCkubGluZUNvdW50IC0gMSwgbGluZSk7XG59XG5cblxuZXhwb3J0IGludGVyZmFjZSBDb2RlTGluZUVsZW1lbnQge1xuXHRlbGVtZW50OiBIVE1MRWxlbWVudDtcblx0bGluZTogbnVtYmVyO1xufVxuXG5jb25zdCBnZXRDb2RlTGluZUVsZW1lbnRzID0gKCgpID0+IHtcblx0bGV0IGVsZW1lbnRzOiBDb2RlTGluZUVsZW1lbnRbXTtcblx0cmV0dXJuICgpID0+IHtcblx0XHRpZiAoIWVsZW1lbnRzKSB7XG5cdFx0XHRlbGVtZW50cyA9IFt7IGVsZW1lbnQ6IGRvY3VtZW50LmJvZHksIGxpbmU6IDAgfV07XG5cdFx0XHRmb3IgKGNvbnN0IGVsZW1lbnQgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS1saW5lJykpIHtcblx0XHRcdFx0Y29uc3QgbGluZSA9ICtlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS1saW5lJykhO1xuXHRcdFx0XHRpZiAoIWlzTmFOKGxpbmUpKSB7XG5cdFx0XHRcdFx0ZWxlbWVudHMucHVzaCh7IGVsZW1lbnQ6IGVsZW1lbnQgYXMgSFRNTEVsZW1lbnQsIGxpbmUgfSk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cdFx0cmV0dXJuIGVsZW1lbnRzO1xuXHR9O1xufSkoKTtcblxuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgbWFwIHRvIGEgc3BlY2lmaWMgdGFyZ2V0IGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqXG4gKiBJZiBhbiBleGFjdCBtYXRjaCwgcmV0dXJucyBhIHNpbmdsZSBlbGVtZW50LiBJZiB0aGUgbGluZSBpcyBiZXR3ZWVuIGVsZW1lbnRzLFxuICogcmV0dXJucyB0aGUgZWxlbWVudCBwcmlvciB0byBhbmQgdGhlIGVsZW1lbnQgYWZ0ZXIgdGhlIGdpdmVuIGxpbmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUodGFyZ2V0TGluZTogbnVtYmVyKTogeyBwcmV2aW91czogQ29kZUxpbmVFbGVtZW50OyBuZXh0PzogQ29kZUxpbmVFbGVtZW50OyB9IHtcblx0Y29uc3QgbGluZU51bWJlciA9IE1hdGguZmxvb3IodGFyZ2V0TGluZSk7XG5cdGNvbnN0IGxpbmVzID0gZ2V0Q29kZUxpbmVFbGVtZW50cygpO1xuXHRsZXQgcHJldmlvdXMgPSBsaW5lc1swXSB8fCBudWxsO1xuXHRmb3IgKGNvbnN0IGVudHJ5IG9mIGxpbmVzKSB7XG5cdFx0aWYgKGVudHJ5LmxpbmUgPT09IGxpbmVOdW1iZXIpIHtcblx0XHRcdHJldHVybiB7IHByZXZpb3VzOiBlbnRyeSwgbmV4dDogdW5kZWZpbmVkIH07XG5cdFx0fSBlbHNlIGlmIChlbnRyeS5saW5lID4gbGluZU51bWJlcikge1xuXHRcdFx0cmV0dXJuIHsgcHJldmlvdXMsIG5leHQ6IGVudHJ5IH07XG5cdFx0fVxuXHRcdHByZXZpb3VzID0gZW50cnk7XG5cdH1cblx0cmV0dXJuIHsgcHJldmlvdXMgfTtcbn1cblxuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgYXJlIGF0IGEgc3BlY2lmaWMgcGl4ZWwgb2Zmc2V0IG9uIHRoZSBwYWdlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldDogbnVtYmVyKTogeyBwcmV2aW91czogQ29kZUxpbmVFbGVtZW50OyBuZXh0PzogQ29kZUxpbmVFbGVtZW50OyB9IHtcblx0Y29uc3QgbGluZXMgPSBnZXRDb2RlTGluZUVsZW1lbnRzKCk7XG5cdGNvbnN0IHBvc2l0aW9uID0gb2Zmc2V0IC0gd2luZG93LnNjcm9sbFk7XG5cdGxldCBsbyA9IC0xO1xuXHRsZXQgaGkgPSBsaW5lcy5sZW5ndGggLSAxO1xuXHR3aGlsZSAobG8gKyAxIDwgaGkpIHtcblx0XHRjb25zdCBtaWQgPSBNYXRoLmZsb29yKChsbyArIGhpKSAvIDIpO1xuXHRcdGNvbnN0IGJvdW5kcyA9IGxpbmVzW21pZF0uZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0XHRpZiAoYm91bmRzLnRvcCArIGJvdW5kcy5oZWlnaHQgPj0gcG9zaXRpb24pIHtcblx0XHRcdGhpID0gbWlkO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGxvID0gbWlkO1xuXHRcdH1cblx0fVxuXHRjb25zdCBoaUVsZW1lbnQgPSBsaW5lc1toaV07XG5cdGNvbnN0IGhpQm91bmRzID0gaGlFbGVtZW50LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG5cdGlmIChoaSA+PSAxICYmIGhpQm91bmRzLnRvcCA+IHBvc2l0aW9uKSB7XG5cdFx0Y29uc3QgbG9FbGVtZW50ID0gbGluZXNbbG9dO1xuXHRcdHJldHVybiB7IHByZXZpb3VzOiBsb0VsZW1lbnQsIG5leHQ6IGhpRWxlbWVudCB9O1xuXHR9XG5cdHJldHVybiB7IHByZXZpb3VzOiBoaUVsZW1lbnQgfTtcbn1cblxuLyoqXG4gKiBBdHRlbXB0IHRvIHJldmVhbCB0aGUgZWxlbWVudCBmb3IgYSBzb3VyY2UgbGluZSBpbiB0aGUgZWRpdG9yLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGxpbmU6IG51bWJlcikge1xuXHRpZiAoIWdldFNldHRpbmdzKCkuc2Nyb2xsUHJldmlld1dpdGhFZGl0b3IpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHRpZiAobGluZSA8PSAwKSB7XG5cdFx0d2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgMCk7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0Y29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lKGxpbmUpO1xuXHRpZiAoIXByZXZpb3VzKSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cdGxldCBzY3JvbGxUbyA9IDA7XG5cdGNvbnN0IHJlY3QgPSBwcmV2aW91cy5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuXHRjb25zdCBwcmV2aW91c1RvcCA9IHJlY3QudG9wO1xuXHRpZiAobmV4dCAmJiBuZXh0LmxpbmUgIT09IHByZXZpb3VzLmxpbmUpIHtcblx0XHQvLyBCZXR3ZWVuIHR3byBlbGVtZW50cy4gR28gdG8gcGVyY2VudGFnZSBvZmZzZXQgYmV0d2VlbiB0aGVtLlxuXHRcdGNvbnN0IGJldHdlZW5Qcm9ncmVzcyA9IChsaW5lIC0gcHJldmlvdXMubGluZSkgLyAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG5cdFx0Y29uc3QgZWxlbWVudE9mZnNldCA9IG5leHQuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AgLSBwcmV2aW91c1RvcDtcblx0XHRzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgYmV0d2VlblByb2dyZXNzICogZWxlbWVudE9mZnNldDtcblx0fSBlbHNlIHtcblx0XHRjb25zdCBwcm9ncmVzc0luRWxlbWVudCA9IGxpbmUgLSBNYXRoLmZsb29yKGxpbmUpO1xuXHRcdHNjcm9sbFRvID0gcHJldmlvdXNUb3AgKyAocmVjdC5oZWlnaHQgKiBwcm9ncmVzc0luRWxlbWVudCk7XG5cdH1cblx0d2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgTWF0aC5tYXgoMSwgd2luZG93LnNjcm9sbFkgKyBzY3JvbGxUbykpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQob2Zmc2V0OiBudW1iZXIpIHtcblx0Y29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldCk7XG5cdGlmIChwcmV2aW91cykge1xuXHRcdGNvbnN0IHByZXZpb3VzQm91bmRzID0gcHJldmlvdXMuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0XHRjb25zdCBvZmZzZXRGcm9tUHJldmlvdXMgPSAob2Zmc2V0IC0gd2luZG93LnNjcm9sbFkgLSBwcmV2aW91c0JvdW5kcy50b3ApO1xuXHRcdGlmIChuZXh0KSB7XG5cdFx0XHRjb25zdCBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyA9IG9mZnNldEZyb21QcmV2aW91cyAvIChuZXh0LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wIC0gcHJldmlvdXNCb3VuZHMudG9wKTtcblx0XHRcdGNvbnN0IGxpbmUgPSBwcmV2aW91cy5saW5lICsgcHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMgKiAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG5cdFx0XHRyZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGNvbnN0IHByb2dyZXNzV2l0aGluRWxlbWVudCA9IG9mZnNldEZyb21QcmV2aW91cyAvIChwcmV2aW91c0JvdW5kcy5oZWlnaHQpO1xuXHRcdFx0Y29uc3QgbGluZSA9IHByZXZpb3VzLmxpbmUgKyBwcm9ncmVzc1dpdGhpbkVsZW1lbnQ7XG5cdFx0XHRyZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuXHRcdH1cblx0fVxuXHRyZXR1cm4gbnVsbDtcbn1cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgaW50ZXJmYWNlIFByZXZpZXdTZXR0aW5ncyB7XG5cdHJlYWRvbmx5IHNvdXJjZTogc3RyaW5nO1xuXHRyZWFkb25seSBsaW5lOiBudW1iZXI7XG5cdHJlYWRvbmx5IGxpbmVDb3VudDogbnVtYmVyO1xuXHRyZWFkb25seSBzY3JvbGxQcmV2aWV3V2l0aEVkaXRvcj86IGJvb2xlYW47XG5cdHJlYWRvbmx5IHNjcm9sbEVkaXRvcldpdGhQcmV2aWV3OiBib29sZWFuO1xuXHRyZWFkb25seSBkaXNhYmxlU2VjdXJpdHlXYXJuaW5nczogYm9vbGVhbjtcblx0cmVhZG9ubHkgZG91YmxlQ2xpY2tUb1N3aXRjaFRvRWRpdG9yOiBib29sZWFuO1xuXHRyZWFkb25seSB3ZWJ2aWV3UmVzb3VyY2VSb290OiBzdHJpbmc7XG59XG5cbmxldCBjYWNoZWRTZXR0aW5nczogUHJldmlld1NldHRpbmdzIHwgdW5kZWZpbmVkID0gdW5kZWZpbmVkO1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGF0YTxUID0ge30+KGtleTogc3RyaW5nKTogVCB7XG5cdGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndnNjb2RlLW1hcmtkb3duLXByZXZpZXctZGF0YScpO1xuXHRpZiAoZWxlbWVudCkge1xuXHRcdGNvbnN0IGRhdGEgPSBlbGVtZW50LmdldEF0dHJpYnV0ZShrZXkpO1xuXHRcdGlmIChkYXRhKSB7XG5cdFx0XHRyZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcblx0XHR9XG5cdH1cblxuXHR0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U2V0dGluZ3MoKTogUHJldmlld1NldHRpbmdzIHtcblx0aWYgKGNhY2hlZFNldHRpbmdzKSB7XG5cdFx0cmV0dXJuIGNhY2hlZFNldHRpbmdzO1xuXHR9XG5cblx0Y2FjaGVkU2V0dGluZ3MgPSBnZXREYXRhKCdkYXRhLXNldHRpbmdzJyk7XG5cdGlmIChjYWNoZWRTZXR0aW5ncykge1xuXHRcdHJldHVybiBjYWNoZWRTZXR0aW5ncztcblx0fVxuXG5cdHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbiJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vbm9kZV9tb2R1bGVzL2xvZGFzaC50aHJvdHRsZS9pbmRleC5qcyIsIndlYnBhY2s6Ly8vKHdlYnBhY2spL2J1aWxkaW4vZ2xvYmFsLmpzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2FjdGl2ZUxpbmVNYXJrZXIudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvZXZlbnRzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2luZGV4LnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL21lc3NhZ2luZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zY3JvbGwtc3luYy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zZXR0aW5ncy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQSx5REFBaUQsY0FBYztBQUMvRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBMkIsMEJBQTBCLEVBQUU7QUFDdkQseUNBQWlDLGVBQWU7QUFDaEQ7QUFDQTtBQUNBOztBQUVBO0FBQ0EsOERBQXNELCtEQUErRDs7QUFFckg7QUFDQTs7O0FBR0E7QUFDQTs7Ozs7Ozs7Ozs7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsT0FBTztBQUNsQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBLDhDQUE4QyxrQkFBa0I7QUFDaEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtREFBbUQsb0JBQW9CO0FBQ3ZFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsRUFBRTtBQUNiLGFBQWEsUUFBUTtBQUNyQjtBQUNBO0FBQ0Esb0JBQW9CO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVyxFQUFFO0FBQ2IsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOzs7Ozs7Ozs7Ozs7O0FDdGJBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLENBQUM7O0FBRUQ7QUFDQTtBQUNBO0FBQ0EsQ0FBQztBQUNEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsNENBQTRDOztBQUU1Qzs7Ozs7Ozs7Ozs7Ozs7O0FDbkJBOzs7Z0dBR2dHO0FBQ2hHLCtGQUF5RDtBQUV6RCxNQUFhLGdCQUFnQjtJQUc1Qiw4QkFBOEIsQ0FBQyxJQUFZO1FBQzFDLE1BQU0sRUFBRSxRQUFRLEVBQUUsR0FBRyxzQ0FBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNwRCxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFBSSxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUVELE9BQU8sQ0FBQyxNQUErQjtRQUN0QyxJQUFJLENBQUMsb0JBQW9CLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3pDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNoQyxJQUFJLENBQUMsUUFBUSxHQUFHLE1BQU0sQ0FBQztJQUN4QixDQUFDO0lBRUQsb0JBQW9CLENBQUMsT0FBZ0M7UUFDcEQsSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNiLE9BQU87U0FDUDtRQUNELE9BQU8sQ0FBQyxTQUFTLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsdUJBQXVCLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDNUUsQ0FBQztJQUVELGtCQUFrQixDQUFDLE9BQWdDO1FBQ2xELElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDYixPQUFPO1NBQ1A7UUFDRCxPQUFPLENBQUMsU0FBUyxJQUFJLG1CQUFtQixDQUFDO0lBQzFDLENBQUM7Q0FDRDtBQTNCRCw0Q0EyQkM7Ozs7Ozs7Ozs7Ozs7O0FDakNEOzs7Z0dBR2dHOztBQUVoRyxTQUFnQixrQkFBa0IsQ0FBQyxDQUFhO0lBQy9DLElBQUksUUFBUSxDQUFDLFVBQVUsS0FBSyxTQUFTLElBQUksUUFBUSxDQUFDLFVBQW9CLEtBQUssZUFBZSxFQUFFO1FBQzNGLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDLENBQUMsQ0FBQztLQUNqRDtTQUFNO1FBQ04sQ0FBQyxFQUFFLENBQUM7S0FDSjtBQUNGLENBQUM7QUFORCxnREFNQzs7Ozs7Ozs7Ozs7Ozs7QUNYRDs7O2dHQUdnRzs7QUFFaEcsOEdBQXNEO0FBQ3RELGdGQUE4QztBQUM5Qyx5RkFBb0Q7QUFDcEQsK0ZBQXNIO0FBQ3RILHNGQUFrRDtBQUNsRCx1R0FBNkM7QUFJN0MsSUFBSSxjQUFjLEdBQUcsSUFBSSxDQUFDO0FBQzFCLE1BQU0sTUFBTSxHQUFHLElBQUksbUNBQWdCLEVBQUUsQ0FBQztBQUN0QyxNQUFNLFFBQVEsR0FBRyxzQkFBVyxFQUFFLENBQUM7QUFFL0IsTUFBTSxNQUFNLEdBQUcsZ0JBQWdCLEVBQUUsQ0FBQztBQUVsQyxvQkFBb0I7QUFDcEIsSUFBSSxLQUFLLEdBQUcsa0JBQU8sQ0FBc0MsWUFBWSxDQUFDLENBQUM7QUFDdkUsTUFBTSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUV2QixNQUFNLFNBQVMsR0FBRyxpQ0FBcUIsQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUVoRCxNQUFNLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUN2QyxNQUFNLENBQUMsbUJBQW1CLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBRWhELE1BQU0sQ0FBQyxNQUFNLEdBQUcsR0FBRyxFQUFFO0lBQ3BCLGdCQUFnQixFQUFFLENBQUM7QUFDcEIsQ0FBQyxDQUFDO0FBRUYsMkJBQWtCLENBQUMsR0FBRyxFQUFFO0lBQ3ZCLElBQUksUUFBUSxDQUFDLHVCQUF1QixFQUFFO1FBQ3JDLFVBQVUsQ0FBQyxHQUFHLEVBQUU7WUFDZix5Q0FBeUM7WUFDekMsSUFBSSxLQUFLLENBQUMsUUFBUSxFQUFFO2dCQUNuQixNQUFNLE9BQU8sR0FBRyx1Q0FBeUIsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7Z0JBQzFELElBQUksT0FBTyxFQUFFO29CQUNaLGNBQWMsR0FBRyxJQUFJLENBQUM7b0JBQ3RCLHNDQUF3QixDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztpQkFDdkM7YUFDRDtpQkFBTTtnQkFDTixNQUFNLFdBQVcsR0FBRyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7Z0JBQ25DLElBQUksQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLEVBQUU7b0JBQ3hCLGNBQWMsR0FBRyxJQUFJLENBQUM7b0JBQ3RCLHNDQUF3QixDQUFDLFdBQVcsQ0FBQyxDQUFDO2lCQUN0QzthQUNEO1FBQ0YsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0tBQ047QUFDRixDQUFDLENBQUMsQ0FBQztBQUVILE1BQU0sWUFBWSxHQUFHLENBQUMsR0FBRyxFQUFFO0lBQzFCLE1BQU0sUUFBUSxHQUFHLFFBQVEsQ0FBQyxDQUFDLElBQVksRUFBRSxFQUFFO1FBQzFDLGNBQWMsR0FBRyxJQUFJLENBQUM7UUFDdEIsc0NBQXdCLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDaEMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBRVAsT0FBTyxDQUFDLElBQVksRUFBRSxRQUFhLEVBQUUsRUFBRTtRQUN0QyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFO1lBQ2pCLFFBQVEsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO1lBQ3JCLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUNmO0lBQ0YsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUVMLElBQUksZ0JBQWdCLEdBQUcsUUFBUSxDQUFDLEdBQUcsRUFBRTtJQUNwQyxNQUFNLFNBQVMsR0FBb0QsRUFBRSxDQUFDO0lBQ3RFLElBQUksTUFBTSxHQUFHLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNsRCxJQUFJLE1BQU0sRUFBRTtRQUNYLElBQUksQ0FBQyxDQUFDO1FBQ04sS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFO1lBQ25DLE1BQU0sR0FBRyxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUV0QixJQUFJLEdBQUcsQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxFQUFFO2dCQUN0QyxHQUFHLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQzthQUNoQztZQUVELFNBQVMsQ0FBQyxJQUFJLENBQUM7Z0JBQ2QsRUFBRSxFQUFFLEdBQUcsQ0FBQyxFQUFFO2dCQUNWLE1BQU0sRUFBRSxHQUFHLENBQUMsTUFBTTtnQkFDbEIsS0FBSyxFQUFFLEdBQUcsQ0FBQyxLQUFLO2FBQ2hCLENBQUMsQ0FBQztTQUNIO1FBRUQsU0FBUyxDQUFDLFdBQVcsQ0FBQyxpQkFBaUIsRUFBRSxTQUFTLENBQUMsQ0FBQztLQUNwRDtBQUNGLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztBQUVQLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUUsR0FBRyxFQUFFO0lBQ3RDLGNBQWMsR0FBRyxJQUFJLENBQUM7SUFDdEIsZ0JBQWdCLEVBQUUsQ0FBQztBQUNwQixDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFFVCxNQUFNLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxFQUFFLEtBQUssQ0FBQyxFQUFFO0lBQzFDLElBQUksS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEtBQUssUUFBUSxDQUFDLE1BQU0sRUFBRTtRQUMxQyxPQUFPO0tBQ1A7SUFFRCxRQUFRLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFO1FBQ3hCLEtBQUssZ0NBQWdDO1lBQ3BDLE1BQU0sQ0FBQyw4QkFBOEIsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3ZELE1BQU07UUFFUCxLQUFLLFlBQVk7WUFDaEIsWUFBWSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1lBQ3hDLE1BQU07S0FDUDtBQUNGLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztBQUVWLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVLEVBQUUsS0FBSyxDQUFDLEVBQUU7SUFDN0MsSUFBSSxDQUFDLFFBQVEsQ0FBQywyQkFBMkIsRUFBRTtRQUMxQyxPQUFPO0tBQ1A7SUFFRCx5QkFBeUI7SUFDekIsS0FBSyxJQUFJLElBQUksR0FBRyxLQUFLLENBQUMsTUFBcUIsRUFBRSxJQUFJLEVBQUUsSUFBSSxHQUFHLElBQUksQ0FBQyxVQUF5QixFQUFFO1FBQ3pGLElBQUksSUFBSSxDQUFDLE9BQU8sS0FBSyxHQUFHLEVBQUU7WUFDekIsT0FBTztTQUNQO0tBQ0Q7SUFFRCxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDO0lBQzNCLE1BQU0sSUFBSSxHQUFHLDhDQUFnQyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ3RELElBQUksT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFO1FBQzdDLFNBQVMsQ0FBQyxXQUFXLENBQUMsVUFBVSxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0tBQzlEO0FBQ0YsQ0FBQyxDQUFDLENBQUM7QUFFSCxRQUFRLENBQUMsZ0JBQWdCLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxFQUFFO0lBQzFDLElBQUksQ0FBQyxLQUFLLEVBQUU7UUFDWCxPQUFPO0tBQ1A7SUFFRCxJQUFJLElBQUksR0FBUSxLQUFLLENBQUMsTUFBTSxDQUFDO0lBQzdCLE9BQU8sSUFBSSxFQUFFO1FBQ1osSUFBSSxJQUFJLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxPQUFPLEtBQUssR0FBRyxJQUFJLElBQUksQ0FBQyxJQUFJLEVBQUU7WUFDdEQsSUFBSSxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsRUFBRTtnQkFDOUMsTUFBTTthQUNOO1lBQ0QsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxtQkFBbUIsQ0FBQyxFQUFFO2dCQUN0SSxNQUFNLENBQUMsSUFBSSxFQUFFLFFBQVEsQ0FBQyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLGdDQUFnQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLE1BQU0sQ0FBQyxJQUFJLFlBQVksQ0FBQyxRQUFRLENBQUMsbUJBQW1CLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBQ2xLLFNBQVMsQ0FBQyxXQUFXLENBQUMsV0FBVyxFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7Z0JBQ3ZELEtBQUssQ0FBQyxjQUFjLEVBQUUsQ0FBQztnQkFDdkIsS0FBSyxDQUFDLGVBQWUsRUFBRSxDQUFDO2dCQUN4QixNQUFNO2FBQ047WUFDRCxNQUFNO1NBQ047UUFDRCxJQUFJLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQztLQUN2QjtBQUNGLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUVULElBQUksUUFBUSxDQUFDLHVCQUF1QixFQUFFO0lBQ3JDLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDLEdBQUcsRUFBRTtRQUMvQyxJQUFJLGNBQWMsRUFBRTtZQUNuQixjQUFjLEdBQUcsS0FBSyxDQUFDO1NBQ3ZCO2FBQU07WUFDTixNQUFNLElBQUksR0FBRyw4Q0FBZ0MsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDOUQsSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUU7Z0JBQzdDLFNBQVMsQ0FBQyxXQUFXLENBQUMsWUFBWSxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztnQkFDOUMsS0FBSyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7Z0JBQ2xCLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7YUFDdkI7U0FDRDtJQUNGLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0NBQ1I7QUFFRCxTQUFTLFlBQVksQ0FBQyxJQUFZO0lBQ2pDLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQywwQkFBMEIsRUFBRSxNQUFNLENBQUMsQ0FBQztBQUN6RCxDQUFDOzs7Ozs7Ozs7Ozs7OztBQzVLRDs7O2dHQUdnRzs7QUFFaEcsc0ZBQXlDO0FBUzVCLDZCQUFxQixHQUFHLENBQUMsTUFBVyxFQUFFLEVBQUU7SUFDcEQsT0FBTyxJQUFJO1FBQ1YsV0FBVyxDQUFDLElBQVksRUFBRSxJQUFZO1lBQ3JDLE1BQU0sQ0FBQyxXQUFXLENBQUM7Z0JBQ2xCLElBQUk7Z0JBQ0osTUFBTSxFQUFFLHNCQUFXLEVBQUUsQ0FBQyxNQUFNO2dCQUM1QixJQUFJO2FBQ0osQ0FBQyxDQUFDO1FBQ0osQ0FBQztLQUNELENBQUM7QUFDSCxDQUFDLENBQUM7Ozs7Ozs7Ozs7Ozs7O0FDeEJGOzs7Z0dBR2dHOztBQUVoRyxzRkFBeUM7QUFHekMsU0FBUyxLQUFLLENBQUMsR0FBVyxFQUFFLEdBQVcsRUFBRSxLQUFhO0lBQ3JELE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBRUQsU0FBUyxTQUFTLENBQUMsSUFBWTtJQUM5QixPQUFPLEtBQUssQ0FBQyxDQUFDLEVBQUUsc0JBQVcsRUFBRSxDQUFDLFNBQVMsR0FBRyxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDcEQsQ0FBQztBQVFELE1BQU0sbUJBQW1CLEdBQUcsQ0FBQyxHQUFHLEVBQUU7SUFDakMsSUFBSSxRQUEyQixDQUFDO0lBQ2hDLE9BQU8sR0FBRyxFQUFFO1FBQ1gsSUFBSSxDQUFDLFFBQVEsRUFBRTtZQUNkLFFBQVEsR0FBRyxDQUFDLEVBQUUsT0FBTyxFQUFFLFFBQVEsQ0FBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUM7WUFDakQsS0FBSyxNQUFNLE9BQU8sSUFBSSxRQUFRLENBQUMsc0JBQXNCLENBQUMsV0FBVyxDQUFDLEVBQUU7Z0JBQ25FLE1BQU0sSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQyxXQUFXLENBQUUsQ0FBQztnQkFDakQsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRTtvQkFDakIsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLE9BQU8sRUFBRSxPQUFzQixFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7aUJBQ3pEO2FBQ0Q7U0FDRDtRQUNELE9BQU8sUUFBUSxDQUFDO0lBQ2pCLENBQUMsQ0FBQztBQUNILENBQUMsQ0FBQyxFQUFFLENBQUM7QUFFTDs7Ozs7R0FLRztBQUNILFNBQWdCLHdCQUF3QixDQUFDLFVBQWtCO0lBQzFELE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDMUMsTUFBTSxLQUFLLEdBQUcsbUJBQW1CLEVBQUUsQ0FBQztJQUNwQyxJQUFJLFFBQVEsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDO0lBQ2hDLEtBQUssTUFBTSxLQUFLLElBQUksS0FBSyxFQUFFO1FBQzFCLElBQUksS0FBSyxDQUFDLElBQUksS0FBSyxVQUFVLEVBQUU7WUFDOUIsT0FBTyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFDO1NBQzVDO2FBQU0sSUFBSSxLQUFLLENBQUMsSUFBSSxHQUFHLFVBQVUsRUFBRTtZQUNuQyxPQUFPLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsQ0FBQztTQUNqQztRQUNELFFBQVEsR0FBRyxLQUFLLENBQUM7S0FDakI7SUFDRCxPQUFPLEVBQUUsUUFBUSxFQUFFLENBQUM7QUFDckIsQ0FBQztBQWJELDREQWFDO0FBRUQ7O0dBRUc7QUFDSCxTQUFnQiwyQkFBMkIsQ0FBQyxNQUFjO0lBQ3pELE1BQU0sS0FBSyxHQUFHLG1CQUFtQixFQUFFLENBQUM7SUFDcEMsTUFBTSxRQUFRLEdBQUcsTUFBTSxHQUFHLE1BQU0sQ0FBQyxPQUFPLENBQUM7SUFDekMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDWixJQUFJLEVBQUUsR0FBRyxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQztJQUMxQixPQUFPLEVBQUUsR0FBRyxDQUFDLEdBQUcsRUFBRSxFQUFFO1FBQ25CLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7UUFDdEMsTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1FBQzFELElBQUksTUFBTSxDQUFDLEdBQUcsR0FBRyxNQUFNLENBQUMsTUFBTSxJQUFJLFFBQVEsRUFBRTtZQUMzQyxFQUFFLEdBQUcsR0FBRyxDQUFDO1NBQ1Q7YUFDSTtZQUNKLEVBQUUsR0FBRyxHQUFHLENBQUM7U0FDVDtLQUNEO0lBQ0QsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzVCLE1BQU0sUUFBUSxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQUMscUJBQXFCLEVBQUUsQ0FBQztJQUMzRCxJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUksUUFBUSxDQUFDLEdBQUcsR0FBRyxRQUFRLEVBQUU7UUFDdkMsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQzVCLE9BQU8sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUUsQ0FBQztLQUNoRDtJQUNELE9BQU8sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLENBQUM7QUFDaEMsQ0FBQztBQXRCRCxrRUFzQkM7QUFFRDs7R0FFRztBQUNILFNBQWdCLHdCQUF3QixDQUFDLElBQVk7SUFDcEQsSUFBSSxDQUFDLHNCQUFXLEVBQUUsQ0FBQyx1QkFBdUIsRUFBRTtRQUMzQyxPQUFPO0tBQ1A7SUFFRCxJQUFJLElBQUksSUFBSSxDQUFDLEVBQUU7UUFDZCxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDakMsT0FBTztLQUNQO0lBRUQsTUFBTSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsR0FBRyx3QkFBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUMxRCxJQUFJLENBQUMsUUFBUSxFQUFFO1FBQ2QsT0FBTztLQUNQO0lBQ0QsSUFBSSxRQUFRLEdBQUcsQ0FBQyxDQUFDO0lBQ2pCLE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMscUJBQXFCLEVBQUUsQ0FBQztJQUN0RCxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDO0lBQzdCLElBQUksSUFBSSxJQUFJLElBQUksQ0FBQyxJQUFJLEtBQUssUUFBUSxDQUFDLElBQUksRUFBRTtRQUN4Qyw4REFBOEQ7UUFDOUQsTUFBTSxlQUFlLEdBQUcsQ0FBQyxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDN0UsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDLEdBQUcsR0FBRyxXQUFXLENBQUM7UUFDN0UsUUFBUSxHQUFHLFdBQVcsR0FBRyxlQUFlLEdBQUcsYUFBYSxDQUFDO0tBQ3pEO1NBQU07UUFDTixNQUFNLGlCQUFpQixHQUFHLElBQUksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ2xELFFBQVEsR0FBRyxXQUFXLEdBQUcsQ0FBQyxJQUFJLENBQUMsTUFBTSxHQUFHLGlCQUFpQixDQUFDLENBQUM7S0FDM0Q7SUFDRCxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsTUFBTSxDQUFDLE9BQU8sR0FBRyxRQUFRLENBQUMsQ0FBQyxDQUFDO0FBQ3ZFLENBQUM7QUEzQkQsNERBMkJDO0FBRUQsU0FBZ0IsZ0NBQWdDLENBQUMsTUFBYztJQUM5RCxNQUFNLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxHQUFHLDJCQUEyQixDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQy9ELElBQUksUUFBUSxFQUFFO1FBQ2IsTUFBTSxjQUFjLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1FBQ2hFLE1BQU0sa0JBQWtCLEdBQUcsQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sR0FBRyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDMUUsSUFBSSxJQUFJLEVBQUU7WUFDVCxNQUFNLHVCQUF1QixHQUFHLGtCQUFrQixHQUFHLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDLEdBQUcsR0FBRyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDckgsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksR0FBRyx1QkFBdUIsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ25GLE9BQU8sU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO1NBQ3ZCO2FBQ0k7WUFDSixNQUFNLHFCQUFxQixHQUFHLGtCQUFrQixHQUFHLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQzNFLE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxJQUFJLEdBQUcscUJBQXFCLENBQUM7WUFDbkQsT0FBTyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDdkI7S0FDRDtJQUNELE9BQU8sSUFBSSxDQUFDO0FBQ2IsQ0FBQztBQWpCRCw0RUFpQkM7QUFFRDs7R0FFRztBQUNILFNBQWdCLHlCQUF5QixDQUFDLFFBQWdCO0lBQ3pELE9BQU8sbUJBQW1CLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxPQUFPLEVBQUUsRUFBRTtRQUM3QyxPQUFPLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxLQUFLLFFBQVEsQ0FBQztJQUN4QyxDQUFDLENBQUMsQ0FBQztBQUNKLENBQUM7QUFKRCw4REFJQzs7Ozs7Ozs7Ozs7Ozs7QUNoSkQ7OztnR0FHZ0c7O0FBYWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsU0FBZ0IsT0FBTyxDQUFTLEdBQVc7SUFDMUMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLElBQUksT0FBTyxFQUFFO1FBQ1osTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QyxJQUFJLElBQUksRUFBRTtZQUNULE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN4QjtLQUNEO0lBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsR0FBRyxFQUFFLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBVkQsMEJBVUM7QUFFRCxTQUFnQixXQUFXO0lBQzFCLElBQUksY0FBYyxFQUFFO1FBQ25CLE9BQU8sY0FBYyxDQUFDO0tBQ3RCO0lBRUQsY0FBYyxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMxQyxJQUFJLGNBQWMsRUFBRTtRQUNuQixPQUFPLGNBQWMsQ0FBQztLQUN0QjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBWEQsa0NBV0MiLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IFwiLi9wcmV2aWV3LXNyYy9pbmRleC50c1wiKTtcbiIsIi8qKlxuICogbG9kYXNoIChDdXN0b20gQnVpbGQpIDxodHRwczovL2xvZGFzaC5jb20vPlxuICogQnVpbGQ6IGBsb2Rhc2ggbW9kdWxhcml6ZSBleHBvcnRzPVwibnBtXCIgLW8gLi9gXG4gKiBDb3B5cmlnaHQgalF1ZXJ5IEZvdW5kYXRpb24gYW5kIG90aGVyIGNvbnRyaWJ1dG9ycyA8aHR0cHM6Ly9qcXVlcnkub3JnLz5cbiAqIFJlbGVhc2VkIHVuZGVyIE1JVCBsaWNlbnNlIDxodHRwczovL2xvZGFzaC5jb20vbGljZW5zZT5cbiAqIEJhc2VkIG9uIFVuZGVyc2NvcmUuanMgMS44LjMgPGh0dHA6Ly91bmRlcnNjb3JlanMub3JnL0xJQ0VOU0U+XG4gKiBDb3B5cmlnaHQgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIGFuZCBJbnZlc3RpZ2F0aXZlIFJlcG9ydGVycyAmIEVkaXRvcnNcbiAqL1xuXG4vKiogVXNlZCBhcyB0aGUgYFR5cGVFcnJvcmAgbWVzc2FnZSBmb3IgXCJGdW5jdGlvbnNcIiBtZXRob2RzLiAqL1xudmFyIEZVTkNfRVJST1JfVEVYVCA9ICdFeHBlY3RlZCBhIGZ1bmN0aW9uJztcblxuLyoqIFVzZWQgYXMgcmVmZXJlbmNlcyBmb3IgdmFyaW91cyBgTnVtYmVyYCBjb25zdGFudHMuICovXG52YXIgTkFOID0gMCAvIDA7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBzeW1ib2xUYWcgPSAnW29iamVjdCBTeW1ib2xdJztcblxuLyoqIFVzZWQgdG8gbWF0Y2ggbGVhZGluZyBhbmQgdHJhaWxpbmcgd2hpdGVzcGFjZS4gKi9cbnZhciByZVRyaW0gPSAvXlxccyt8XFxzKyQvZztcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJhZCBzaWduZWQgaGV4YWRlY2ltYWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmFkSGV4ID0gL15bLStdMHhbMC05YS1mXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBiaW5hcnkgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmluYXJ5ID0gL14wYlswMV0rJC9pO1xuXG4vKiogVXNlZCB0byBkZXRlY3Qgb2N0YWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzT2N0YWwgPSAvXjBvWzAtN10rJC9pO1xuXG4vKiogQnVpbHQtaW4gbWV0aG9kIHJlZmVyZW5jZXMgd2l0aG91dCBhIGRlcGVuZGVuY3kgb24gYHJvb3RgLiAqL1xudmFyIGZyZWVQYXJzZUludCA9IHBhcnNlSW50O1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYGdsb2JhbGAgZnJvbSBOb2RlLmpzLiAqL1xudmFyIGZyZWVHbG9iYWwgPSB0eXBlb2YgZ2xvYmFsID09ICdvYmplY3QnICYmIGdsb2JhbCAmJiBnbG9iYWwuT2JqZWN0ID09PSBPYmplY3QgJiYgZ2xvYmFsO1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYHNlbGZgLiAqL1xudmFyIGZyZWVTZWxmID0gdHlwZW9mIHNlbGYgPT0gJ29iamVjdCcgJiYgc2VsZiAmJiBzZWxmLk9iamVjdCA9PT0gT2JqZWN0ICYmIHNlbGY7XG5cbi8qKiBVc2VkIGFzIGEgcmVmZXJlbmNlIHRvIHRoZSBnbG9iYWwgb2JqZWN0LiAqL1xudmFyIHJvb3QgPSBmcmVlR2xvYmFsIHx8IGZyZWVTZWxmIHx8IEZ1bmN0aW9uKCdyZXR1cm4gdGhpcycpKCk7XG5cbi8qKiBVc2VkIGZvciBidWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKlxuICogVXNlZCB0byByZXNvbHZlIHRoZVxuICogW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmplY3RUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXgsXG4gICAgbmF0aXZlTWluID0gTWF0aC5taW47XG5cbi8qKlxuICogR2V0cyB0aGUgdGltZXN0YW1wIG9mIHRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRoYXQgaGF2ZSBlbGFwc2VkIHNpbmNlXG4gKiB0aGUgVW5peCBlcG9jaCAoMSBKYW51YXJ5IDE5NzAgMDA6MDA6MDAgVVRDKS5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDIuNC4wXG4gKiBAY2F0ZWdvcnkgRGF0ZVxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgdGltZXN0YW1wLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmRlZmVyKGZ1bmN0aW9uKHN0YW1wKSB7XG4gKiAgIGNvbnNvbGUubG9nKF8ubm93KCkgLSBzdGFtcCk7XG4gKiB9LCBfLm5vdygpKTtcbiAqIC8vID0+IExvZ3MgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgaXQgdG9vayBmb3IgdGhlIGRlZmVycmVkIGludm9jYXRpb24uXG4gKi9cbnZhciBub3cgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIHJvb3QuRGF0ZS5ub3coKTtcbn07XG5cbi8qKlxuICogQ3JlYXRlcyBhIGRlYm91bmNlZCBmdW5jdGlvbiB0aGF0IGRlbGF5cyBpbnZva2luZyBgZnVuY2AgdW50aWwgYWZ0ZXIgYHdhaXRgXG4gKiBtaWxsaXNlY29uZHMgaGF2ZSBlbGFwc2VkIHNpbmNlIHRoZSBsYXN0IHRpbWUgdGhlIGRlYm91bmNlZCBmdW5jdGlvbiB3YXNcbiAqIGludm9rZWQuIFRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgIG1ldGhvZCB0byBjYW5jZWxcbiAqIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLlxuICogUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2Agc2hvdWxkIGJlIGludm9rZWQgb24gdGhlXG4gKiBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGAgdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkXG4gKiB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50XG4gKiBjYWxscyB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHJldHVybiB0aGUgcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYFxuICogaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIGRlYm91bmNlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy5kZWJvdW5jZWAgYW5kIGBfLnRocm90dGxlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGRlYm91bmNlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIGRlbGF5LlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9ZmFsc2VdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgbGVhZGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHBhcmFtIHtudW1iZXJ9IFtvcHRpb25zLm1heFdhaXRdXG4gKiAgVGhlIG1heGltdW0gdGltZSBgZnVuY2AgaXMgYWxsb3dlZCB0byBiZSBkZWxheWVkIGJlZm9yZSBpdCdzIGludm9rZWQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGRlYm91bmNlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgY29zdGx5IGNhbGN1bGF0aW9ucyB3aGlsZSB0aGUgd2luZG93IHNpemUgaXMgaW4gZmx1eC5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdyZXNpemUnLCBfLmRlYm91bmNlKGNhbGN1bGF0ZUxheW91dCwgMTUwKSk7XG4gKlxuICogLy8gSW52b2tlIGBzZW5kTWFpbGAgd2hlbiBjbGlja2VkLCBkZWJvdW5jaW5nIHN1YnNlcXVlbnQgY2FsbHMuXG4gKiBqUXVlcnkoZWxlbWVudCkub24oJ2NsaWNrJywgXy5kZWJvdW5jZShzZW5kTWFpbCwgMzAwLCB7XG4gKiAgICdsZWFkaW5nJzogdHJ1ZSxcbiAqICAgJ3RyYWlsaW5nJzogZmFsc2VcbiAqIH0pKTtcbiAqXG4gKiAvLyBFbnN1cmUgYGJhdGNoTG9nYCBpcyBpbnZva2VkIG9uY2UgYWZ0ZXIgMSBzZWNvbmQgb2YgZGVib3VuY2VkIGNhbGxzLlxuICogdmFyIGRlYm91bmNlZCA9IF8uZGVib3VuY2UoYmF0Y2hMb2csIDI1MCwgeyAnbWF4V2FpdCc6IDEwMDAgfSk7XG4gKiB2YXIgc291cmNlID0gbmV3IEV2ZW50U291cmNlKCcvc3RyZWFtJyk7XG4gKiBqUXVlcnkoc291cmNlKS5vbignbWVzc2FnZScsIGRlYm91bmNlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyBkZWJvdW5jZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIGRlYm91bmNlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiBkZWJvdW5jZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsYXN0QXJncyxcbiAgICAgIGxhc3RUaGlzLFxuICAgICAgbWF4V2FpdCxcbiAgICAgIHJlc3VsdCxcbiAgICAgIHRpbWVySWQsXG4gICAgICBsYXN0Q2FsbFRpbWUsXG4gICAgICBsYXN0SW52b2tlVGltZSA9IDAsXG4gICAgICBsZWFkaW5nID0gZmFsc2UsXG4gICAgICBtYXhpbmcgPSBmYWxzZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICB3YWl0ID0gdG9OdW1iZXIod2FpdCkgfHwgMDtcbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICEhb3B0aW9ucy5sZWFkaW5nO1xuICAgIG1heGluZyA9ICdtYXhXYWl0JyBpbiBvcHRpb25zO1xuICAgIG1heFdhaXQgPSBtYXhpbmcgPyBuYXRpdmVNYXgodG9OdW1iZXIob3B0aW9ucy5tYXhXYWl0KSB8fCAwLCB3YWl0KSA6IG1heFdhaXQ7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuXG4gIGZ1bmN0aW9uIGludm9rZUZ1bmModGltZSkge1xuICAgIHZhciBhcmdzID0gbGFzdEFyZ3MsXG4gICAgICAgIHRoaXNBcmcgPSBsYXN0VGhpcztcblxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIHJlc3VsdCA9IGZ1bmMuYXBwbHkodGhpc0FyZywgYXJncyk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGxlYWRpbmdFZGdlKHRpbWUpIHtcbiAgICAvLyBSZXNldCBhbnkgYG1heFdhaXRgIHRpbWVyLlxuICAgIGxhc3RJbnZva2VUaW1lID0gdGltZTtcbiAgICAvLyBTdGFydCB0aGUgdGltZXIgZm9yIHRoZSB0cmFpbGluZyBlZGdlLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgLy8gSW52b2tlIHRoZSBsZWFkaW5nIGVkZ2UuXG4gICAgcmV0dXJuIGxlYWRpbmcgPyBpbnZva2VGdW5jKHRpbWUpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gcmVtYWluaW5nV2FpdCh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZSxcbiAgICAgICAgcmVzdWx0ID0gd2FpdCAtIHRpbWVTaW5jZUxhc3RDYWxsO1xuXG4gICAgcmV0dXJuIG1heGluZyA/IG5hdGl2ZU1pbihyZXN1bHQsIG1heFdhaXQgLSB0aW1lU2luY2VMYXN0SW52b2tlKSA6IHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIHNob3VsZEludm9rZSh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZTtcblxuICAgIC8vIEVpdGhlciB0aGlzIGlzIHRoZSBmaXJzdCBjYWxsLCBhY3Rpdml0eSBoYXMgc3RvcHBlZCBhbmQgd2UncmUgYXQgdGhlXG4gICAgLy8gdHJhaWxpbmcgZWRnZSwgdGhlIHN5c3RlbSB0aW1lIGhhcyBnb25lIGJhY2t3YXJkcyBhbmQgd2UncmUgdHJlYXRpbmdcbiAgICAvLyBpdCBhcyB0aGUgdHJhaWxpbmcgZWRnZSwgb3Igd2UndmUgaGl0IHRoZSBgbWF4V2FpdGAgbGltaXQuXG4gICAgcmV0dXJuIChsYXN0Q2FsbFRpbWUgPT09IHVuZGVmaW5lZCB8fCAodGltZVNpbmNlTGFzdENhbGwgPj0gd2FpdCkgfHxcbiAgICAgICh0aW1lU2luY2VMYXN0Q2FsbCA8IDApIHx8IChtYXhpbmcgJiYgdGltZVNpbmNlTGFzdEludm9rZSA+PSBtYXhXYWl0KSk7XG4gIH1cblxuICBmdW5jdGlvbiB0aW1lckV4cGlyZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKTtcbiAgICBpZiAoc2hvdWxkSW52b2tlKHRpbWUpKSB7XG4gICAgICByZXR1cm4gdHJhaWxpbmdFZGdlKHRpbWUpO1xuICAgIH1cbiAgICAvLyBSZXN0YXJ0IHRoZSB0aW1lci5cbiAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHJlbWFpbmluZ1dhaXQodGltZSkpO1xuICB9XG5cbiAgZnVuY3Rpb24gdHJhaWxpbmdFZGdlKHRpbWUpIHtcbiAgICB0aW1lcklkID0gdW5kZWZpbmVkO1xuXG4gICAgLy8gT25seSBpbnZva2UgaWYgd2UgaGF2ZSBgbGFzdEFyZ3NgIHdoaWNoIG1lYW5zIGBmdW5jYCBoYXMgYmVlblxuICAgIC8vIGRlYm91bmNlZCBhdCBsZWFzdCBvbmNlLlxuICAgIGlmICh0cmFpbGluZyAmJiBsYXN0QXJncykge1xuICAgICAgcmV0dXJuIGludm9rZUZ1bmModGltZSk7XG4gICAgfVxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGNhbmNlbCgpIHtcbiAgICBpZiAodGltZXJJZCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBjbGVhclRpbWVvdXQodGltZXJJZCk7XG4gICAgfVxuICAgIGxhc3RJbnZva2VUaW1lID0gMDtcbiAgICBsYXN0QXJncyA9IGxhc3RDYWxsVGltZSA9IGxhc3RUaGlzID0gdGltZXJJZCA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGZsdXNoKCkge1xuICAgIHJldHVybiB0aW1lcklkID09PSB1bmRlZmluZWQgPyByZXN1bHQgOiB0cmFpbGluZ0VkZ2Uobm93KCkpO1xuICB9XG5cbiAgZnVuY3Rpb24gZGVib3VuY2VkKCkge1xuICAgIHZhciB0aW1lID0gbm93KCksXG4gICAgICAgIGlzSW52b2tpbmcgPSBzaG91bGRJbnZva2UodGltZSk7XG5cbiAgICBsYXN0QXJncyA9IGFyZ3VtZW50cztcbiAgICBsYXN0VGhpcyA9IHRoaXM7XG4gICAgbGFzdENhbGxUaW1lID0gdGltZTtcblxuICAgIGlmIChpc0ludm9raW5nKSB7XG4gICAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBsZWFkaW5nRWRnZShsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgICAgaWYgKG1heGluZykge1xuICAgICAgICAvLyBIYW5kbGUgaW52b2NhdGlvbnMgaW4gYSB0aWdodCBsb29wLlxuICAgICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgICAgICByZXR1cm4gaW52b2tlRnVuYyhsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIGRlYm91bmNlZC5jYW5jZWwgPSBjYW5jZWw7XG4gIGRlYm91bmNlZC5mbHVzaCA9IGZsdXNoO1xuICByZXR1cm4gZGVib3VuY2VkO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSB0aHJvdHRsZWQgZnVuY3Rpb24gdGhhdCBvbmx5IGludm9rZXMgYGZ1bmNgIGF0IG1vc3Qgb25jZSBwZXJcbiAqIGV2ZXJ5IGB3YWl0YCBtaWxsaXNlY29uZHMuIFRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgXG4gKiBtZXRob2QgdG8gY2FuY2VsIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvXG4gKiBpbW1lZGlhdGVseSBpbnZva2UgdGhlbS4gUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2BcbiAqIHNob3VsZCBiZSBpbnZva2VkIG9uIHRoZSBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGBcbiAqIHRpbWVvdXQuIFRoZSBgZnVuY2AgaXMgaW52b2tlZCB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGVcbiAqIHRocm90dGxlZCBmdW5jdGlvbi4gU3Vic2VxdWVudCBjYWxscyB0byB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uIHJldHVybiB0aGVcbiAqIHJlc3VsdCBvZiB0aGUgbGFzdCBgZnVuY2AgaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIHRocm90dGxlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy50aHJvdHRsZWAgYW5kIGBfLmRlYm91bmNlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIHRocm90dGxlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIHRocm90dGxlIGludm9jYXRpb25zIHRvLlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IHRocm90dGxlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgZXhjZXNzaXZlbHkgdXBkYXRpbmcgdGhlIHBvc2l0aW9uIHdoaWxlIHNjcm9sbGluZy5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdzY3JvbGwnLCBfLnRocm90dGxlKHVwZGF0ZVBvc2l0aW9uLCAxMDApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHJlbmV3VG9rZW5gIHdoZW4gdGhlIGNsaWNrIGV2ZW50IGlzIGZpcmVkLCBidXQgbm90IG1vcmUgdGhhbiBvbmNlIGV2ZXJ5IDUgbWludXRlcy5cbiAqIHZhciB0aHJvdHRsZWQgPSBfLnRocm90dGxlKHJlbmV3VG9rZW4sIDMwMDAwMCwgeyAndHJhaWxpbmcnOiBmYWxzZSB9KTtcbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCB0aHJvdHRsZWQpO1xuICpcbiAqIC8vIENhbmNlbCB0aGUgdHJhaWxpbmcgdGhyb3R0bGVkIGludm9jYXRpb24uXG4gKiBqUXVlcnkod2luZG93KS5vbigncG9wc3RhdGUnLCB0aHJvdHRsZWQuY2FuY2VsKTtcbiAqL1xuZnVuY3Rpb24gdGhyb3R0bGUoZnVuYywgd2FpdCwgb3B0aW9ucykge1xuICB2YXIgbGVhZGluZyA9IHRydWUsXG4gICAgICB0cmFpbGluZyA9IHRydWU7XG5cbiAgaWYgKHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKEZVTkNfRVJST1JfVEVYVCk7XG4gIH1cbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICdsZWFkaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLmxlYWRpbmcgOiBsZWFkaW5nO1xuICAgIHRyYWlsaW5nID0gJ3RyYWlsaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLnRyYWlsaW5nIDogdHJhaWxpbmc7XG4gIH1cbiAgcmV0dXJuIGRlYm91bmNlKGZ1bmMsIHdhaXQsIHtcbiAgICAnbGVhZGluZyc6IGxlYWRpbmcsXG4gICAgJ21heFdhaXQnOiB3YWl0LFxuICAgICd0cmFpbGluZyc6IHRyYWlsaW5nXG4gIH0pO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIHRoZVxuICogW2xhbmd1YWdlIHR5cGVdKGh0dHA6Ly93d3cuZWNtYS1pbnRlcm5hdGlvbmFsLm9yZy9lY21hLTI2Mi83LjAvI3NlYy1lY21hc2NyaXB0LWxhbmd1YWdlLXR5cGVzKVxuICogb2YgYE9iamVjdGAuIChlLmcuIGFycmF5cywgZnVuY3Rpb25zLCBvYmplY3RzLCByZWdleGVzLCBgbmV3IE51bWJlcigwKWAsIGFuZCBgbmV3IFN0cmluZygnJylgKVxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGFuIG9iamVjdCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0KHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KFsxLCAyLCAzXSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChfLm5vb3ApO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QobnVsbCk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc09iamVjdCh2YWx1ZSkge1xuICB2YXIgdHlwZSA9IHR5cGVvZiB2YWx1ZTtcbiAgcmV0dXJuICEhdmFsdWUgJiYgKHR5cGUgPT0gJ29iamVjdCcgfHwgdHlwZSA9PSAnZnVuY3Rpb24nKTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZS4gQSB2YWx1ZSBpcyBvYmplY3QtbGlrZSBpZiBpdCdzIG5vdCBgbnVsbGBcbiAqIGFuZCBoYXMgYSBgdHlwZW9mYCByZXN1bHQgb2YgXCJvYmplY3RcIi5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZSwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZSh7fSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdExpa2UoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShfLm5vb3ApO1xuICogLy8gPT4gZmFsc2VcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0TGlrZSh2YWx1ZSkge1xuICByZXR1cm4gISF2YWx1ZSAmJiB0eXBlb2YgdmFsdWUgPT0gJ29iamVjdCc7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgY2xhc3NpZmllZCBhcyBhIGBTeW1ib2xgIHByaW1pdGl2ZSBvciBvYmplY3QuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSA0LjAuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYSBzeW1ib2wsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc1N5bWJvbChTeW1ib2wuaXRlcmF0b3IpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNTeW1ib2woJ2FiYycpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNTeW1ib2wodmFsdWUpIHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PSAnc3ltYm9sJyB8fFxuICAgIChpc09iamVjdExpa2UodmFsdWUpICYmIG9iamVjdFRvU3RyaW5nLmNhbGwodmFsdWUpID09IHN5bWJvbFRhZyk7XG59XG5cbi8qKlxuICogQ29udmVydHMgYHZhbHVlYCB0byBhIG51bWJlci5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gcHJvY2Vzcy5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIG51bWJlci5cbiAqIEBleGFtcGxlXG4gKlxuICogXy50b051bWJlcigzLjIpO1xuICogLy8gPT4gMy4yXG4gKlxuICogXy50b051bWJlcihOdW1iZXIuTUlOX1ZBTFVFKTtcbiAqIC8vID0+IDVlLTMyNFxuICpcbiAqIF8udG9OdW1iZXIoSW5maW5pdHkpO1xuICogLy8gPT4gSW5maW5pdHlcbiAqXG4gKiBfLnRvTnVtYmVyKCczLjInKTtcbiAqIC8vID0+IDMuMlxuICovXG5mdW5jdGlvbiB0b051bWJlcih2YWx1ZSkge1xuICBpZiAodHlwZW9mIHZhbHVlID09ICdudW1iZXInKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIGlmIChpc1N5bWJvbCh2YWx1ZSkpIHtcbiAgICByZXR1cm4gTkFOO1xuICB9XG4gIGlmIChpc09iamVjdCh2YWx1ZSkpIHtcbiAgICB2YXIgb3RoZXIgPSB0eXBlb2YgdmFsdWUudmFsdWVPZiA9PSAnZnVuY3Rpb24nID8gdmFsdWUudmFsdWVPZigpIDogdmFsdWU7XG4gICAgdmFsdWUgPSBpc09iamVjdChvdGhlcikgPyAob3RoZXIgKyAnJykgOiBvdGhlcjtcbiAgfVxuICBpZiAodHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHZhbHVlID09PSAwID8gdmFsdWUgOiArdmFsdWU7XG4gIH1cbiAgdmFsdWUgPSB2YWx1ZS5yZXBsYWNlKHJlVHJpbSwgJycpO1xuICB2YXIgaXNCaW5hcnkgPSByZUlzQmluYXJ5LnRlc3QodmFsdWUpO1xuICByZXR1cm4gKGlzQmluYXJ5IHx8IHJlSXNPY3RhbC50ZXN0KHZhbHVlKSlcbiAgICA/IGZyZWVQYXJzZUludCh2YWx1ZS5zbGljZSgyKSwgaXNCaW5hcnkgPyAyIDogOClcbiAgICA6IChyZUlzQmFkSGV4LnRlc3QodmFsdWUpID8gTkFOIDogK3ZhbHVlKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSB0aHJvdHRsZTtcbiIsInZhciBnO1xyXG5cclxuLy8gVGhpcyB3b3JrcyBpbiBub24tc3RyaWN0IG1vZGVcclxuZyA9IChmdW5jdGlvbigpIHtcclxuXHRyZXR1cm4gdGhpcztcclxufSkoKTtcclxuXHJcbnRyeSB7XHJcblx0Ly8gVGhpcyB3b3JrcyBpZiBldmFsIGlzIGFsbG93ZWQgKHNlZSBDU1ApXHJcblx0ZyA9IGcgfHwgRnVuY3Rpb24oXCJyZXR1cm4gdGhpc1wiKSgpIHx8ICgxLCBldmFsKShcInRoaXNcIik7XHJcbn0gY2F0Y2ggKGUpIHtcclxuXHQvLyBUaGlzIHdvcmtzIGlmIHRoZSB3aW5kb3cgcmVmZXJlbmNlIGlzIGF2YWlsYWJsZVxyXG5cdGlmICh0eXBlb2Ygd2luZG93ID09PSBcIm9iamVjdFwiKSBnID0gd2luZG93O1xyXG59XHJcblxyXG4vLyBnIGNhbiBzdGlsbCBiZSB1bmRlZmluZWQsIGJ1dCBub3RoaW5nIHRvIGRvIGFib3V0IGl0Li4uXHJcbi8vIFdlIHJldHVybiB1bmRlZmluZWQsIGluc3RlYWQgb2Ygbm90aGluZyBoZXJlLCBzbyBpdCdzXHJcbi8vIGVhc2llciB0byBoYW5kbGUgdGhpcyBjYXNlLiBpZighZ2xvYmFsKSB7IC4uLn1cclxuXHJcbm1vZHVsZS5leHBvcnRzID0gZztcclxuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5pbXBvcnQgeyBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcblxuZXhwb3J0IGNsYXNzIEFjdGl2ZUxpbmVNYXJrZXIge1xuXHRwcml2YXRlIF9jdXJyZW50OiBhbnk7XG5cblx0b25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmU6IG51bWJlcikge1xuXHRcdGNvbnN0IHsgcHJldmlvdXMgfSA9IGdldEVsZW1lbnRzRm9yU291cmNlTGluZShsaW5lKTtcblx0XHR0aGlzLl91cGRhdGUocHJldmlvdXMgJiYgcHJldmlvdXMuZWxlbWVudCk7XG5cdH1cblxuXHRfdXBkYXRlKGJlZm9yZTogSFRNTEVsZW1lbnQgfCB1bmRlZmluZWQpIHtcblx0XHR0aGlzLl91bm1hcmtBY3RpdmVFbGVtZW50KHRoaXMuX2N1cnJlbnQpO1xuXHRcdHRoaXMuX21hcmtBY3RpdmVFbGVtZW50KGJlZm9yZSk7XG5cdFx0dGhpcy5fY3VycmVudCA9IGJlZm9yZTtcblx0fVxuXG5cdF91bm1hcmtBY3RpdmVFbGVtZW50KGVsZW1lbnQ6IEhUTUxFbGVtZW50IHwgdW5kZWZpbmVkKSB7XG5cdFx0aWYgKCFlbGVtZW50KSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdGVsZW1lbnQuY2xhc3NOYW1lID0gZWxlbWVudC5jbGFzc05hbWUucmVwbGFjZSgvXFxiY29kZS1hY3RpdmUtbGluZVxcYi9nLCAnJyk7XG5cdH1cblxuXHRfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudDogSFRNTEVsZW1lbnQgfCB1bmRlZmluZWQpIHtcblx0XHRpZiAoIWVsZW1lbnQpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0ZWxlbWVudC5jbGFzc05hbWUgKz0gJyBjb2RlLWFjdGl2ZS1saW5lJztcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGY6ICgpID0+IHZvaWQpIHtcblx0aWYgKGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdsb2FkaW5nJyB8fCBkb2N1bWVudC5yZWFkeVN0YXRlIGFzIHN0cmluZyA9PT0gJ3VuaW5pdGlhbGl6ZWQnKSB7XG5cdFx0ZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGYpO1xuXHR9IGVsc2Uge1xuXHRcdGYoKTtcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBBY3RpdmVMaW5lTWFya2VyIH0gZnJvbSAnLi9hY3RpdmVMaW5lTWFya2VyJztcbmltcG9ydCB7IG9uY2VEb2N1bWVudExvYWRlZCB9IGZyb20gJy4vZXZlbnRzJztcbmltcG9ydCB7IGNyZWF0ZVBvc3RlckZvclZzQ29kZSB9IGZyb20gJy4vbWVzc2FnaW5nJztcbmltcG9ydCB7IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0LCBzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUsIGdldExpbmVFbGVtZW50Rm9yRnJhZ21lbnQgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcbmltcG9ydCB7IGdldFNldHRpbmdzLCBnZXREYXRhIH0gZnJvbSAnLi9zZXR0aW5ncyc7XG5pbXBvcnQgdGhyb3R0bGUgPSByZXF1aXJlKCdsb2Rhc2gudGhyb3R0bGUnKTtcblxuZGVjbGFyZSB2YXIgYWNxdWlyZVZzQ29kZUFwaTogYW55O1xuXG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IEFjdGl2ZUxpbmVNYXJrZXIoKTtcbmNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3MoKTtcblxuY29uc3QgdnNjb2RlID0gYWNxdWlyZVZzQ29kZUFwaSgpO1xuXG4vLyBTZXQgVlMgQ29kZSBzdGF0ZVxubGV0IHN0YXRlID0gZ2V0RGF0YTx7IGxpbmU6IG51bWJlciwgIGZyYWdtZW50OiBzdHJpbmcgfT4oJ2RhdGEtc3RhdGUnKTtcbnZzY29kZS5zZXRTdGF0ZShzdGF0ZSk7XG5cbmNvbnN0IG1lc3NhZ2luZyA9IGNyZWF0ZVBvc3RlckZvclZzQ29kZSh2c2NvZGUpO1xuXG53aW5kb3cuY3NwQWxlcnRlci5zZXRQb3N0ZXIobWVzc2FnaW5nKTtcbndpbmRvdy5zdHlsZUxvYWRpbmdNb25pdG9yLnNldFBvc3RlcihtZXNzYWdpbmcpO1xuXG53aW5kb3cub25sb2FkID0gKCkgPT4ge1xuXHR1cGRhdGVJbWFnZVNpemVzKCk7XG59O1xuXG5vbmNlRG9jdW1lbnRMb2FkZWQoKCkgPT4ge1xuXHRpZiAoc2V0dGluZ3Muc2Nyb2xsUHJldmlld1dpdGhFZGl0b3IpIHtcblx0XHRzZXRUaW1lb3V0KCgpID0+IHtcblx0XHRcdC8vIFRyeSB0byBzY3JvbGwgdG8gZnJhZ21lbnQgaWYgYXZhaWxhYmxlXG5cdFx0XHRpZiAoc3RhdGUuZnJhZ21lbnQpIHtcblx0XHRcdFx0Y29uc3QgZWxlbWVudCA9IGdldExpbmVFbGVtZW50Rm9yRnJhZ21lbnQoc3RhdGUuZnJhZ21lbnQpO1xuXHRcdFx0XHRpZiAoZWxlbWVudCkge1xuXHRcdFx0XHRcdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0XHRcdFx0XHRzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUoZWxlbWVudC5saW5lKTtcblx0XHRcdFx0fVxuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0Y29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcblx0XHRcdFx0aWYgKCFpc05hTihpbml0aWFsTGluZSkpIHtcblx0XHRcdFx0XHRzY3JvbGxEaXNhYmxlZCA9IHRydWU7XG5cdFx0XHRcdFx0c2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH0sIDApO1xuXHR9XG59KTtcblxuY29uc3Qgb25VcGRhdGVWaWV3ID0gKCgpID0+IHtcblx0Y29uc3QgZG9TY3JvbGwgPSB0aHJvdHRsZSgobGluZTogbnVtYmVyKSA9PiB7XG5cdFx0c2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuXHRcdHNjcm9sbFRvUmV2ZWFsU291cmNlTGluZShsaW5lKTtcblx0fSwgNTApO1xuXG5cdHJldHVybiAobGluZTogbnVtYmVyLCBzZXR0aW5nczogYW55KSA9PiB7XG5cdFx0aWYgKCFpc05hTihsaW5lKSkge1xuXHRcdFx0c2V0dGluZ3MubGluZSA9IGxpbmU7XG5cdFx0XHRkb1Njcm9sbChsaW5lKTtcblx0XHR9XG5cdH07XG59KSgpO1xuXG5sZXQgdXBkYXRlSW1hZ2VTaXplcyA9IHRocm90dGxlKCgpID0+IHtcblx0Y29uc3QgaW1hZ2VJbmZvOiB7IGlkOiBzdHJpbmcsIGhlaWdodDogbnVtYmVyLCB3aWR0aDogbnVtYmVyIH1bXSA9IFtdO1xuXHRsZXQgaW1hZ2VzID0gZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ2ltZycpO1xuXHRpZiAoaW1hZ2VzKSB7XG5cdFx0bGV0IGk7XG5cdFx0Zm9yIChpID0gMDsgaSA8IGltYWdlcy5sZW5ndGg7IGkrKykge1xuXHRcdFx0Y29uc3QgaW1nID0gaW1hZ2VzW2ldO1xuXG5cdFx0XHRpZiAoaW1nLmNsYXNzTGlzdC5jb250YWlucygnbG9hZGluZycpKSB7XG5cdFx0XHRcdGltZy5jbGFzc0xpc3QucmVtb3ZlKCdsb2FkaW5nJyk7XG5cdFx0XHR9XG5cblx0XHRcdGltYWdlSW5mby5wdXNoKHtcblx0XHRcdFx0aWQ6IGltZy5pZCxcblx0XHRcdFx0aGVpZ2h0OiBpbWcuaGVpZ2h0LFxuXHRcdFx0XHR3aWR0aDogaW1nLndpZHRoXG5cdFx0XHR9KTtcblx0XHR9XG5cblx0XHRtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NhY2hlSW1hZ2VTaXplcycsIGltYWdlSW5mbyk7XG5cdH1cbn0sIDUwKTtcblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Jlc2l6ZScsICgpID0+IHtcblx0c2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuXHR1cGRhdGVJbWFnZVNpemVzKCk7XG59LCB0cnVlKTtcblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21lc3NhZ2UnLCBldmVudCA9PiB7XG5cdGlmIChldmVudC5kYXRhLnNvdXJjZSAhPT0gc2V0dGluZ3Muc291cmNlKSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0c3dpdGNoIChldmVudC5kYXRhLnR5cGUpIHtcblx0XHRjYXNlICdvbkRpZENoYW5nZVRleHRFZGl0b3JTZWxlY3Rpb24nOlxuXHRcdFx0bWFya2VyLm9uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbihldmVudC5kYXRhLmxpbmUpO1xuXHRcdFx0YnJlYWs7XG5cblx0XHRjYXNlICd1cGRhdGVWaWV3Jzpcblx0XHRcdG9uVXBkYXRlVmlldyhldmVudC5kYXRhLmxpbmUsIHNldHRpbmdzKTtcblx0XHRcdGJyZWFrO1xuXHR9XG59LCBmYWxzZSk7XG5cbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2RibGNsaWNrJywgZXZlbnQgPT4ge1xuXHRpZiAoIXNldHRpbmdzLmRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvcikge1xuXHRcdHJldHVybjtcblx0fVxuXG5cdC8vIElnbm9yZSBjbGlja3Mgb24gbGlua3Ncblx0Zm9yIChsZXQgbm9kZSA9IGV2ZW50LnRhcmdldCBhcyBIVE1MRWxlbWVudDsgbm9kZTsgbm9kZSA9IG5vZGUucGFyZW50Tm9kZSBhcyBIVE1MRWxlbWVudCkge1xuXHRcdGlmIChub2RlLnRhZ05hbWUgPT09ICdBJykge1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblx0fVxuXG5cdGNvbnN0IG9mZnNldCA9IGV2ZW50LnBhZ2VZO1xuXHRjb25zdCBsaW5lID0gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQob2Zmc2V0KTtcblx0aWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcblx0XHRtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2RpZENsaWNrJywgeyBsaW5lOiBNYXRoLmZsb29yKGxpbmUpIH0pO1xuXHR9XG59KTtcblxuZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCBldmVudCA9PiB7XG5cdGlmICghZXZlbnQpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHRsZXQgbm9kZTogYW55ID0gZXZlbnQudGFyZ2V0O1xuXHR3aGlsZSAobm9kZSkge1xuXHRcdGlmIChub2RlLnRhZ05hbWUgJiYgbm9kZS50YWdOYW1lID09PSAnQScgJiYgbm9kZS5ocmVmKSB7XG5cdFx0XHRpZiAobm9kZS5nZXRBdHRyaWJ1dGUoJ2hyZWYnKS5zdGFydHNXaXRoKCcjJykpIHtcblx0XHRcdFx0YnJlYWs7XG5cdFx0XHR9XG5cdFx0XHRpZiAobm9kZS5ocmVmLnN0YXJ0c1dpdGgoJ2ZpbGU6Ly8nKSB8fCBub2RlLmhyZWYuc3RhcnRzV2l0aCgndnNjb2RlLXJlc291cmNlOicpIHx8IG5vZGUuaHJlZi5zdGFydHNXaXRoKHNldHRpbmdzLndlYnZpZXdSZXNvdXJjZVJvb3QpKSB7XG5cdFx0XHRcdGNvbnN0IFtwYXRoLCBmcmFnbWVudF0gPSBub2RlLmhyZWYucmVwbGFjZSgvXihmaWxlOlxcL1xcL3x2c2NvZGUtcmVzb3VyY2U6KS9pLCAnJykucmVwbGFjZShuZXcgUmVnRXhwKGBeJHtlc2NhcGVSZWdFeHAoc2V0dGluZ3Mud2Vidmlld1Jlc291cmNlUm9vdCl9YCkpLnNwbGl0KCcjJyk7XG5cdFx0XHRcdG1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnY2xpY2tMaW5rJywgeyBwYXRoLCBmcmFnbWVudCB9KTtcblx0XHRcdFx0ZXZlbnQucHJldmVudERlZmF1bHQoKTtcblx0XHRcdFx0ZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG5cdFx0XHRcdGJyZWFrO1xuXHRcdFx0fVxuXHRcdFx0YnJlYWs7XG5cdFx0fVxuXHRcdG5vZGUgPSBub2RlLnBhcmVudE5vZGU7XG5cdH1cbn0sIHRydWUpO1xuXG5pZiAoc2V0dGluZ3Muc2Nyb2xsRWRpdG9yV2l0aFByZXZpZXcpIHtcblx0d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Njcm9sbCcsIHRocm90dGxlKCgpID0+IHtcblx0XHRpZiAoc2Nyb2xsRGlzYWJsZWQpIHtcblx0XHRcdHNjcm9sbERpc2FibGVkID0gZmFsc2U7XG5cdFx0fSBlbHNlIHtcblx0XHRcdGNvbnN0IGxpbmUgPSBnZXRFZGl0b3JMaW5lTnVtYmVyRm9yUGFnZU9mZnNldCh3aW5kb3cuc2Nyb2xsWSk7XG5cdFx0XHRpZiAodHlwZW9mIGxpbmUgPT09ICdudW1iZXInICYmICFpc05hTihsaW5lKSkge1xuXHRcdFx0XHRtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ3JldmVhbExpbmUnLCB7IGxpbmUgfSk7XG5cdFx0XHRcdHN0YXRlLmxpbmUgPSBsaW5lO1xuXHRcdFx0XHR2c2NvZGUuc2V0U3RhdGUoc3RhdGUpO1xuXHRcdFx0fVxuXHRcdH1cblx0fSwgNTApKTtcbn1cblxuZnVuY3Rpb24gZXNjYXBlUmVnRXhwKHRleHQ6IHN0cmluZykge1xuXHRyZXR1cm4gdGV4dC5yZXBsYWNlKC9bLVtcXF17fSgpKis/LixcXFxcXiR8I1xcc10vZywgJ1xcXFwkJicpO1xufVxuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmltcG9ydCB7IGdldFNldHRpbmdzIH0gZnJvbSAnLi9zZXR0aW5ncyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgTWVzc2FnZVBvc3RlciB7XG5cdC8qKlxuXHQgKiBQb3N0IGEgbWVzc2FnZSB0byB0aGUgbWFya2Rvd24gZXh0ZW5zaW9uXG5cdCAqL1xuXHRwb3N0TWVzc2FnZSh0eXBlOiBzdHJpbmcsIGJvZHk6IG9iamVjdCk6IHZvaWQ7XG59XG5cbmV4cG9ydCBjb25zdCBjcmVhdGVQb3N0ZXJGb3JWc0NvZGUgPSAodnNjb2RlOiBhbnkpID0+IHtcblx0cmV0dXJuIG5ldyBjbGFzcyBpbXBsZW1lbnRzIE1lc3NhZ2VQb3N0ZXIge1xuXHRcdHBvc3RNZXNzYWdlKHR5cGU6IHN0cmluZywgYm9keTogb2JqZWN0KTogdm9pZCB7XG5cdFx0XHR2c2NvZGUucG9zdE1lc3NhZ2Uoe1xuXHRcdFx0XHR0eXBlLFxuXHRcdFx0XHRzb3VyY2U6IGdldFNldHRpbmdzKCkuc291cmNlLFxuXHRcdFx0XHRib2R5XG5cdFx0XHR9KTtcblx0XHR9XG5cdH07XG59O1xuXG4iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgZ2V0U2V0dGluZ3MgfSBmcm9tICcuL3NldHRpbmdzJztcblxuXG5mdW5jdGlvbiBjbGFtcChtaW46IG51bWJlciwgbWF4OiBudW1iZXIsIHZhbHVlOiBudW1iZXIpIHtcblx0cmV0dXJuIE1hdGgubWluKG1heCwgTWF0aC5tYXgobWluLCB2YWx1ZSkpO1xufVxuXG5mdW5jdGlvbiBjbGFtcExpbmUobGluZTogbnVtYmVyKSB7XG5cdHJldHVybiBjbGFtcCgwLCBnZXRTZXR0aW5ncygpLmxpbmVDb3VudCAtIDEsIGxpbmUpO1xufVxuXG5cbmV4cG9ydCBpbnRlcmZhY2UgQ29kZUxpbmVFbGVtZW50IHtcblx0ZWxlbWVudDogSFRNTEVsZW1lbnQ7XG5cdGxpbmU6IG51bWJlcjtcbn1cblxuY29uc3QgZ2V0Q29kZUxpbmVFbGVtZW50cyA9ICgoKSA9PiB7XG5cdGxldCBlbGVtZW50czogQ29kZUxpbmVFbGVtZW50W107XG5cdHJldHVybiAoKSA9PiB7XG5cdFx0aWYgKCFlbGVtZW50cykge1xuXHRcdFx0ZWxlbWVudHMgPSBbeyBlbGVtZW50OiBkb2N1bWVudC5ib2R5LCBsaW5lOiAwIH1dO1xuXHRcdFx0Zm9yIChjb25zdCBlbGVtZW50IG9mIGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ2NvZGUtbGluZScpKSB7XG5cdFx0XHRcdGNvbnN0IGxpbmUgPSArZWxlbWVudC5nZXRBdHRyaWJ1dGUoJ2RhdGEtbGluZScpITtcblx0XHRcdFx0aWYgKCFpc05hTihsaW5lKSkge1xuXHRcdFx0XHRcdGVsZW1lbnRzLnB1c2goeyBlbGVtZW50OiBlbGVtZW50IGFzIEhUTUxFbGVtZW50LCBsaW5lIH0pO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fVxuXHRcdHJldHVybiBlbGVtZW50cztcblx0fTtcbn0pKCk7XG5cbi8qKlxuICogRmluZCB0aGUgaHRtbCBlbGVtZW50cyB0aGF0IG1hcCB0byBhIHNwZWNpZmljIHRhcmdldCBsaW5lIGluIHRoZSBlZGl0b3IuXG4gKlxuICogSWYgYW4gZXhhY3QgbWF0Y2gsIHJldHVybnMgYSBzaW5nbGUgZWxlbWVudC4gSWYgdGhlIGxpbmUgaXMgYmV0d2VlbiBlbGVtZW50cyxcbiAqIHJldHVybnMgdGhlIGVsZW1lbnQgcHJpb3IgdG8gYW5kIHRoZSBlbGVtZW50IGFmdGVyIHRoZSBnaXZlbiBsaW5lLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lKHRhcmdldExpbmU6IG51bWJlcik6IHsgcHJldmlvdXM6IENvZGVMaW5lRWxlbWVudDsgbmV4dD86IENvZGVMaW5lRWxlbWVudDsgfSB7XG5cdGNvbnN0IGxpbmVOdW1iZXIgPSBNYXRoLmZsb29yKHRhcmdldExpbmUpO1xuXHRjb25zdCBsaW5lcyA9IGdldENvZGVMaW5lRWxlbWVudHMoKTtcblx0bGV0IHByZXZpb3VzID0gbGluZXNbMF0gfHwgbnVsbDtcblx0Zm9yIChjb25zdCBlbnRyeSBvZiBsaW5lcykge1xuXHRcdGlmIChlbnRyeS5saW5lID09PSBsaW5lTnVtYmVyKSB7XG5cdFx0XHRyZXR1cm4geyBwcmV2aW91czogZW50cnksIG5leHQ6IHVuZGVmaW5lZCB9O1xuXHRcdH0gZWxzZSBpZiAoZW50cnkubGluZSA+IGxpbmVOdW1iZXIpIHtcblx0XHRcdHJldHVybiB7IHByZXZpb3VzLCBuZXh0OiBlbnRyeSB9O1xuXHRcdH1cblx0XHRwcmV2aW91cyA9IGVudHJ5O1xuXHR9XG5cdHJldHVybiB7IHByZXZpb3VzIH07XG59XG5cbi8qKlxuICogRmluZCB0aGUgaHRtbCBlbGVtZW50cyB0aGF0IGFyZSBhdCBhIHNwZWNpZmljIHBpeGVsIG9mZnNldCBvbiB0aGUgcGFnZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldChvZmZzZXQ6IG51bWJlcik6IHsgcHJldmlvdXM6IENvZGVMaW5lRWxlbWVudDsgbmV4dD86IENvZGVMaW5lRWxlbWVudDsgfSB7XG5cdGNvbnN0IGxpbmVzID0gZ2V0Q29kZUxpbmVFbGVtZW50cygpO1xuXHRjb25zdCBwb3NpdGlvbiA9IG9mZnNldCAtIHdpbmRvdy5zY3JvbGxZO1xuXHRsZXQgbG8gPSAtMTtcblx0bGV0IGhpID0gbGluZXMubGVuZ3RoIC0gMTtcblx0d2hpbGUgKGxvICsgMSA8IGhpKSB7XG5cdFx0Y29uc3QgbWlkID0gTWF0aC5mbG9vcigobG8gKyBoaSkgLyAyKTtcblx0XHRjb25zdCBib3VuZHMgPSBsaW5lc1ttaWRdLmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG5cdFx0aWYgKGJvdW5kcy50b3AgKyBib3VuZHMuaGVpZ2h0ID49IHBvc2l0aW9uKSB7XG5cdFx0XHRoaSA9IG1pZDtcblx0XHR9XG5cdFx0ZWxzZSB7XG5cdFx0XHRsbyA9IG1pZDtcblx0XHR9XG5cdH1cblx0Y29uc3QgaGlFbGVtZW50ID0gbGluZXNbaGldO1xuXHRjb25zdCBoaUJvdW5kcyA9IGhpRWxlbWVudC5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuXHRpZiAoaGkgPj0gMSAmJiBoaUJvdW5kcy50b3AgPiBwb3NpdGlvbikge1xuXHRcdGNvbnN0IGxvRWxlbWVudCA9IGxpbmVzW2xvXTtcblx0XHRyZXR1cm4geyBwcmV2aW91czogbG9FbGVtZW50LCBuZXh0OiBoaUVsZW1lbnQgfTtcblx0fVxuXHRyZXR1cm4geyBwcmV2aW91czogaGlFbGVtZW50IH07XG59XG5cbi8qKlxuICogQXR0ZW1wdCB0byByZXZlYWwgdGhlIGVsZW1lbnQgZm9yIGEgc291cmNlIGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNjcm9sbFRvUmV2ZWFsU291cmNlTGluZShsaW5lOiBudW1iZXIpIHtcblx0aWYgKCFnZXRTZXR0aW5ncygpLnNjcm9sbFByZXZpZXdXaXRoRWRpdG9yKSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0aWYgKGxpbmUgPD0gMCkge1xuXHRcdHdpbmRvdy5zY3JvbGwod2luZG93LnNjcm9sbFgsIDApO1xuXHRcdHJldHVybjtcblx0fVxuXG5cdGNvbnN0IHsgcHJldmlvdXMsIG5leHQgfSA9IGdldEVsZW1lbnRzRm9yU291cmNlTGluZShsaW5lKTtcblx0aWYgKCFwcmV2aW91cykge1xuXHRcdHJldHVybjtcblx0fVxuXHRsZXQgc2Nyb2xsVG8gPSAwO1xuXHRjb25zdCByZWN0ID0gcHJldmlvdXMuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0Y29uc3QgcHJldmlvdXNUb3AgPSByZWN0LnRvcDtcblx0aWYgKG5leHQgJiYgbmV4dC5saW5lICE9PSBwcmV2aW91cy5saW5lKSB7XG5cdFx0Ly8gQmV0d2VlbiB0d28gZWxlbWVudHMuIEdvIHRvIHBlcmNlbnRhZ2Ugb2Zmc2V0IGJldHdlZW4gdGhlbS5cblx0XHRjb25zdCBiZXR3ZWVuUHJvZ3Jlc3MgPSAobGluZSAtIHByZXZpb3VzLmxpbmUpIC8gKG5leHQubGluZSAtIHByZXZpb3VzLmxpbmUpO1xuXHRcdGNvbnN0IGVsZW1lbnRPZmZzZXQgPSBuZXh0LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wIC0gcHJldmlvdXNUb3A7XG5cdFx0c2Nyb2xsVG8gPSBwcmV2aW91c1RvcCArIGJldHdlZW5Qcm9ncmVzcyAqIGVsZW1lbnRPZmZzZXQ7XG5cdH0gZWxzZSB7XG5cdFx0Y29uc3QgcHJvZ3Jlc3NJbkVsZW1lbnQgPSBsaW5lIC0gTWF0aC5mbG9vcihsaW5lKTtcblx0XHRzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgKHJlY3QuaGVpZ2h0ICogcHJvZ3Jlc3NJbkVsZW1lbnQpO1xuXHR9XG5cdHdpbmRvdy5zY3JvbGwod2luZG93LnNjcm9sbFgsIE1hdGgubWF4KDEsIHdpbmRvdy5zY3JvbGxZICsgc2Nyb2xsVG8pKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldDogbnVtYmVyKSB7XG5cdGNvbnN0IHsgcHJldmlvdXMsIG5leHQgfSA9IGdldExpbmVFbGVtZW50c0F0UGFnZU9mZnNldChvZmZzZXQpO1xuXHRpZiAocHJldmlvdXMpIHtcblx0XHRjb25zdCBwcmV2aW91c0JvdW5kcyA9IHByZXZpb3VzLmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG5cdFx0Y29uc3Qgb2Zmc2V0RnJvbVByZXZpb3VzID0gKG9mZnNldCAtIHdpbmRvdy5zY3JvbGxZIC0gcHJldmlvdXNCb3VuZHMudG9wKTtcblx0XHRpZiAobmV4dCkge1xuXHRcdFx0Y29uc3QgcHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMgPSBvZmZzZXRGcm9tUHJldmlvdXMgLyAobmV4dC5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLnRvcCAtIHByZXZpb3VzQm91bmRzLnRvcCk7XG5cdFx0XHRjb25zdCBsaW5lID0gcHJldmlvdXMubGluZSArIHByb2dyZXNzQmV0d2VlbkVsZW1lbnRzICogKG5leHQubGluZSAtIHByZXZpb3VzLmxpbmUpO1xuXHRcdFx0cmV0dXJuIGNsYW1wTGluZShsaW5lKTtcblx0XHR9XG5cdFx0ZWxzZSB7XG5cdFx0XHRjb25zdCBwcm9ncmVzc1dpdGhpbkVsZW1lbnQgPSBvZmZzZXRGcm9tUHJldmlvdXMgLyAocHJldmlvdXNCb3VuZHMuaGVpZ2h0KTtcblx0XHRcdGNvbnN0IGxpbmUgPSBwcmV2aW91cy5saW5lICsgcHJvZ3Jlc3NXaXRoaW5FbGVtZW50O1xuXHRcdFx0cmV0dXJuIGNsYW1wTGluZShsaW5lKTtcblx0XHR9XG5cdH1cblx0cmV0dXJuIG51bGw7XG59XG5cbi8qKlxuICogVHJ5IHRvIGZpbmQgdGhlIGh0bWwgZWxlbWVudCBieSB1c2luZyBhIGZyYWdtZW50IGlkXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRMaW5lRWxlbWVudEZvckZyYWdtZW50KGZyYWdtZW50OiBzdHJpbmcpOiBDb2RlTGluZUVsZW1lbnQgfCB1bmRlZmluZWQge1xuXHRyZXR1cm4gZ2V0Q29kZUxpbmVFbGVtZW50cygpLmZpbmQoKGVsZW1lbnQpID0+IHtcblx0XHRyZXR1cm4gZWxlbWVudC5lbGVtZW50LmlkID09PSBmcmFnbWVudDtcblx0fSk7XG59XG4iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuZXhwb3J0IGludGVyZmFjZSBQcmV2aWV3U2V0dGluZ3Mge1xuXHRyZWFkb25seSBzb3VyY2U6IHN0cmluZztcblx0cmVhZG9ubHkgbGluZTogbnVtYmVyO1xuXHRyZWFkb25seSBsaW5lQ291bnQ6IG51bWJlcjtcblx0cmVhZG9ubHkgc2Nyb2xsUHJldmlld1dpdGhFZGl0b3I/OiBib29sZWFuO1xuXHRyZWFkb25seSBzY3JvbGxFZGl0b3JXaXRoUHJldmlldzogYm9vbGVhbjtcblx0cmVhZG9ubHkgZGlzYWJsZVNlY3VyaXR5V2FybmluZ3M6IGJvb2xlYW47XG5cdHJlYWRvbmx5IGRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvcjogYm9vbGVhbjtcblx0cmVhZG9ubHkgd2Vidmlld1Jlc291cmNlUm9vdDogc3RyaW5nO1xufVxuXG5sZXQgY2FjaGVkU2V0dGluZ3M6IFByZXZpZXdTZXR0aW5ncyB8IHVuZGVmaW5lZCA9IHVuZGVmaW5lZDtcblxuZXhwb3J0IGZ1bmN0aW9uIGdldERhdGE8VCA9IHt9PihrZXk6IHN0cmluZyk6IFQge1xuXHRjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcblx0aWYgKGVsZW1lbnQpIHtcblx0XHRjb25zdCBkYXRhID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoa2V5KTtcblx0XHRpZiAoZGF0YSkge1xuXHRcdFx0cmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG5cdFx0fVxuXHR9XG5cblx0dGhyb3cgbmV3IEVycm9yKGBDb3VsZCBub3QgbG9hZCBkYXRhIGZvciAke2tleX1gKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdldFNldHRpbmdzKCk6IFByZXZpZXdTZXR0aW5ncyB7XG5cdGlmIChjYWNoZWRTZXR0aW5ncykge1xuXHRcdHJldHVybiBjYWNoZWRTZXR0aW5ncztcblx0fVxuXG5cdGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuXHRpZiAoY2FjaGVkU2V0dGluZ3MpIHtcblx0XHRyZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG5cdH1cblxuXHR0aHJvdyBuZXcgRXJyb3IoJ0NvdWxkIG5vdCBsb2FkIHNldHRpbmdzJyk7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 0d74e1ac674..7bcf7ca8641 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -24,6 +24,7 @@ "onCommand:markdown.showLockedPreviewToSide", "onCommand:markdown.showSource", "onCommand:markdown.showPreviewSecuritySelector", + "onCommand:markdown.api.render", "onWebviewPanel:markdown.preview" ], "contributes": { @@ -328,4 +329,4 @@ "webpack": "^4.1.0", "webpack-cli": "^2.0.10" } -} \ No newline at end of file +} diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index f78ae2f7972..b12614fe9ec 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -6,7 +6,7 @@ import { ActiveLineMarker } from './activeLineMarker'; import { onceDocumentLoaded } from './events'; import { createPosterForVsCode } from './messaging'; -import { getEditorLineNumberForPageOffset, scrollToRevealSourceLine } from './scroll-sync'; +import { getEditorLineNumberForPageOffset, scrollToRevealSourceLine, getLineElementForFragment } from './scroll-sync'; import { getSettings, getData } from './settings'; import throttle = require('lodash.throttle'); @@ -19,7 +19,7 @@ const settings = getSettings(); const vscode = acquireVsCodeApi(); // Set VS Code state -let state = getData<{ line: number }>('data-state'); +let state = getData<{ line: number, fragment: string }>('data-state'); vscode.setState(state); const messaging = createPosterForVsCode(vscode); @@ -34,10 +34,19 @@ window.onload = () => { onceDocumentLoaded(() => { if (settings.scrollPreviewWithEditor) { setTimeout(() => { - const initialLine = +settings.line; - if (!isNaN(initialLine)) { - scrollDisabled = true; - scrollToRevealSourceLine(initialLine); + // Try to scroll to fragment if available + if (state.fragment) { + const element = getLineElementForFragment(state.fragment); + if (element) { + scrollDisabled = true; + scrollToRevealSourceLine(element.line); + } + } else { + const initialLine = +settings.line; + if (!isNaN(initialLine)) { + scrollDisabled = true; + scrollToRevealSourceLine(initialLine); + } } }, 0); } @@ -161,4 +170,4 @@ if (settings.scrollEditorWithPreview) { function escapeRegExp(text: string) { return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); -} \ No newline at end of file +} diff --git a/extensions/markdown-language-features/preview-src/scroll-sync.ts b/extensions/markdown-language-features/preview-src/scroll-sync.ts index 4fe8987f6cd..eee320ac8e7 100644 --- a/extensions/markdown-language-features/preview-src/scroll-sync.ts +++ b/extensions/markdown-language-features/preview-src/scroll-sync.ts @@ -134,3 +134,12 @@ export function getEditorLineNumberForPageOffset(offset: number) { } return null; } + +/** + * Try to find the html element by using a fragment id + */ +export function getLineElementForFragment(fragment: string): CodeLineElement | undefined { + return getCodeLineElements().find((element) => { + return element.element.id === fragment; + }); +} diff --git a/extensions/markdown-language-features/src/commands/index.ts b/extensions/markdown-language-features/src/commands/index.ts index e8c9651ee0c..68aff7ffcf5 100644 --- a/extensions/markdown-language-features/src/commands/index.ts +++ b/extensions/markdown-language-features/src/commands/index.ts @@ -10,3 +10,4 @@ export { RefreshPreviewCommand } from './refreshPreview'; export { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector'; export { MoveCursorToPositionCommand } from './moveCursorToPosition'; export { ToggleLockCommand } from './toggleLock'; +export { RenderDocument } from './renderDocument'; diff --git a/extensions/markdown-language-features/src/commands/renderDocument.ts b/extensions/markdown-language-features/src/commands/renderDocument.ts new file mode 100644 index 00000000000..f9ec89fce3d --- /dev/null +++ b/extensions/markdown-language-features/src/commands/renderDocument.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Command } from '../commandManager'; +import { MarkdownEngine } from '../markdownEngine'; +import { SkinnyTextDocument } from '../tableOfContentsProvider'; + +export class RenderDocument implements Command { + public readonly id = 'markdown.api.render'; + + public constructor( + private readonly engine: MarkdownEngine + ) { } + + public async execute(document: SkinnyTextDocument | string): Promise { + return this.engine.render(document); + } +} diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index 3fe2c9d4cd3..44e8873b16d 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -83,6 +83,7 @@ function registerMarkdownCommands( commandManager.register(new commands.ShowPreviewSecuritySelectorCommand(previewSecuritySelector, previewManager)); commandManager.register(new commands.OpenDocumentLinkCommand(engine)); commandManager.register(new commands.ToggleLockCommand(previewManager)); + commandManager.register(new commands.RenderDocument(engine)); return commandManager; } diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index c874f5791ed..3ee4d26f4f2 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -89,6 +89,7 @@ export class MarkdownPreview extends Disposable { private isScrolling = false; private _disposed: boolean = false; private imageInfo: { id: string, width: number, height: number }[] = []; + private scrollToFragment: string | undefined; public static async revive( webview: vscode.WebviewPanel, @@ -171,19 +172,19 @@ export class MarkdownPreview extends Disposable { this._locked = locked; this.editor = webview; - this.editor.onDidDispose(() => { + this._register(this.editor.onDidDispose(() => { this.dispose(); - }, null, this._disposables); + })); - this.editor.onDidChangeViewState(e => { + this._register(this.editor.onDidChangeViewState(e => { this._onDidChangeViewStateEmitter.fire(e); - }, null, this._disposables); + })); - _contributionProvider.onContributionsChanged(() => { + this._register(_contributionProvider.onContributionsChanged(() => { setImmediate(() => this.refresh()); - }, null, this._disposables); + })); - this.editor.webview.onDidReceiveMessage((e: CacheImageSizesMessage | RevealLineMessage | DidClickMessage | ClickLinkMessage | ShowPreviewSecuritySelectorMessage | PreviewStyleLoadErrorMessage) => { + this._register(this.editor.webview.onDidReceiveMessage((e: CacheImageSizesMessage | RevealLineMessage | DidClickMessage | ClickLinkMessage | ShowPreviewSecuritySelectorMessage | PreviewStyleLoadErrorMessage) => { if (e.source !== this._resource.toString()) { return; } @@ -213,21 +214,21 @@ export class MarkdownPreview extends Disposable { vscode.window.showWarningMessage(localize('onPreviewStyleLoadError', "Could not load 'markdown.styles': {0}", e.body.unloadedStyles.join(', '))); break; } - }, null, this._disposables); + })); - vscode.workspace.onDidChangeTextDocument(event => { + this._register(vscode.workspace.onDidChangeTextDocument(event => { if (this.isPreviewOf(event.document.uri)) { this.refresh(); } - }, null, this._disposables); + })); - topmostLineMonitor.onDidChangeTopmostLine(event => { + this._register(topmostLineMonitor.onDidChangeTopmostLine(event => { if (this.isPreviewOf(event.resource)) { this.updateForView(event.resource, event.line); } - }, null, this._disposables); + })); - vscode.window.onDidChangeTextEditorSelection(event => { + this._register(vscode.window.onDidChangeTextEditorSelection(event => { if (this.isPreviewOf(event.textEditor.document.uri)) { this.postMessage({ type: 'onDidChangeTextEditorSelection', @@ -235,19 +236,19 @@ export class MarkdownPreview extends Disposable { source: this.resource.toString() }); } - }, null, this._disposables); + })); - vscode.window.onDidChangeActiveTextEditor(editor => { + this._register(vscode.window.onDidChangeActiveTextEditor(editor => { if (editor && isMarkdownFile(editor.document) && !this._locked) { this.update(editor.document.uri); } - }, null, this._disposables); + })); } - private readonly _onDisposeEmitter = new vscode.EventEmitter(); + private readonly _onDisposeEmitter = this._register(new vscode.EventEmitter()); public readonly onDispose = this._onDisposeEmitter.event; - private readonly _onDidChangeViewStateEmitter = new vscode.EventEmitter(); + private readonly _onDidChangeViewStateEmitter = this._register(new vscode.EventEmitter()); public readonly onDidChangeViewState = this._onDidChangeViewStateEmitter.event; public get resource(): vscode.Uri { @@ -264,7 +265,8 @@ export class MarkdownPreview extends Disposable { locked: this._locked, line: this.line, resourceColumn: this.resourceColumn, - imageInfo: this.imageInfo + imageInfo: this.imageInfo, + fragment: this.scrollToFragment }; } @@ -284,8 +286,12 @@ export class MarkdownPreview extends Disposable { public update(resource: vscode.Uri) { const editor = vscode.window.activeTextEditor; + // Reposition scroll preview, position scroll to the top if active text editor + // doesn't corresponds with preview if (editor && editor.document.uri.fsPath === resource.fsPath) { this.line = getVisibleLine(editor); + } else { + this.line = 0; } // If we have changed resources, cancel any pending updates @@ -433,8 +439,8 @@ export class MarkdownPreview extends Disposable { if (this._resource === markdownResource) { const self = this; const resourceProvider: WebviewResourceProvider = { - toWebviewResource: (resource) => { - return this.editor.webview.toWebviewResource(normalizeResource(markdownResource, resource)); + asWebviewUri: (resource) => { + return this.editor.webview.asWebviewUri(normalizeResource(markdownResource, resource)); }, get cspSource() { return self.editor.webview.cspSource; } }; @@ -520,11 +526,15 @@ export class MarkdownPreview extends Disposable { } private async onDidClickPreviewLink(path: string, fragment: string | undefined) { + this.scrollToFragment = undefined; const config = vscode.workspace.getConfiguration('markdown', this.resource); const openLinks = config.get('preview.openMarkdownLinks', 'inPreview'); if (openLinks === 'inPreview') { const markdownLink = await resolveLinkToMarkdownFile(path); if (markdownLink) { + if (fragment) { + this.scrollToFragment = fragment; + } this.update(markdownLink); return; } diff --git a/extensions/markdown-language-features/src/features/previewContentProvider.ts b/extensions/markdown-language-features/src/features/previewContentProvider.ts index 17b6d4f4ebb..0be66d1d630 100644 --- a/extensions/markdown-language-features/src/features/previewContentProvider.ts +++ b/extensions/markdown-language-features/src/features/previewContentProvider.ts @@ -65,7 +65,7 @@ export class MarkdownContentProvider { scrollEditorWithPreview: config.scrollEditorWithPreview, doubleClickToSwitchToEditor: config.doubleClickToSwitchToEditor, disableSecurityWarnings: this.cspArbiter.shouldDisableSecurityWarnings(), - webviewResourceRoot: resourceProvider.toWebviewResource(markdownDocument.uri).toString(), + webviewResourceRoot: resourceProvider.asWebviewUri(markdownDocument.uri).toString(), }; this.logger.log('provideTextDocumentContent', initialData); @@ -86,7 +86,7 @@ export class MarkdownContentProvider { data-state="${escapeAttribute(JSON.stringify(state || {}))}"> ${this.getStyles(resourceProvider, sourceUri, config, state)} - + ${body} @@ -110,7 +110,7 @@ export class MarkdownContentProvider { } private extensionResourcePath(resourceProvider: WebviewResourceProvider, mediaFile: string): string { - const webviewResource = resourceProvider.toWebviewResource( + const webviewResource = resourceProvider.asWebviewUri( vscode.Uri.file(this.context.asAbsolutePath(path.join('media', mediaFile)))); return webviewResource.toString(); } @@ -126,17 +126,17 @@ export class MarkdownContentProvider { // Assume it must be a local file if (path.isAbsolute(href)) { - return resourceProvider.toWebviewResource(vscode.Uri.file(href)).toString(); + return resourceProvider.asWebviewUri(vscode.Uri.file(href)).toString(); } // Use a workspace relative path if there is a workspace const root = vscode.workspace.getWorkspaceFolder(resource); if (root) { - return resourceProvider.toWebviewResource(vscode.Uri.file(path.join(root.uri.fsPath, href))).toString(); + return resourceProvider.asWebviewUri(vscode.Uri.file(path.join(root.uri.fsPath, href))).toString(); } // Otherwise look relative to the markdown file - return resourceProvider.toWebviewResource(vscode.Uri.file(path.join(path.dirname(resource.fsPath), href))).toString(); + return resourceProvider.asWebviewUri(vscode.Uri.file(path.join(path.dirname(resource.fsPath), href))).toString(); } private computeCustomStyleSheetIncludes(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration): string { @@ -176,7 +176,7 @@ export class MarkdownContentProvider { private getStyles(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration, state?: any): string { const baseStyles: string[] = []; for (const resource of this.contributionProvider.contributions.previewStyles) { - baseStyles.push(``); + baseStyles.push(``); } return `${baseStyles.join('\n')} @@ -188,7 +188,7 @@ export class MarkdownContentProvider { const out: string[] = []; for (const resource of this.contributionProvider.contributions.previewScripts) { out.push(``); } @@ -209,7 +209,7 @@ export class MarkdownContentProvider { return ``; case MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent: - return ''; + return ''; case MarkdownPreviewSecurityLevel.Strict: default: diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 0cc75bfe823..bcd7dc43ff4 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -118,7 +118,7 @@ export class MarkdownEngine { return md; } - private tokenize( + private tokenizeDocument( document: SkinnyTextDocument, config: MarkdownItConfig, engine: MarkdownIt @@ -131,16 +131,23 @@ export class MarkdownEngine { this.currentDocument = document.uri; this._slugCount = new Map(); - const text = document.getText(); - const tokens = engine.parse(text.replace(UNICODE_NEWLINE_REGEX, ''), {}); + const tokens = this.tokenizeString(document.getText(), engine); this._tokenCache.update(document, config, tokens); return tokens; } - public async render(document: SkinnyTextDocument): Promise { - const config = this.getConfig(document.uri); + private tokenizeString(text: string, engine: MarkdownIt) { + return engine.parse(text.replace(UNICODE_NEWLINE_REGEX, ''), {}); + } + + public async render(input: SkinnyTextDocument | string): Promise { + const config = this.getConfig(typeof input === 'string' ? undefined : input.uri); const engine = await this.getEngine(config); - return engine.renderer.render(this.tokenize(document, config, engine), { + const tokens = typeof input === 'string' + ? this.tokenizeString(input, engine) + : this.tokenizeDocument(input, config, engine); + + return engine.renderer.render(tokens, { ...(engine as any).options, ...config }, {}); @@ -149,14 +156,14 @@ export class MarkdownEngine { public async parse(document: SkinnyTextDocument): Promise { const config = this.getConfig(document.uri); const engine = await this.getEngine(config); - return this.tokenize(document, config, engine); + return this.tokenizeDocument(document, config, engine); } public cleanCache(): void { this._tokenCache.clean(); } - private getConfig(resource: vscode.Uri): MarkdownItConfig { + private getConfig(resource?: vscode.Uri): MarkdownItConfig { const config = vscode.workspace.getConfiguration('markdown', resource); return { breaks: config.get('preview.breaks', false), @@ -297,13 +304,13 @@ async function getMarkdownOptions(md: () => MarkdownIt) { html: true, highlight: (str: string, lang?: string) => { // Workaround for highlight not supporting tsx: https://github.com/isagalaev/highlight.js/issues/1155 - if (lang && ['tsx', 'typescriptreact'].indexOf(lang.toLocaleLowerCase()) >= 0) { + if (lang && ['tsx', 'typescriptreact'].includes(lang.toLocaleLowerCase())) { lang = 'jsx'; } if (lang && lang.toLocaleLowerCase() === 'json5') { lang = 'json'; } - if (lang && lang.toLocaleLowerCase() === 'c#') { + if (lang && ['c#', 'csharp'].includes(lang.toLocaleLowerCase())) { lang = 'cs'; } if (lang && hljs.getLanguage(lang)) { diff --git a/extensions/markdown-language-features/src/test/engine.test.ts b/extensions/markdown-language-features/src/test/engine.test.ts new file mode 100644 index 00000000000..3cd4f86693e --- /dev/null +++ b/extensions/markdown-language-features/src/test/engine.test.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * 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 vscode from 'vscode'; +import 'mocha'; + +import { InMemoryDocument } from './inMemoryDocument'; +import { createNewMarkdownEngine } from './engine'; + +const testFileName = vscode.Uri.file('test.md'); + +suite('markdown.engine', () => { + suite('rendering', () => { + const input = '# hello\n\nworld!'; + const output = '

hello

\n' + + '

world!

\n'; + + test('Renders a document', async () => { + const doc = new InMemoryDocument(testFileName, input); + const engine = createNewMarkdownEngine(); + assert.strictEqual(await engine.render(doc), output); + }); + + test('Renders a string', async () => { + const engine = createNewMarkdownEngine(); + assert.strictEqual(await engine.render(input), output); + }); + }); +}); diff --git a/extensions/markdown-language-features/src/util/resources.ts b/extensions/markdown-language-features/src/util/resources.ts index 1def7adcce0..063c410b39e 100644 --- a/extensions/markdown-language-features/src/util/resources.ts +++ b/extensions/markdown-language-features/src/util/resources.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; export interface WebviewResourceProvider { - toWebviewResource(resource: vscode.Uri): vscode.Uri; + asWebviewUri(resource: vscode.Uri): vscode.Uri; readonly cspSource: string; } @@ -30,4 +30,4 @@ export function normalizeResource( } } return resource; -} \ No newline at end of file +} diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index f60f7531516..0e6a28bfc33 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -5,7 +5,7 @@ import { TaskDefinition, Task, TaskGroup, WorkspaceFolder, RelativePattern, ShellExecution, Uri, workspace, - DebugConfiguration, debug, TaskProvider, TextDocument, tasks + DebugConfiguration, debug, TaskProvider, TextDocument, tasks, TaskScope } from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; @@ -34,6 +34,21 @@ export class NpmTaskProvider implements TaskProvider { } public resolveTask(_task: Task): Task | undefined { + const npmTask = (_task.definition).script; + if (npmTask) { + const kind: NpmTaskDefinition = (_task.definition); + let packageJsonUri: Uri; + if (_task.scope === undefined || _task.scope === TaskScope.Global || _task.scope === TaskScope.Workspace) { + // scope is required to be a WorkspaceFolder for resolveTask + return undefined; + } + if (kind.path) { + packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/' + kind.path + 'package.json' }); + } else { + packageJsonUri = _task.scope.uri.with({ path: _task.scope.uri.path + '/package.json' }); + } + return createTask(kind, `run ${kind.script}`, _task.scope, packageJsonUri); + } return undefined; } } @@ -221,7 +236,13 @@ export function getTaskName(script: string, relativePath: string | undefined) { return script; } -export function createTask(script: string, cmd: string, folder: WorkspaceFolder, packageJsonUri: Uri, matcher?: any): Task { +export function createTask(script: NpmTaskDefinition | string, cmd: string, folder: WorkspaceFolder, packageJsonUri: Uri, matcher?: any): Task { + let kind: NpmTaskDefinition; + if (typeof script === 'string') { + kind = { type: 'npm', script: script }; + } else { + kind = script; + } function getCommandLine(folder: WorkspaceFolder, cmd: string): string { let packageManager = getPackageManager(folder); @@ -237,15 +258,11 @@ export function createTask(script: string, cmd: string, folder: WorkspaceFolder, return absolutePath.substring(rootUri.path.length + 1); } - let kind: NpmTaskDefinition = { - type: 'npm', - script: script - }; let relativePackageJson = getRelativePath(folder, packageJsonUri); if (relativePackageJson.length) { kind.path = getRelativePath(folder, packageJsonUri); } - let taskName = getTaskName(script, relativePackageJson); + let taskName = getTaskName(kind.script, relativePackageJson); let cwd = path.dirname(packageJsonUri.fsPath); return new Task(kind, folder, taskName, 'npm', new ShellExecution(getCommandLine(folder, cmd), { cwd: cwd }), matcher); } diff --git a/extensions/package.json b/extensions/package.json index 52ece5e2051..5c73fcbd3d7 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.5.2" + "typescript": "3.6.1-rc" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/perl/cgmanifest.json b/extensions/perl/cgmanifest.json index b7c850dd1aa..83d91107671 100644 --- a/extensions/perl/cgmanifest.json +++ b/extensions/perl/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "textmate/perl.tmbundle", "repositoryUrl": "https://github.com/textmate/perl.tmbundle", - "commitHash": "d9841a0878239fa43f88c640f8d458590f97e8f5" + "commitHash": "80826abe75250286c2a1a07958e50e8551d3f50c" } }, "licenseDetail": [ diff --git a/extensions/powershell/cgmanifest.json b/extensions/powershell/cgmanifest.json index e9adf03ca04..c8dcfd7cc80 100644 --- a/extensions/powershell/cgmanifest.json +++ b/extensions/powershell/cgmanifest.json @@ -5,13 +5,13 @@ "type": "git", "git": { "name": "PowerShell/EditorSyntax", - "repositoryUrl": "https://github.com/powershell/editorsyntax", - "commitHash": "12b7d7257eb493e45a9af0af9094ec0c2a996712" + "repositoryUrl": "https://github.com/PowerShell/EditorSyntax", + "commitHash": "d10ae29c0d3ceb248172c383a159ae43b9ccfb4d" } }, "license": "MIT", - "version": "0.0.0" + "version": "1.0.0" } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/powershell/syntaxes/powershell.tmLanguage.json b/extensions/powershell/syntaxes/powershell.tmLanguage.json index 59ee6fbd130..de473e19fb8 100644 --- a/extensions/powershell/syntaxes/powershell.tmLanguage.json +++ b/extensions/powershell/syntaxes/powershell.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/PowerShell/EditorSyntax/commit/44eac8702f3cbe55a4ec70c1fdb163d42b4162fc", + "version": "https://github.com/PowerShell/EditorSyntax/commit/d10ae29c0d3ceb248172c383a159ae43b9ccfb4d", "name": "PowerShell", "scopeName": "source.powershell", "patterns": [ @@ -670,7 +670,7 @@ } }, "comment": "Style preference variables as language variables so that they stand out.", - "match": "(\\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|ProgressPreference|PsCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|VerbosePreference|WarningPreference|WhatIfPreference))((?:\\.(?:\\p{L}|\\d|_)+)*\\b)?\\b" + "match": "(\\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|InformationPreference|LogCommandHealthEvent|LogCommandLifecycleEvent|LogEngineHealthEvent|LogEngineLifecycleEvent|LogProviderHealthEvent|LogProviderLifecycleEvent|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|PSCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoLoadingPreference|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|ProgressPreference|VerbosePreference|WarningPreference|WhatIfPreference))((?:\\.(?:\\p{L}|\\d|_)+)*\\b)?\\b" }, { "captures": { @@ -848,7 +848,7 @@ } }, "comment": "Style preference variables as language variables so that they stand out.", - "match": "(\\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|ProgressPreference|PsCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|VerbosePreference|WarningPreference|WhatIfPreference))\\b" + "match": "(\\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|InformationPreference|LogCommandHealthEvent|LogCommandLifecycleEvent|LogEngineHealthEvent|LogEngineLifecycleEvent|LogProviderHealthEvent|LogProviderLifecycleEvent|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|PSCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoLoadingPreference|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|ProgressPreference|VerbosePreference|WarningPreference|WhatIfPreference))\\b" }, { "captures": { diff --git a/extensions/powershell/test/colorize-results/test_ps1.json b/extensions/powershell/test/colorize-results/test_ps1.json index e41d1306499..67380ce77b6 100644 --- a/extensions/powershell/test/colorize-results/test_ps1.json +++ b/extensions/powershell/test/colorize-results/test_ps1.json @@ -465,11 +465,11 @@ "c": ".IsInRole", "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell variable.other.readwrite.powershell variable.other.member.powershell", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.powershell variable.other.member: #DCDCAA", + "light_plus": "source.powershell variable.other.member: #795E26", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" + "hc_black": "source.powershell variable.other.member: #DCDCAA" } }, { diff --git a/extensions/razor/language-configuration.json b/extensions/razor/language-configuration.json index e2a49bcbd8e..55c5321e9a4 100644 --- a/extensions/razor/language-configuration.json +++ b/extensions/razor/language-configuration.json @@ -4,7 +4,6 @@ }, "brackets": [ [""], - ["<", ">"], ["{", "}"], ["(", ")"] ], @@ -20,4 +19,4 @@ { "open": "\"", "close": "\"" }, { "open": "<", "close": ">" } ] -} \ No newline at end of file +} diff --git a/extensions/ruby/cgmanifest.json b/extensions/ruby/cgmanifest.json index ffbbb7b71c6..8f04db20f54 100644 --- a/extensions/ruby/cgmanifest.json +++ b/extensions/ruby/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "textmate/ruby.tmbundle", "repositoryUrl": "https://github.com/textmate/ruby.tmbundle", - "commitHash": "74713556df10fbc7b1f9e99013ab1e34cd836f56" + "commitHash": "05698e99314d8653e2e9d8f198d3c83e387ee1eb" } }, "licenseDetail": [ diff --git a/extensions/ruby/syntaxes/ruby.tmLanguage.json b/extensions/ruby/syntaxes/ruby.tmLanguage.json index 62fbbc265e5..8b6c98616d8 100644 --- a/extensions/ruby/syntaxes/ruby.tmLanguage.json +++ b/extensions/ruby/syntaxes/ruby.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/textmate/ruby.tmbundle/commit/74713556df10fbc7b1f9e99013ab1e34cd836f56", + "version": "https://github.com/textmate/ruby.tmbundle/commit/05698e99314d8653e2e9d8f198d3c83e387ee1eb", "name": "Ruby", "scopeName": "source.ruby", "comment": "\n\tTODO: unresolved issues\n\n\ttext:\n\t\"p <<<[-~](\"?)((?:[_\\w]+_|)HTML)\\b\\1))", "comment": "Heredoc with embedded html", diff --git a/extensions/swift/syntaxes/swift.tmLanguage.json b/extensions/swift/syntaxes/swift.tmLanguage.json index 490388e079a..2e2148073db 100644 --- a/extensions/swift/syntaxes/swift.tmLanguage.json +++ b/extensions/swift/syntaxes/swift.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/textmate/swift.tmbundle/commit/cec27af6662e3799120b208e64483efdfe5521f5", + "version": "https://github.com/textmate/swift.tmbundle/commit/bdc8ae07562060a566b33da3f62601bb5aababd1", "name": "Swift", "scopeName": "source.swift", "comment": "See swift.tmbundle/grammar-test.swift for test cases.", @@ -46,7 +46,7 @@ "name": "constant.numeric.swift" } }, - "match": "\\b((?:iOS|macOS|OSX|watchOS|tvOS)(?:ApplicationExtension)?)\\b(?:\\s+([0-9]+(?:\\.[0-9]+)*\\b))?" + "match": "\\b(swift|(?:iOS|macOS|OSX|watchOS|tvOS|UIKitForMac)(?:ApplicationExtension)?)\\b(?:\\s+([0-9]+(?:\\.[0-9]+)*\\b))?" }, { "begin": "\\b(introduced|deprecated|obsoleted)\\s*(:)\\s*", @@ -587,7 +587,24 @@ "match": "\\b(os)\\s*(\\()\\s*(?:(macOS|OSX|iOS|tvOS|watchOS|Android|Linux|FreeBSD|Windows|PS4)|\\w+)\\s*(\\))" }, { - "begin": "\\b(swift)\\s*(\\()", + "captures": { + "1": { + "name": "keyword.other.condition.swift" + }, + "2": { + "name": "punctuation.definition.parameters.begin.swift" + }, + "3": { + "name": "entity.name.type.module.swift" + }, + "4": { + "name": "punctuation.definition.parameters.end.swift" + } + }, + "match": "\\b(canImport)\\s*(\\()([\\p{L}_][\\p{L}_\\p{N}\\p{M}]*)(\\))" + }, + { + "begin": "\\b(targetEnvironment)\\s*(\\()", "beginCaptures": { "1": { "name": "keyword.other.condition.swift" @@ -604,7 +621,30 @@ }, "patterns": [ { - "match": ">=", + "match": "\\b(simulator|UIKitForMac)\\b", + "name": "support.constant.platform.environment.swift" + } + ] + }, + { + "begin": "\\b(swift|compiler)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "keyword.other.condition.swift" + }, + "2": { + "name": "punctuation.definition.parameters.begin.swift" + } + }, + "end": "(\\))|$", + "endCaptures": { + "1": { + "name": "punctuation.definition.parameters.end.swift" + } + }, + "patterns": [ + { + "match": ">=|<", "name": "keyword.operator.comparison.swift" }, { @@ -1977,22 +2017,27 @@ "include": "#comments" }, { - "captures": { + "begin": "(?x)\n\t\t\t\t\t\t\t\t\t\t(?:(_)|((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k))\n\t\t\t\t\t\t\t\t\t\t\\s+\n\t\t\t\t\t\t\t\t\t\t(((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k))\n\t\t\t\t\t\t\t\t\t\t\\s*(:)", + "beginCaptures": { "1": { - "name": "invalid.illegal.distinct-labels-not-allowed.swift" - }, - "3": { "name": "entity.name.function.swift" }, - "4": { + "2": { + "name": "invalid.illegal.distinct-labels-not-allowed.swift" + }, + "5": { "name": "variable.parameter.function.swift" }, - "6": { + "7": { "name": "punctuation.separator.argument-label.swift" } }, "end": "(?=[,)\\]])", - "match": "(?x)\n\t\t\t\t\t\t\t\t\t\t((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k)\n\t\t\t\t\t\t\t\t\t\t\\s+\n\t\t\t\t\t\t\t\t\t\t(((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k))\n\t\t\t\t\t\t\t\t\t\t\\s*(:)" + "patterns": [ + { + "include": "#available-types" + } + ] }, { "begin": "(((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*\\k))\\s*(:)", @@ -2279,7 +2324,7 @@ "name": "constant.numeric.swift" } }, - "match": "\\s*\\b((?:iOS|macOS|OSX|watchOS|tvOS)(?:ApplicationExtension)?)\\b(?:\\s+([0-9]+(?:\\.[0-9]+)*\\b))" + "match": "\\s*\\b((?:iOS|macOS|OSX|watchOS|tvOS|UIKitForMac)(?:ApplicationExtension)?)\\b(?:\\s+([0-9]+(?:\\.[0-9]+)*\\b))" }, { "captures": { diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index 1898153ba89..f12d3d7633e 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -8,7 +8,8 @@ "scope": [ "entity.name.function", "support.function", - "support.constant.handlebars" + "support.constant.handlebars", + "source.powershell variable.other.member" ], "settings": { "foreground": "#DCDCAA" @@ -171,4 +172,4 @@ } } ] -} \ No newline at end of file +} diff --git a/extensions/theme-defaults/themes/hc_black.json b/extensions/theme-defaults/themes/hc_black.json index 8119256d5f2..f76d7bb960f 100644 --- a/extensions/theme-defaults/themes/hc_black.json +++ b/extensions/theme-defaults/themes/hc_black.json @@ -13,7 +13,8 @@ "scope": [ "entity.name.function", "support.function", - "support.constant.handlebars" + "support.constant.handlebars", + "source.powershell variable.other.member" ], "settings": { "foreground": "#DCDCAA" @@ -115,4 +116,4 @@ } } ] -} \ No newline at end of file +} diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index 7138f045d58..3d30c5d17fb 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -8,7 +8,8 @@ "scope": [ "entity.name.function", "support.function", - "support.constant.handlebars" + "support.constant.handlebars", + "source.powershell variable.other.member" ], "settings": { "foreground": "#795E26" @@ -172,4 +173,4 @@ } ] -} \ No newline at end of file +} diff --git a/extensions/theme-seti/build/update-icon-theme.js b/extensions/theme-seti/build/update-icon-theme.js index 6819e853339..3e14951e342 100644 --- a/extensions/theme-seti/build/update-icon-theme.js +++ b/extensions/theme-seti/build/update-icon-theme.js @@ -32,7 +32,8 @@ let nonBuiltInLanguages = { // { fileNames, extensions } "haml": { extensions: ['haml'] }, "stylus": { extensions: ['styl'] }, "vala": { extensions: ['vala'] }, - "todo": { fileNames: ['todo'] } + "todo": { fileNames: ['todo'] }, + "jsonc": { extensions: ['json'] } }; let FROM_DISK = true; // set to true to take content from a repo checked out next to the vscode repo @@ -109,7 +110,7 @@ function downloadBinary(source, dest) { return new Promise((c, e) => { https.get(source, function (response) { switch (response.statusCode) { - case 200: + case 200: { let file = fs.createWriteStream(dest); response.on('data', function (chunk) { file.write(chunk); @@ -121,6 +122,7 @@ function downloadBinary(source, dest) { e(err.message); }); break; + } case 301: case 302: case 303: diff --git a/extensions/theme-seti/cgmanifest.json b/extensions/theme-seti/cgmanifest.json index c742c019ea3..1c86b8bcb2b 100644 --- a/extensions/theme-seti/cgmanifest.json +++ b/extensions/theme-seti/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "seti-ui", "repositoryUrl": "https://github.com/jesseweed/seti-ui", - "commitHash": "904c16acced1134a81b31d71d60293288c31334b" + "commitHash": "85a222708824c6f19bbecbec71633d2c97077dad" } }, "version": "0.1.0" diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index 2b5d276a515..825ac52ee83 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -1564,6 +1564,7 @@ "version.md": "_clock", "version": "_clock", "mvnw": "_maven", + "tsconfig.json": "_tsconfig", "swagger.json": "_json_1", "swagger.yml": "_json_1", "swagger.yaml": "_json_1", @@ -1573,6 +1574,8 @@ "docker-healthcheck": "_docker_2", "docker-compose.yml": "_docker_3", "docker-compose.yaml": "_docker_3", + "docker-compose.override.yml": "_docker_3", + "docker-compose.override.yaml": "_docker_3", "firebase.json": "_firebase", "geckodriver": "_firefox", "gruntfile.js": "_grunt", @@ -1608,7 +1611,7 @@ "bat": "_windows", "clojure": "_clojure", "coffeescript": "_coffee", - "jsonc": "_tsconfig", + "jsonc": "_json", "c": "_c", "cpp": "_cpp", "csharp": "_c-sharp", @@ -1871,7 +1874,7 @@ "bat": "_windows_light", "clojure": "_clojure_light", "coffeescript": "_coffee_light", - "jsonc": "_tsconfig_light", + "jsonc": "_json_light", "c": "_c_light", "cpp": "_cpp_light", "csharp": "_c-sharp_light", @@ -1940,6 +1943,7 @@ "version.md": "_clock_light", "version": "_clock_light", "mvnw": "_maven_light", + "tsconfig.json": "_tsconfig_light", "swagger.json": "_json_1_light", "swagger.yml": "_json_1_light", "swagger.yaml": "_json_1_light", @@ -1949,6 +1953,8 @@ "docker-healthcheck": "_docker_2_light", "docker-compose.yml": "_docker_3_light", "docker-compose.yaml": "_docker_3_light", + "docker-compose.override.yml": "_docker_3_light", + "docker-compose.override.yaml": "_docker_3_light", "firebase.json": "_firebase_light", "geckodriver": "_firefox_light", "gruntfile.js": "_grunt_light", @@ -1980,5 +1986,5 @@ "npm-debug.log": "_npm_ignored_light" } }, - "version": "https://github.com/jesseweed/seti-ui/commit/904c16acced1134a81b31d71d60293288c31334b" + "version": "https://github.com/jesseweed/seti-ui/commit/85a222708824c6f19bbecbec71633d2c97077dad" } \ No newline at end of file diff --git a/extensions/typescript-basics/cgmanifest.json b/extensions/typescript-basics/cgmanifest.json index cd8c1071a82..077f5f0424e 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": "cf7b1ec2c20b5fe28695249596128ff514e1a659" + "commitHash": "84238ef0fa1c7e4e2c9fe77875d832dab9f57e5d" } }, "license": "MIT", diff --git a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json index ce5a34d5f9a..3d1b37da01e 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/11b1a4f8dc3a3eaa4df71e8cc1ad6f01a688961d", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/84238ef0fa1c7e4e2c9fe77875d832dab9f57e5d", "name": "TypeScript", "scopeName": "source.ts", "patterns": [ @@ -602,7 +602,7 @@ "include": "#comment" }, { - "begin": "(?x)(?=((\\b(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.ts" @@ -1347,7 +1347,7 @@ }, "object-literal-method-declaration": { "name": "meta.method.declaration.ts", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.async.ts" @@ -1368,7 +1368,7 @@ "include": "#function-body" }, { - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.async.ts" @@ -1390,7 +1390,7 @@ ] }, "method-declaration-name": { - "begin": "(?x)(?=((\\b(?\\s*$)", + "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`)))+\\s*/>\\s*$)", "beginCaptures": { "1": { "name": "punctuation.definition.comment.ts" diff --git a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json index 68776811480..d50da9102c9 100644 --- a/extensions/typescript-basics/syntaxes/TypeScriptReact.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScriptReact.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/cf7b1ec2c20b5fe28695249596128ff514e1a659", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/84238ef0fa1c7e4e2c9fe77875d832dab9f57e5d", "name": "TypeScriptReact", "scopeName": "source.tsx", "patterns": [ @@ -605,7 +605,7 @@ "include": "#comment" }, { - "begin": "(?x)(?=((\\b(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.tsx" @@ -1350,7 +1350,7 @@ }, "object-literal-method-declaration": { "name": "meta.method.declaration.tsx", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.async.tsx" @@ -1371,7 +1371,7 @@ "include": "#function-body" }, { - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", + "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{[^\\{\\}]*\\}))*\\})|(\\(([^\\(\\)]|(\\([^\\(\\)]*\\)))*\\))|(\\[([^\\[\\]]|(\\[[^\\[\\]]*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", "beginCaptures": { "1": { "name": "storage.modifier.async.tsx" @@ -1393,7 +1393,7 @@ ] }, "method-declaration-name": { - "begin": "(?x)(?=((\\b(?\\s*$)", + "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`)))+\\s*/>\\s*$)", "beginCaptures": { "1": { "name": "punctuation.definition.comment.tsx" diff --git a/extensions/typescript-basics/syntaxes/jsdoc.injection.tmLanguage.json b/extensions/typescript-basics/syntaxes/jsdoc.injection.tmLanguage.json index b7b4db2d2a5..02053ebab13 100644 --- a/extensions/typescript-basics/syntaxes/jsdoc.injection.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/jsdoc.injection.tmLanguage.json @@ -11,10 +11,13 @@ "while": "(^|\\G)\\s*\\*(?!/)(?=([^*]|[*](?!/))*$)", "patterns": [ { - "include": "text.html.markdown#fenced_code_block" + "include": "text.html.markdown#fenced_code_block_js" }, { - "include": "text.html.markdown#lists" + "include": "text.html.markdown#fenced_code_block_ts" + }, + { + "include": "text.html.markdown#fenced_code_block_unknown" }, { "include": "#example" @@ -47,4 +50,4 @@ } }, "scopeName": "documentation.injection" -} \ No newline at end of file +} diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index a4ec5f1bc0e..ffa61f231f1 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -7,7 +7,6 @@ import * as vscode from 'vscode'; import { Api, getExtensionApi } from './api'; import { registerCommands } from './commands/index'; import { LanguageConfigurationManager } from './features/languageConfiguration'; -import TypeScriptTaskProviderManager from './features/task'; import TypeScriptServiceClientHost from './typeScriptServiceClientHost'; import { flatten } from './utils/arrays'; import * as electron from './utils/electron'; @@ -21,6 +20,7 @@ import ManagedFileContextManager from './utils/managedFileContext'; import { PluginManager } from './utils/plugins'; import * as ProjectStatus from './utils/projectStatus'; import { Surveyor } from './utils/surveyor'; +import TscTaskProvider from './features/task'; export function activate( context: vscode.ExtensionContext @@ -39,7 +39,7 @@ export function activate( }); registerCommands(commandManager, lazyClientHost, pluginManager); - context.subscriptions.push(new TypeScriptTaskProviderManager(lazyClientHost.map(x => x.serviceClient))); + context.subscriptions.push(vscode.workspace.registerTaskProvider('typescript', new TscTaskProvider(lazyClientHost.map(x => x.serviceClient)))); context.subscriptions.push(new LanguageConfigurationManager()); import('./features/tsconfig').then(module => { @@ -134,4 +134,4 @@ function isSupportedDocument( export function deactivate() { rimraf.sync(electron.getInstanceDir()); -} \ No newline at end of file +} diff --git a/extensions/typescript-language-features/src/features/folding.ts b/extensions/typescript-language-features/src/features/folding.ts index a00a2db5f00..0867c86fdc7 100644 --- a/extensions/typescript-language-features/src/features/folding.ts +++ b/extensions/typescript-language-features/src/features/folding.ts @@ -55,7 +55,7 @@ class TypeScriptFoldingProvider implements vscode.FoldingRangeProvider { const start = range.start.line; // workaround for #47240 - const end = (range.end.character > 0 && document.getText(new vscode.Range(range.end.translate(0, -1), range.end)) === '}') + const end = (range.end.character > 0 && new Set(['}', ']']).has(document.getText(new vscode.Range(range.end.translate(0, -1), range.end)))) ? Math.max(range.end.line - 1, range.start.line) : range.end.line; @@ -81,4 +81,4 @@ export function register( return vscode.languages.registerFoldingRangeProvider(selector, new TypeScriptFoldingProvider(client)); }); -} \ No newline at end of file +} diff --git a/extensions/typescript-language-features/src/features/quickFix.ts b/extensions/typescript-language-features/src/features/quickFix.ts index 6ce9f199704..2585142e097 100644 --- a/extensions/typescript-language-features/src/features/quickFix.ts +++ b/extensions/typescript-language-features/src/features/quickFix.ts @@ -286,7 +286,13 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { } // Make sure there are multiple diagnostics of the same type in the file - if (!this.diagnosticsManager.getDiagnostics(document.uri).some(x => x.code === diagnostic.code && x !== diagnostic)) { + if (!this.diagnosticsManager.getDiagnostics(document.uri).some(x => { + if (x === diagnostic) { + return false; + } + return x.code === diagnostic.code + || (fixAllErrorCodes.has(x.code as number) && fixAllErrorCodes.get(x.code as number) === fixAllErrorCodes.get(diagnostic.code as number)); + })) { return results; } @@ -304,6 +310,15 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { } } +// Some fix all actions can actually fix multiple differnt diagnostics. Make sure we still show the fix all action +// in such cases +const fixAllErrorCodes = new Map([ + // Missing async + [2339, 2339], + [2345, 2339], +]); + + const preferredFixes = new Set([ 'annotateWithTypeFromJSDoc', 'constructorForDerivedNeedSuperCall', diff --git a/extensions/typescript-language-features/src/features/task.ts b/extensions/typescript-language-features/src/features/task.ts index d9fa6f783d8..64057555d5f 100644 --- a/extensions/typescript-language-features/src/features/task.ts +++ b/extensions/typescript-language-features/src/features/task.ts @@ -4,16 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; +import * as jsonc from 'jsonc-parser'; import * as path from 'path'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; -import * as jsonc from 'jsonc-parser'; import { ITypeScriptServiceClient } from '../typescriptService'; +import { isTsConfigFileName } from '../utils/languageDescription'; import { Lazy } from '../utils/lazy'; import { isImplicitProjectConfigFile } from '../utils/tsconfig'; import TsConfigProvider, { TSConfig } from '../utils/tsconfigProvider'; -import { Disposable } from '../utils/dispose'; - const localize = nls.loadMessageBundle(); @@ -36,7 +35,7 @@ interface TypeScriptTaskDefinition extends vscode.TaskDefinition { /** * Provides tasks for building `tsconfig.json` files in a project. */ -class TscTaskProvider implements vscode.TaskProvider { +export default class TscTaskProvider implements vscode.TaskProvider { private autoDetect: AutoDetect = 'on'; private readonly tsconfigProvider: TsConfigProvider; private readonly disposables: vscode.Disposable[] = []; @@ -56,7 +55,7 @@ class TscTaskProvider implements vscode.TaskProvider { public async provideTasks(token: vscode.CancellationToken): Promise { const folders = vscode.workspace.workspaceFolders; - if (!folders || !folders.length) { + if ((this.autoDetect === 'off') || !folders || !folders.length) { return []; } @@ -71,12 +70,29 @@ class TscTaskProvider implements vscode.TaskProvider { return tasks; } - public resolveTask(_task: vscode.Task): vscode.Task | undefined { + public async resolveTask(_task: vscode.Task): Promise { const definition = _task.definition; const badTsconfig = /\\tsconfig.*\.json/; if (badTsconfig.exec(definition.tsconfig) !== null) { // Warn that the task has the wrong slash type vscode.window.showWarningMessage(localize('badTsConfig', "Typescript Task in tasks.json contains \"\\\\\". Typescript tasks tsconfig must use \"/\"")); + return undefined; + } + + const typescriptTask = (_task.definition).tsconfig; + if (typescriptTask) { + if (_task.scope === undefined || _task.scope === vscode.TaskScope.Global || _task.scope === vscode.TaskScope.Workspace) { + // scope is required to be a WorkspaceFolder for resolveTask + return undefined; + } + const kind: TypeScriptTaskDefinition = (_task.definition); + const tsconfigUri: vscode.Uri = _task.scope.uri.with({ path: _task.scope.uri.path + '/' + kind.tsconfig }); + const tsconfig: TSConfig = { + path: tsconfigUri.fsPath, + posixPath: tsconfigUri.path, + workspaceFolder: _task.scope + }; + return this.getTasksForProjectAndDefinition(tsconfig, kind); } return undefined; } @@ -98,7 +114,7 @@ class TscTaskProvider implements vscode.TaskProvider { private async getTsConfigForActiveFile(token: vscode.CancellationToken): Promise { const editor = vscode.window.activeTextEditor; if (editor) { - if (path.basename(editor.document.fileName).match(/^tsconfig\.(.\.)?json$/)) { + if (isTsConfigFileName(editor.document.fileName)) { const uri = editor.document.uri; return [{ path: uri.fsPath, @@ -179,6 +195,32 @@ class TscTaskProvider implements vscode.TaskProvider { return undefined; } + private getBuildTask(workspaceFolder: vscode.WorkspaceFolder | undefined, label: string, command: string, args: string[], buildTaskidentifier: TypeScriptTaskDefinition): vscode.Task { + const buildTask = new vscode.Task( + buildTaskidentifier, + workspaceFolder || vscode.TaskScope.Workspace, + localize('buildTscLabel', 'build - {0}', label), + 'tsc', + new vscode.ShellExecution(command, args), + '$tsc'); + buildTask.group = vscode.TaskGroup.Build; + buildTask.isBackground = false; + return buildTask; + } + + private getWatchTask(workspaceFolder: vscode.WorkspaceFolder | undefined, label: string, command: string, args: string[], watchTaskidentifier: TypeScriptTaskDefinition) { + const watchTask = new vscode.Task( + watchTaskidentifier, + workspaceFolder || vscode.TaskScope.Workspace, + localize('buildAndWatchTscLabel', 'watch - {0}', label), + 'tsc', + new vscode.ShellExecution(command, [...args, '--watch']), + '$tsc-watch'); + watchTask.group = vscode.TaskGroup.Build; + watchTask.isBackground = true; + return watchTask; + } + private async getTasksForProject(project: TSConfig): Promise { const command = await TscTaskProvider.getCommand(project); const args = await this.getBuildShellArgs(project); @@ -187,36 +229,33 @@ class TscTaskProvider implements vscode.TaskProvider { const tasks: vscode.Task[] = []; if (this.autoDetect === 'build' || this.autoDetect === 'on') { - const buildTaskidentifier: TypeScriptTaskDefinition = { type: 'typescript', tsconfig: label }; - const buildTask = new vscode.Task( - buildTaskidentifier, - project.workspaceFolder || vscode.TaskScope.Workspace, - localize('buildTscLabel', 'build - {0}', label), - 'tsc', - new vscode.ShellExecution(command, args), - '$tsc'); - buildTask.group = vscode.TaskGroup.Build; - buildTask.isBackground = false; - tasks.push(buildTask); + tasks.push(this.getBuildTask(project.workspaceFolder, label, command, args, { type: 'typescript', tsconfig: label })); } if (this.autoDetect === 'watch' || this.autoDetect === 'on') { - const watchTaskidentifier: TypeScriptTaskDefinition = { type: 'typescript', tsconfig: label, option: 'watch' }; - const watchTask = new vscode.Task( - watchTaskidentifier, - project.workspaceFolder || vscode.TaskScope.Workspace, - localize('buildAndWatchTscLabel', 'watch - {0}', label), - 'tsc', - new vscode.ShellExecution(command, [...args, '--watch']), - '$tsc-watch'); - watchTask.group = vscode.TaskGroup.Build; - watchTask.isBackground = true; - tasks.push(watchTask); + + tasks.push(this.getWatchTask(project.workspaceFolder, label, command, args, { type: 'typescript', tsconfig: label, option: 'watch' })); } return tasks; } + private async getTasksForProjectAndDefinition(project: TSConfig, definition: TypeScriptTaskDefinition): Promise { + const command = await TscTaskProvider.getCommand(project); + const args = await this.getBuildShellArgs(project); + const label = this.getLabelForTasks(project); + + let task: vscode.Task | undefined; + + if (definition.option === undefined) { + task = this.getBuildTask(project.workspaceFolder, label, command, args, definition); + } else if (definition.option === 'watch') { + task = this.getWatchTask(project.workspaceFolder, label, command, args, definition); + } + + return task; + } + private getBuildShellArgs(project: TSConfig): Promise> { const defaultArgs = ['-p', project.path]; return new Promise>((resolve) => { @@ -252,37 +291,3 @@ class TscTaskProvider implements vscode.TaskProvider { this.autoDetect = typeof type === 'undefined' ? 'on' : type; } } - -/** - * Manages registrations of TypeScript task providers with VS Code. - */ -export default class TypeScriptTaskProviderManager extends Disposable { - private taskProviderSub: vscode.Disposable | undefined = undefined; - - constructor( - private readonly client: Lazy - ) { - super(); - vscode.workspace.onDidChangeConfiguration(this.onConfigurationChanged, this, this._disposables); - this.onConfigurationChanged(); - } - - dispose() { - super.dispose(); - - if (this.taskProviderSub) { - this.taskProviderSub.dispose(); - this.taskProviderSub = undefined; - } - } - - private onConfigurationChanged() { - const autoDetect = vscode.workspace.getConfiguration('typescript.tsc').get('autoDetect'); - if (this.taskProviderSub && autoDetect === 'off') { - this.taskProviderSub.dispose(); - this.taskProviderSub = undefined; - } else if (!this.taskProviderSub && autoDetect !== 'off') { - this.taskProviderSub = vscode.workspace.registerTaskProvider('typescript', new TscTaskProvider(this.client)); - } - } -} diff --git a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts index c4c2e9ca135..102658ecf01 100644 --- a/extensions/typescript-language-features/src/features/updatePathsOnRename.ts +++ b/extensions/typescript-language-features/src/features/updatePathsOnRename.ts @@ -33,14 +33,14 @@ function isDirectory(path: string): Promise { }); } -enum UpdateImportsOnFileMoveSetting { +const enum UpdateImportsOnFileMoveSetting { Prompt = 'prompt', Always = 'always', Never = 'never', } class UpdateImportsOnFileRenameHandler extends Disposable { - public static minVersion = API.v300; + public static readonly minVersion = API.v300; public constructor( private readonly client: ITypeScriptServiceClient, @@ -127,7 +127,7 @@ class UpdateImportsOnFileRenameHandler extends Disposable { newResource: vscode.Uri, newDocument: vscode.TextDocument ): Promise { - enum Choice { + const enum Choice { None = 0, Accept = 1, Reject = 2, @@ -237,4 +237,4 @@ export function register( ) { return new VersionDependentRegistration(client, UpdateImportsOnFileRenameHandler.minVersion, () => new UpdateImportsOnFileRenameHandler(client, fileConfigurationManager, handles)); -} \ No newline at end of file +} diff --git a/extensions/typescript-language-features/src/tsServer/serverError.ts b/extensions/typescript-language-features/src/tsServer/serverError.ts index cb5cb8035f1..b51ca545bff 100644 --- a/extensions/typescript-language-features/src/tsServer/serverError.ts +++ b/extensions/typescript-language-features/src/tsServer/serverError.ts @@ -40,7 +40,16 @@ export class TypeScriptServerError extends Error { if (errorText) { const errorPrefix = 'Error processing request. '; if (errorText.startsWith(errorPrefix)) { - const prefixFreeErrorText = errorText.substr(errorPrefix.length); + let prefixFreeErrorText = errorText.substr(errorPrefix.length); + + // Prior to https://github.com/microsoft/TypeScript/pull/32785, this error + // returned and excessively long and detailed list of paths. Since server-side + // filtering doesn't have sufficient granularity to drop these specific + // messages, we sanitize them here. + if (prefixFreeErrorText.indexOf('Could not find sourceFile') >= 0) { + prefixFreeErrorText = prefixFreeErrorText.replace(/ in \[[^\]]*\]/g, ''); + } + const newlineIndex = prefixFreeErrorText.indexOf('\n'); if (newlineIndex >= 0) { // Newline expected between message and stack. diff --git a/extensions/typescript-language-features/src/utils/languageDescription.ts b/extensions/typescript-language-features/src/utils/languageDescription.ts index 79f9ce9f47b..f6b066bd400 100644 --- a/extensions/typescript-language-features/src/utils/languageDescription.ts +++ b/extensions/typescript-language-features/src/utils/languageDescription.ts @@ -2,6 +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 { basename } from 'path'; import * as languageModeIds from './languageModeIds'; export const enum DiagnosticLanguage { @@ -38,3 +40,11 @@ export const standardLanguageDescriptions: LanguageDescription[] = [ configFilePattern: /^jsconfig(\..*)?\.json$/gi } ]; + +export function isTsConfigFileName(fileName: string): boolean { + return /^tsconfig\.(.+\.)?json$/i.test(basename(fileName)); +} + +export function isJsConfigOrTsConfigFileName(fileName: string): boolean { + return /^[jt]sconfig\.(.+\.)?json$/i.test(basename(fileName)); +} diff --git a/extensions/typescript-language-features/src/utils/managedFileContext.ts b/extensions/typescript-language-features/src/utils/managedFileContext.ts index 81acf9ad505..481f2482d26 100644 --- a/extensions/typescript-language-features/src/utils/managedFileContext.ts +++ b/extensions/typescript-language-features/src/utils/managedFileContext.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { isSupportedLanguageMode } from './languageModeIds'; import { Disposable } from './dispose'; +import { isJsConfigOrTsConfigFileName } from './languageDescription'; +import { isSupportedLanguageMode } from './languageModeIds'; /** * When clause context set when the current file is managed by vscode's built-in typescript extension. @@ -26,8 +27,7 @@ export default class ManagedFileContextManager extends Disposable { private onDidChangeActiveTextEditor(editor?: vscode.TextEditor): any { if (editor) { - const isManagedFile = isSupportedLanguageMode(editor.document) && this.normalizePath(editor.document.uri) !== null; - this.updateContext(isManagedFile); + this.updateContext(this.isManagedFile(editor)); } } @@ -39,4 +39,16 @@ export default class ManagedFileContextManager extends Disposable { vscode.commands.executeCommand('setContext', ManagedFileContextManager.contextName, newValue); this.isInManagedFileContext = newValue; } + + private isManagedFile(editor: vscode.TextEditor): boolean { + return this.isManagedScriptFile(editor) || this.isManagedConfigFile(editor); + } + + private isManagedScriptFile(editor: vscode.TextEditor): boolean { + return isSupportedLanguageMode(editor.document) && this.normalizePath(editor.document.uri) !== null; + } + + private isManagedConfigFile(editor: vscode.TextEditor): boolean { + return isJsConfigOrTsConfigFileName(editor.document.fileName); + } } diff --git a/extensions/vscode-api-tests/src/memfs.ts b/extensions/vscode-api-tests/src/memfs.ts new file mode 100644 index 00000000000..2b4a5f751ab --- /dev/null +++ b/extensions/vscode-api-tests/src/memfs.ts @@ -0,0 +1,229 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +import * as path from 'path'; +import * as vscode from 'vscode'; + +class File implements vscode.FileStat { + + type: vscode.FileType; + ctime: number; + mtime: number; + size: number; + + name: string; + data?: Uint8Array; + + constructor(name: string) { + this.type = vscode.FileType.File; + this.ctime = Date.now(); + this.mtime = Date.now(); + this.size = 0; + this.name = name; + } +} + +class Directory implements vscode.FileStat { + + type: vscode.FileType; + ctime: number; + mtime: number; + size: number; + + name: string; + entries: Map; + + constructor(name: string) { + this.type = vscode.FileType.Directory; + this.ctime = Date.now(); + this.mtime = Date.now(); + this.size = 0; + this.name = name; + this.entries = new Map(); + } +} + +export type Entry = File | Directory; + +export class MemFS implements vscode.FileSystemProvider { + + readonly scheme = 'fake-fs'; + + readonly root = new Directory(''); + + // --- manage file metadata + + stat(uri: vscode.Uri): vscode.FileStat { + return this._lookup(uri, false); + } + + readDirectory(uri: vscode.Uri): [string, vscode.FileType][] { + const entry = this._lookupAsDirectory(uri, false); + let result: [string, vscode.FileType][] = []; + for (const [name, child] of entry.entries) { + result.push([name, child.type]); + } + return result; + } + + // --- manage file contents + + readFile(uri: vscode.Uri): Uint8Array { + const data = this._lookupAsFile(uri, false).data; + if (data) { + return data; + } + throw vscode.FileSystemError.FileNotFound(); + } + + writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }): void { + let basename = path.posix.basename(uri.path); + let parent = this._lookupParentDirectory(uri); + let entry = parent.entries.get(basename); + if (entry instanceof Directory) { + throw vscode.FileSystemError.FileIsADirectory(uri); + } + if (!entry && !options.create) { + throw vscode.FileSystemError.FileNotFound(uri); + } + if (entry && options.create && !options.overwrite) { + throw vscode.FileSystemError.FileExists(uri); + } + if (!entry) { + entry = new File(basename); + parent.entries.set(basename, entry); + this._fireSoon({ type: vscode.FileChangeType.Created, uri }); + } + entry.mtime = Date.now(); + entry.size = content.byteLength; + entry.data = content; + + this._fireSoon({ type: vscode.FileChangeType.Changed, uri }); + } + + // --- manage files/folders + + rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean }): void { + + if (!options.overwrite && this._lookup(newUri, true)) { + throw vscode.FileSystemError.FileExists(newUri); + } + + let entry = this._lookup(oldUri, false); + let oldParent = this._lookupParentDirectory(oldUri); + + let newParent = this._lookupParentDirectory(newUri); + let newName = path.posix.basename(newUri.path); + + oldParent.entries.delete(entry.name); + entry.name = newName; + newParent.entries.set(newName, entry); + + this._fireSoon( + { type: vscode.FileChangeType.Deleted, uri: oldUri }, + { type: vscode.FileChangeType.Created, uri: newUri } + ); + } + + delete(uri: vscode.Uri): void { + let dirname = uri.with({ path: path.posix.dirname(uri.path) }); + let basename = path.posix.basename(uri.path); + let parent = this._lookupAsDirectory(dirname, false); + if (!parent.entries.has(basename)) { + throw vscode.FileSystemError.FileNotFound(uri); + } + parent.entries.delete(basename); + parent.mtime = Date.now(); + parent.size -= 1; + this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { uri, type: vscode.FileChangeType.Deleted }); + } + + createDirectory(uri: vscode.Uri): void { + let basename = path.posix.basename(uri.path); + let dirname = uri.with({ path: path.posix.dirname(uri.path) }); + let parent = this._lookupAsDirectory(dirname, false); + + let entry = new Directory(basename); + parent.entries.set(entry.name, entry); + parent.mtime = Date.now(); + parent.size += 1; + this._fireSoon({ type: vscode.FileChangeType.Changed, uri: dirname }, { type: vscode.FileChangeType.Created, uri }); + } + + // --- lookup + + private _lookup(uri: vscode.Uri, silent: false): Entry; + private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined; + private _lookup(uri: vscode.Uri, silent: boolean): Entry | undefined { + let parts = uri.path.split('/'); + let entry: Entry = this.root; + for (const part of parts) { + if (!part) { + continue; + } + let child: Entry | undefined; + if (entry instanceof Directory) { + child = entry.entries.get(part); + } + if (!child) { + if (!silent) { + throw vscode.FileSystemError.FileNotFound(uri); + } else { + return undefined; + } + } + entry = child; + } + return entry; + } + + private _lookupAsDirectory(uri: vscode.Uri, silent: boolean): Directory { + let entry = this._lookup(uri, silent); + if (entry instanceof Directory) { + return entry; + } + throw vscode.FileSystemError.FileNotADirectory(uri); + } + + private _lookupAsFile(uri: vscode.Uri, silent: boolean): File { + let entry = this._lookup(uri, silent); + if (entry instanceof File) { + return entry; + } + throw vscode.FileSystemError.FileIsADirectory(uri); + } + + private _lookupParentDirectory(uri: vscode.Uri): Directory { + const dirname = uri.with({ path: path.posix.dirname(uri.path) }); + return this._lookupAsDirectory(dirname, false); + } + + // --- manage file events + + private _emitter = new vscode.EventEmitter(); + private _bufferedEvents: vscode.FileChangeEvent[] = []; + private _fireSoonHandle?: NodeJS.Timer; + + readonly onDidChangeFile: vscode.Event = this._emitter.event; + + watch(_resource: vscode.Uri): vscode.Disposable { + // ignore, fires for all changes... + return new vscode.Disposable(() => { }); + } + + private _fireSoon(...events: vscode.FileChangeEvent[]): void { + this._bufferedEvents.push(...events); + + if (this._fireSoonHandle) { + clearTimeout(this._fireSoonHandle); + } + + this._fireSoonHandle = setTimeout(() => { + this._emitter.fire(this._bufferedEvents); + this._bufferedEvents.length = 0; + }, 5); + } +} diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts index 9ea76e6a119..7dba2cfa93c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts @@ -113,31 +113,4 @@ suite('commands namespace tests', () => { return Promise.all([a, b, c, d]); }); - - test('onDidExecuteCommand', async function () { - let args: any[]; - let d1 = commands.registerCommand('t1', function () { - args = [...arguments]; - }); - - - const p = new Promise((resolve, reject) => { - - let d2 = commands.onDidExecuteCommand(event => { - d2.dispose(); - d1.dispose(); - - try { - assert.equal(event.command, 't1'); - assert.deepEqual(args, event.arguments); - resolve(); - } catch (e) { - reject(e); - } - }); - }); - - await commands.executeCommand('t1', { foo: 1 }); - await p; - }); }); 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 81c9c2eeadf..0a828ad8d73 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { join } from 'path'; import * as vscode from 'vscode'; -import { createRandomFile } from '../utils'; +import { createRandomFile, testFs } from '../utils'; suite('languages namespace tests', () => { @@ -103,7 +103,7 @@ suite('languages namespace tests', () => { return [new vscode.DocumentLink(range, target)]; } }; - vscode.languages.registerDocumentLinkProvider({ language: 'java', scheme: 'file' }, linkProvider); + vscode.languages.registerDocumentLinkProvider({ language: 'java', scheme: testFs.scheme }, linkProvider); const links = await vscode.commands.executeCommand('vscode.executeLinkProvider', doc.uri); assert.equal(2, links && links.length); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index dc281a06fd5..0905e90acb1 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, Terminal, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget } from 'vscode'; -import { doesNotThrow, equal, ok } from 'assert'; +import { window, Pseudoterminal, EventEmitter, TerminalDimensions, workspace, ConfigurationTarget } from 'vscode'; +import { doesNotThrow, equal, ok, deepEqual } from 'assert'; suite('window namespace tests', () => { suiteSetup(async () => { @@ -81,24 +81,6 @@ suite('window namespace tests', () => { }); const terminal = window.createTerminal('b'); }); - - test('Terminal.sendText should fire Terminal.onInput', (done) => { - const reg1 = window.onDidOpenTerminal(terminal => { - reg1.dispose(); - const reg2 = renderer.onDidAcceptInput(data => { - equal(data, 'bar'); - reg2.dispose(); - const reg3 = window.onDidCloseTerminal(() => { - reg3.dispose(); - done(); - }); - terminal.dispose(); - }); - terminal.sendText('bar', false); - }); - const renderer = window.createTerminalRenderer('foo'); - }); - // test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => { // const reg1 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => { // equal(active, terminal); @@ -201,59 +183,52 @@ suite('window namespace tests', () => { }); }); - suite('Terminal renderers (deprecated)', () => { - test('should fire onDidOpenTerminal and onDidCloseTerminal from createTerminalRenderer terminal', (done) => { - const reg1 = window.onDidOpenTerminal(term => { - equal(term.name, 'c'); - reg1.dispose(); - const reg2 = window.onDidCloseTerminal(() => { + suite('window.onDidWriteTerminalData', () => { + test('should listen to all future terminal data events', (done) => { + const openEvents: string[] = []; + const dataEvents: { name: string, data: string }[] = []; + const closeEvents: string[] = []; + const reg1 = window.onDidOpenTerminal(e => openEvents.push(e.name)); + const reg2 = window.onDidWriteTerminalData(e => dataEvents.push({ name: e.terminal.name, data: e.data })); + const reg3 = window.onDidCloseTerminal(e => { + closeEvents.push(e.name); + if (closeEvents.length === 2) { + deepEqual(openEvents, [ 'test1', 'test2' ]); + deepEqual(dataEvents, [ { name: 'test1', data: 'write1' }, { name: 'test2', data: 'write2' } ]); + deepEqual(closeEvents, [ 'test1', 'test2' ]); + reg1.dispose(); reg2.dispose(); - done(); - }); - term.dispose(); - }); - window.createTerminalRenderer('c'); - }); - - test('should get maximum dimensions set when shown', (done) => { - let terminal: Terminal; - const reg1 = window.onDidOpenTerminal(term => { - reg1.dispose(); - term.show(); - terminal = term; - }); - const renderer = window.createTerminalRenderer('foo'); - const reg2 = renderer.onDidChangeMaximumDimensions(dimensions => { - ok(dimensions.columns > 0); - ok(dimensions.rows > 0); - reg2.dispose(); - const reg3 = window.onDidCloseTerminal(() => { reg3.dispose(); done(); - }); - terminal.dispose(); + } }); - }); - test('should fire Terminal.onData on write', (done) => { - const reg1 = window.onDidOpenTerminal(terminal => { - reg1.dispose(); - const reg2 = terminal.onDidWriteData(data => { - equal(data, 'bar'); - reg2.dispose(); - const reg3 = window.onDidCloseTerminal(() => { - reg3.dispose(); - done(); - }); - terminal.dispose(); - }); - renderer.write('bar'); - }); - const renderer = window.createTerminalRenderer('foo'); + const term1Write = new EventEmitter(); + const term1Close = new EventEmitter(); + window.createTerminal({ name: 'test1', pty: { + onDidWrite: term1Write.event, + onDidClose: term1Close.event, + open: () => { + term1Write.fire('write1'); + term1Close.fire(); + const term2Write = new EventEmitter(); + const term2Close = new EventEmitter(); + window.createTerminal({ name: 'test2', pty: { + onDidWrite: term2Write.event, + onDidClose: term2Close.event, + open: () => { + term2Write.fire('write2'); + term2Close.fire(); + }, + close: () => {} + }}); + }, + close: () => {} + }}); }); }); - suite('Virtual process terminals', () => { + suite('Extension pty terminals', () => { test('should fire onDidOpenTerminal and onDidCloseTerminal', (done) => { const reg1 = window.onDidOpenTerminal(term => { equal(term.name, 'c'); @@ -299,26 +274,52 @@ suite('window namespace tests', () => { const terminal = window.createTerminal({ name: 'foo', pty }); }); - test('should fire provide dimensions on start as the terminal has been shown', (done) => { - const reg1 = window.onDidOpenTerminal(term => { - equal(terminal, term); - reg1.dispose(); - }); - const pty: Pseudoterminal = { - onDidWrite: new EventEmitter().event, - open: (dimensions) => { - ok(dimensions!.columns > 0); - ok(dimensions!.rows > 0); - const reg3 = window.onDidCloseTerminal(() => { - reg3.dispose(); - done(); - }); - terminal.dispose(); - }, - close: () => {} - }; - const terminal = window.createTerminal({ name: 'foo', pty }); - }); + // The below tests depend on global UI state and each other + // test('should not provide dimensions on start as the terminal has not been shown yet', (done) => { + // const reg1 = window.onDidOpenTerminal(term => { + // equal(terminal, term); + // reg1.dispose(); + // }); + // const pty: Pseudoterminal = { + // onDidWrite: new EventEmitter().event, + // open: (dimensions) => { + // equal(dimensions, undefined); + // const reg3 = window.onDidCloseTerminal(() => { + // reg3.dispose(); + // done(); + // }); + // // Show a terminal and wait a brief period before dispose, this will cause + // // the panel to init it's dimenisons and be provided to following terminals. + // // The following test depends on this. + // terminal.show(); + // setTimeout(() => terminal.dispose(), 200); + // }, + // close: () => {} + // }; + // const terminal = window.createTerminal({ name: 'foo', pty }); + // }); + // test('should provide dimensions on start as the terminal has been shown', (done) => { + // const reg1 = window.onDidOpenTerminal(term => { + // equal(terminal, term); + // reg1.dispose(); + // }); + // const pty: Pseudoterminal = { + // onDidWrite: new EventEmitter().event, + // open: (dimensions) => { + // // This test depends on Terminal.show being called some time before such + // // that the panel dimensions are initialized and cached. + // ok(dimensions!.columns > 0); + // ok(dimensions!.rows > 0); + // const reg3 = window.onDidCloseTerminal(() => { + // reg3.dispose(); + // done(); + // }); + // terminal.dispose(); + // }, + // close: () => {} + // }; + // const terminal = window.createTerminal({ name: 'foo', pty }); + // }); test('should respect dimension overrides', (done) => { const reg1 = window.onDidOpenTerminal(term => { @@ -336,14 +337,15 @@ suite('window namespace tests', () => { }); terminal.dispose(); }); - overrideDimensionsEmitter.fire({ columns: 10, rows: 5 }); }); const writeEmitter = new EventEmitter(); const overrideDimensionsEmitter = new EventEmitter(); const pty: Pseudoterminal = { onDidWrite: writeEmitter.event, onDidOverrideDimensions: overrideDimensionsEmitter.event, - open: () => {}, + open: () => { + overrideDimensionsEmitter.fire({ columns: 10, rows: 5 }); + }, close: () => {} }; const terminal = window.createTerminal({ name: 'foo', pty }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index 4be0218ab64..e785f1d4afb 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -251,18 +251,18 @@ suite('Webview tests', () => { }); `); - async function toWebviewResource(path: string) { - const root = await webview.webview.toWebviewResource(vscode.Uri.file(vscode.workspace.rootPath!)); + async function asWebviewUri(path: string) { + const root = await webview.webview.asWebviewUri(vscode.Uri.file(vscode.workspace.rootPath!)); return root.toString() + path; } { - const imagePath = await toWebviewResource('/image.png'); + const imagePath = await asWebviewUri('/image.png'); const response = sendRecieveMessage(webview, { src: imagePath }); assert.strictEqual((await response).value, true); } { - const imagePath = await toWebviewResource('/no-such-image.png'); + const imagePath = await asWebviewUri('/no-such-image.png'); const response = sendRecieveMessage(webview, { src: imagePath }); assert.strictEqual((await response).value, false); } 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 898a41247c3..738e1406834 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -5,10 +5,9 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, disposeAll } from '../utils'; +import { createRandomFile, deleteFile, closeAllEditors, pathEquals, rndName, disposeAll, testFs, delay } from '../utils'; import { join, posix, basename } from 'path'; import * as fs from 'fs'; -import * as os from 'os'; suite('workspace-namespace', () => { @@ -131,8 +130,7 @@ suite('workspace-namespace', () => { assert.ok(fs.existsSync(path)); d0.dispose(); - - return deleteFile(vscode.Uri.file(join(vscode.workspace.rootPath || '', './newfile.txt'))); + fs.unlinkSync(join(vscode.workspace.rootPath || '', './newfile.txt')); }); }); @@ -589,13 +587,15 @@ suite('workspace-namespace', () => { }, cancellation.token); }); - test('applyEdit', () => { + test('applyEdit', async () => { + const doc = await vscode.workspace.openTextDocument(vscode.Uri.parse('untitled:' + join(vscode.workspace.rootPath || '', './new2.txt'))); - return vscode.workspace.openTextDocument(vscode.Uri.parse('untitled:' + join(vscode.workspace.rootPath || '', './new2.txt'))).then(doc => { - let edit = new vscode.WorkspaceEdit(); - edit.insert(doc.uri, new vscode.Position(0, 0), new Array(1000).join('Hello World')); - return vscode.workspace.applyEdit(edit); - }); + let edit = new vscode.WorkspaceEdit(); + edit.insert(doc.uri, new vscode.Position(0, 0), new Array(1000).join('Hello World')); + + let success = await vscode.workspace.applyEdit(edit); + assert.equal(success, true); + assert.equal(doc.isDirty, true); }); test('applyEdit should fail when editing deleted resource', async () => { @@ -632,19 +632,31 @@ suite('workspace-namespace', () => { }); test('applyEdit "edit A -> rename A to B -> edit B"', async () => { + await testEditRenameEdit(oldUri => oldUri.with({ path: oldUri.path + 'NEW' })); + }); + + test('applyEdit "edit A -> rename A to B (different case)" -> edit B', async () => { + await testEditRenameEdit(oldUri => oldUri.with({ path: oldUri.path.toUpperCase() })); + }); + + test('applyEdit "edit A -> rename A to B (same case)" -> edit B', async () => { + await testEditRenameEdit(oldUri => oldUri); + }); + + async function testEditRenameEdit(newUriCreator: (oldUri: vscode.Uri) => vscode.Uri): Promise { const oldUri = await createRandomFile(); - const newUri = oldUri.with({ path: oldUri.path + 'NEW' }); + const newUri = newUriCreator(oldUri); const edit = new vscode.WorkspaceEdit(); edit.insert(oldUri, new vscode.Position(0, 0), 'BEFORE'); edit.renameFile(oldUri, newUri); edit.insert(newUri, new vscode.Position(0, 0), 'AFTER'); - let success = await vscode.workspace.applyEdit(edit); - assert.equal(success, true); + assert.ok(await vscode.workspace.applyEdit(edit)); let doc = await vscode.workspace.openTextDocument(newUri); assert.equal(doc.getText(), 'AFTERBEFORE'); - }); + assert.equal(doc.isDirty, true); + } function nameWithUnderscore(uri: vscode.Uri) { return uri.with({ path: posix.join(posix.dirname(uri.path), `_${posix.basename(uri.path)}`) }); @@ -702,10 +714,8 @@ suite('workspace-namespace', () => { test('WorkspaceEdit: edit and rename parent folder duplicates resource #42641', async function () { - let dir = join(os.tmpdir(), 'before-' + rndName()); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } + let dir = vscode.Uri.parse(`${testFs.scheme}:/before-${rndName()}`); + await testFs.createDirectory(dir); let docUri = await createRandomFile('', dir); let docParent = docUri.with({ path: posix.dirname(docUri.path) }); @@ -811,7 +821,7 @@ suite('workspace-namespace', () => { assert.ok(await vscode.workspace.applyEdit(we)); }); - test('The api workspace.applyEdit drops the TextEdit if there is a RenameFile later #77735', async function () { + test('WorkspaceEdit: insert & rename multiple', async function () { let [f1, f2, f3] = await Promise.all([createRandomFile(), createRandomFile(), createRandomFile()]); @@ -835,4 +845,56 @@ suite('workspace-namespace', () => { assert.ok(true); } }); + + test('workspace.applyEdit drops the TextEdit if there is a RenameFile later #77735 (with opened editor)', async function () { + await test77735(true); + }); + + test('workspace.applyEdit drops the TextEdit if there is a RenameFile later #77735 (without opened editor)', async function () { + await test77735(false); + }); + + async function test77735(withOpenedEditor: boolean): Promise { + const docUriOriginal = await createRandomFile(); + const docUriMoved = docUriOriginal.with({ path: `${docUriOriginal.path}.moved` }); + + if (withOpenedEditor) { + const document = await vscode.workspace.openTextDocument(docUriOriginal); + await vscode.window.showTextDocument(document); + } else { + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + } + + for (let i = 0; i < 4; i++) { + let we = new vscode.WorkspaceEdit(); + let oldUri: vscode.Uri; + let newUri: vscode.Uri; + let expected: string; + + if (i % 2 === 0) { + oldUri = docUriOriginal; + newUri = docUriMoved; + we.insert(oldUri, new vscode.Position(0, 0), 'Hello'); + expected = 'Hello'; + } else { + oldUri = docUriMoved; + newUri = docUriOriginal; + we.delete(oldUri, new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 5))); + expected = ''; + } + + we.renameFile(oldUri, newUri); + assert.ok(await vscode.workspace.applyEdit(we)); + + const document = await vscode.workspace.openTextDocument(newUri); + assert.equal(document.isDirty, true); + + await document.save(); + assert.equal(document.isDirty, false); + + assert.equal(document.getText(), expected); + + await delay(10); + } + } }); diff --git a/extensions/vscode-api-tests/src/utils.ts b/extensions/vscode-api-tests/src/utils.ts index d9fda3bb76d..969a7cd0051 100644 --- a/extensions/vscode-api-tests/src/utils.ts +++ b/extensions/vscode-api-tests/src/utils.ts @@ -4,25 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as fs from 'fs'; -import * as os from 'os'; -import { join } from 'path'; +import { MemFS } from './memfs'; +import * as assert from 'assert'; export function rndName() { return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10); } -export function createRandomFile(contents = '', dir: string = os.tmpdir(), ext = ''): Thenable { - return new Promise((resolve, reject) => { - const tmpFile = join(dir, rndName() + ext); - fs.writeFile(tmpFile, contents, (error) => { - if (error) { - return reject(error); - } +export const testFs = new MemFS(); +vscode.workspace.registerFileSystemProvider(testFs.scheme, testFs); - resolve(vscode.Uri.file(tmpFile)); - }); - }); +export async function createRandomFile(contents = '', dir: vscode.Uri | undefined = undefined, ext = ''): Promise { + let fakeFile: vscode.Uri; + if (dir) { + assert.equal(dir.scheme, testFs.scheme); + fakeFile = dir.with({ path: dir.path + '/' + rndName() + ext }); + } else { + fakeFile = vscode.Uri.parse(`${testFs.scheme}:/${rndName() + ext}`); + } + await testFs.writeFile(fakeFile, Buffer.from(contents), { create: true, overwrite: true }); + return fakeFile; +} + +export async function deleteFile(file: vscode.Uri): Promise { + try { + await testFs.delete(file); + return true; + } catch { + return false; + } } export function pathEquals(path1: string, path2: string): boolean { @@ -34,30 +44,13 @@ export function pathEquals(path1: string, path2: string): boolean { return path1 === path2; } -export function deleteFile(file: vscode.Uri): Thenable { - return new Promise((resolve, reject) => { - fs.unlink(file.fsPath, (err) => { - if (err) { - reject(err); - } else { - resolve(true); - } - }); - }); -} - export function closeAllEditors(): Thenable { return vscode.commands.executeCommand('workbench.action.closeAllEditors'); } export function disposeAll(disposables: vscode.Disposable[]) { - while (disposables.length) { - let item = disposables.pop(); - if (item) { - item.dispose(); - } - } + vscode.Disposable.from(...disposables).dispose(); } export function conditionalTest(name: string, testCallback: (done: MochaDone) => void | Thenable) { @@ -73,4 +66,8 @@ export function conditionalTest(name: string, testCallback: (done: MochaDone) => function isTestTypeActive(): boolean { return !!vscode.extensions.getExtension('vscode-resolver-test'); -} \ No newline at end of file +} + +export function delay(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 8e552194c13..26979cdf9d6 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.2.tgz#a09e1dc69bc9551cadf17dba10ee42cf55e5d56c" - integrity sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA== +typescript@3.6.1-rc: + version "3.6.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.1-rc.tgz#9db650b25d8ef033d9e25b3057bdd1e102bb434b" + integrity sha512-u6AQN9AoocZKYSz8zcc1Qh/V/mbAO+BHc73fTiKlIdjzU60A8TesrK9/7kg3GM8o2RxNyCeOFpcevEtnfUyaLg== diff --git a/package.json b/package.json index e24f6bfec46..559c11a1763 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.37.0", - "distro": "11ab6b1c5be090591adc26a81f72a16908697165", + "version": "1.38.0", + "distro": "2f7403275ca8931a7265b58ac35e33096ce0d074", "author": { "name": "Microsoft Corporation" }, @@ -28,6 +28,7 @@ "update-distro": "node build/npm/update-distro.js" }, "dependencies": { + "@microsoft/applicationinsights-web": "^2.1.1", "applicationinsights": "1.0.8", "graceful-fs": "4.1.11", "http-proxy-agent": "^2.1.0", @@ -35,8 +36,7 @@ "iconv-lite": "0.5.0", "jschardet": "1.6.0", "keytar": "^4.11.0", - "minimist": "1.2.0", - "native-is-elevated": "^0.2.1", + "native-is-elevated": "0.3.0", "native-keymap": "2.0.0", "native-watchdog": "1.0.0", "node-pty": "0.9.0-beta19", @@ -47,12 +47,13 @@ "sudo-prompt": "9.0.0", "v8-inspect-profiler": "^0.0.20", "vscode-chokidar": "2.1.7", + "vscode-minimist": "^1.2.1", "vscode-proxy-agent": "0.4.0", - "vscode-ripgrep": "^1.5.5", + "vscode-ripgrep": "^1.5.6", "vscode-sqlite3": "4.0.8", "vscode-textmate": "^4.2.2", - "xterm": "3.15.0-beta89", - "xterm-addon-search": "0.2.0-beta3", + "xterm": "3.15.0-beta99", + "xterm-addon-search": "0.2.0-beta5", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", "yazl": "^2.4.3" @@ -60,7 +61,6 @@ "devDependencies": { "7zip": "0.0.6", "@types/keytar": "^4.4.0", - "@types/minimist": "^1.2.0", "@types/mocha": "2.2.39", "@types/node": "^10.12.12", "@types/semver": "^5.5.0", @@ -83,7 +83,7 @@ "fast-plist": "0.1.2", "glob": "^5.0.13", "gulp": "^4.0.0", - "gulp-atom-electron": "^1.21.1", + "gulp-atom-electron": "^1.22.0", "gulp-azure-storage": "^0.10.0", "gulp-buffer": "0.0.2", "gulp-concat": "^2.6.1", @@ -124,6 +124,7 @@ "optimist": "0.3.5", "p-all": "^1.0.0", "pump": "^1.0.1", + "puppeteer": "^1.19.0", "queue": "3.0.6", "rcedit": "^1.1.0", "rimraf": "^2.2.8", @@ -138,7 +139,7 @@ "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", "vsce": "1.48.0", - "vscode-debugprotocol": "1.35.0", + "vscode-debugprotocol": "1.36.0-pre.0", "vscode-nls-dev": "^3.3.1", "webpack": "^4.16.5", "webpack-cli": "^3.1.0", @@ -153,7 +154,7 @@ }, "optionalDependencies": { "vscode-windows-ca-certs": "0.1.0", - "vscode-windows-registry": "1.0.1", + "vscode-windows-registry": "1.0.2", "windows-foreground-love": "0.2.0", "windows-mutex": "0.3.0", "windows-process-tree": "0.2.4" diff --git a/remote/package.json b/remote/package.json index 34ffee1a4be..2e8ceed0809 100644 --- a/remote/package.json +++ b/remote/package.json @@ -2,6 +2,7 @@ "name": "vscode-reh", "version": "0.0.0", "dependencies": { + "@microsoft/applicationinsights-web": "^2.1.1", "applicationinsights": "1.0.8", "getmac": "1.4.1", "graceful-fs": "4.1.11", @@ -9,7 +10,6 @@ "https-proxy-agent": "^2.2.1", "iconv-lite": "0.5.0", "jschardet": "1.6.0", - "minimist": "1.2.0", "native-watchdog": "1.0.0", "node-pty": "0.9.0-beta19", "nsfw": "1.2.5", @@ -17,17 +17,18 @@ "semver-umd": "^5.5.3", "spdlog": "^0.9.0", "vscode-chokidar": "2.1.7", + "vscode-minimist": "^1.2.1", "vscode-proxy-agent": "0.4.0", - "vscode-ripgrep": "^1.5.5", + "vscode-ripgrep": "^1.5.6", "vscode-textmate": "^4.2.2", - "xterm": "3.15.0-beta89", - "xterm-addon-search": "0.2.0-beta3", + "xterm": "3.15.0-beta99", + "xterm-addon-search": "0.2.0-beta5", "xterm-addon-web-links": "0.1.0-beta10", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, "optionalDependencies": { "vscode-windows-ca-certs": "0.1.0", - "vscode-windows-registry": "1.0.1" + "vscode-windows-registry": "1.0.2" } } diff --git a/remote/web/.yarnrc b/remote/web/.yarnrc deleted file mode 100644 index b28191e6bae..00000000000 --- a/remote/web/.yarnrc +++ /dev/null @@ -1,3 +0,0 @@ -disturl "http://nodejs.org/dist" -target "10.11.0" -runtime "node" diff --git a/remote/web/package.json b/remote/web/package.json index c7a487b6c2d..f54afe1a3e6 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -2,6 +2,7 @@ "name": "vscode-web", "version": "0.0.0", "dependencies": { + "@microsoft/applicationinsights-web": "^2.1.1", "onigasm-umd": "^2.2.2", "vscode-textmate": "^4.1.1", "xterm": "3.15.0-beta67", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index b624eb37290..23ad784a554 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -2,6 +2,69 @@ # yarn lockfile v1 +"@microsoft/applicationinsights-analytics-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.1.1.tgz#6d09c1915f808026e2d45165d04802f09affed59" + integrity sha512-VKIutoFKY99CyKwxLUuj6Vnq14/QwXo9/QSQDpYnHEjo+uKn7QmLsHqWw0K9uYNfNAXt4BZimX/zDg6jZtzeXg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-channel-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.1.1.tgz#e205eddd93e49d17d9e0711a612b4bfc9810888f" + integrity sha512-fYr9IAqtaEr9AmaPaL3SLQVT3t3GQzl+n74gpNKyAVakDIm0nYQ/bimjdcAhJMDf1VGNSPg/xICneyuZg7Wxlg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-common@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.1.1.tgz#27e6074584a7a3a8ca3f11f7ff2b7ff0f395bf2d" + integrity sha512-2hkS1Ia1FmAjCuYZ5JlG20/WgObqdsKtmK5YALAFGHIB4KSQ/Za1qazS+7GsG+E0F9UJivNWL1geUIcNqg5Qjg== + dependencies: + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-core-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.1.1.tgz#30fb6a519cc1c6119c419c4811ce72c260217d9e" + integrity sha512-4t4wf6SKqIcWEQDPg/uOhm+BxtHhu/AFreyEoYZmMfcxzAu33h1FtTQRtxBNbYH1+thiNZCh80yUpnT7d9Hrlw== + dependencies: + tslib "^1.9.3" + +"@microsoft/applicationinsights-dependencies-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.1.1.tgz#8154c3efcb24617d015d0bce7c2cc47797a8d3c4" + integrity sha512-yhb4EToBp+aI+qLo0h5NDNtoo3sDFV60uyIOK843YjzXqVotcXX/lRShlghTkJtYH09QhrdzDjViUHnD4sMFSQ== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-properties-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.1.1.tgz#ca34232766eb16167b5d87693e2ae5d94f2a1559" + integrity sha512-8l+/ppw6xKTam2RL4EHZ52Lcf217olw81j6kyBNKtIcGwSnLNHrFwEeF3vBWIteG2JKzlg1GhGjrkB3oxXsV2g== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-web@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.1.1.tgz#1a44eddda7c244b88d9eb052dab6c855682e4f05" + integrity sha512-crvhCkNsNxkFuPWmttyWNSAA96D5FxBtKS6UA9MV9f9XHevTfchf/E3AuU9JZcsXufWMQLwLrUQ9ZiA1QJ0EWA== + dependencies: + "@microsoft/applicationinsights-analytics-js" "2.1.1" + "@microsoft/applicationinsights-channel-js" "2.1.1" + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + "@microsoft/applicationinsights-dependencies-js" "2.1.1" + "@microsoft/applicationinsights-properties-js" "2.1.1" + nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -24,6 +87,11 @@ semver-umd@^5.5.3: resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.3.tgz#b64d7a2d4f5a717b369d56e31940a38e47e34d1e" integrity sha512-HOnQrn2iKnVe/xlqCTzMXQdvSz3rPbD0DmQXYuQ+oK1dpptGFfPghonQrx5JHl2O7EJwDqtQnjhE7ME23q6ngw== +tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + vscode-textmate@^4.1.1: version "4.2.2" resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.2.2.tgz#0b4dabc69a6fba79a065cb6b615f66eac07c8f4c" diff --git a/remote/yarn.lock b/remote/yarn.lock index 1c4e700f495..dca0e2501f0 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -2,6 +2,69 @@ # yarn lockfile v1 +"@microsoft/applicationinsights-analytics-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.1.1.tgz#6d09c1915f808026e2d45165d04802f09affed59" + integrity sha512-VKIutoFKY99CyKwxLUuj6Vnq14/QwXo9/QSQDpYnHEjo+uKn7QmLsHqWw0K9uYNfNAXt4BZimX/zDg6jZtzeXg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-channel-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.1.1.tgz#e205eddd93e49d17d9e0711a612b4bfc9810888f" + integrity sha512-fYr9IAqtaEr9AmaPaL3SLQVT3t3GQzl+n74gpNKyAVakDIm0nYQ/bimjdcAhJMDf1VGNSPg/xICneyuZg7Wxlg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-common@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.1.1.tgz#27e6074584a7a3a8ca3f11f7ff2b7ff0f395bf2d" + integrity sha512-2hkS1Ia1FmAjCuYZ5JlG20/WgObqdsKtmK5YALAFGHIB4KSQ/Za1qazS+7GsG+E0F9UJivNWL1geUIcNqg5Qjg== + dependencies: + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-core-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.1.1.tgz#30fb6a519cc1c6119c419c4811ce72c260217d9e" + integrity sha512-4t4wf6SKqIcWEQDPg/uOhm+BxtHhu/AFreyEoYZmMfcxzAu33h1FtTQRtxBNbYH1+thiNZCh80yUpnT7d9Hrlw== + dependencies: + tslib "^1.9.3" + +"@microsoft/applicationinsights-dependencies-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.1.1.tgz#8154c3efcb24617d015d0bce7c2cc47797a8d3c4" + integrity sha512-yhb4EToBp+aI+qLo0h5NDNtoo3sDFV60uyIOK843YjzXqVotcXX/lRShlghTkJtYH09QhrdzDjViUHnD4sMFSQ== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-properties-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.1.1.tgz#ca34232766eb16167b5d87693e2ae5d94f2a1559" + integrity sha512-8l+/ppw6xKTam2RL4EHZ52Lcf217olw81j6kyBNKtIcGwSnLNHrFwEeF3vBWIteG2JKzlg1GhGjrkB3oxXsV2g== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-web@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.1.1.tgz#1a44eddda7c244b88d9eb052dab6c855682e4f05" + integrity sha512-crvhCkNsNxkFuPWmttyWNSAA96D5FxBtKS6UA9MV9f9XHevTfchf/E3AuU9JZcsXufWMQLwLrUQ9ZiA1QJ0EWA== + dependencies: + "@microsoft/applicationinsights-analytics-js" "2.1.1" + "@microsoft/applicationinsights-channel-js" "2.1.1" + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + "@microsoft/applicationinsights-dependencies-js" "2.1.1" + "@microsoft/applicationinsights-properties-js" "2.1.1" + agent-base@4, agent-base@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" @@ -660,11 +723,6 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -685,7 +743,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -nan@^2.0.0, nan@^2.12.1, nan@^2.13.2, nan@^2.14.0: +nan@^2.0.0, nan@^2.13.2, nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -1029,6 +1087,11 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + typechecker@^4.3.0: version "4.7.0" resolved "https://registry.yarnpkg.com/typechecker/-/typechecker-4.7.0.tgz#5249f427358f45b7250c4924fd4d01ed9ba435e9" @@ -1113,6 +1176,11 @@ vscode-fsevents@1.2.12: dependencies: nan "^2.14.0" +vscode-minimist@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a" + integrity sha512-cmB72+qDoiCFJ1UKnGUBdGYfXzdpJ3bQM/D/+XhkVk5v7uZgLbYiCz5JcwVyk7NC7hSi5VGtQ4wihzmi12NeXw== + vscode-proxy-agent@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.4.0.tgz#574833e65405c6333f350f1b9fef9909deccb6b5" @@ -1123,10 +1191,10 @@ vscode-proxy-agent@0.4.0: https-proxy-agent "2.2.1" socks-proxy-agent "4.0.1" -vscode-ripgrep@^1.5.5: - version "1.5.5" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.5.tgz#24c0e9cb356cf889c98e15ecb58f9cf654a1d961" - integrity sha512-OrPrAmcun4+uZAuNcQvE6CCPskh+5AsjANod/Q3zRcJcGNxgoOSGlQN9RPtatkUNmkN8Nn8mZBnS1jMylu/dKg== +vscode-ripgrep@^1.5.6: + version "1.5.6" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.6.tgz#93bf5c99ca5f8248950a305e224f6ca153c30af4" + integrity sha512-WRIM9XpUj6dsfdAmuI3ANbmT1ysPUVsYy/2uCLDHJa9kbiB4T7uGvFnnc0Rgx2qQnyRAwL7PeWaFgUljPPxf2g== vscode-textmate@^4.2.2: version "4.2.2" @@ -1142,27 +1210,25 @@ vscode-windows-ca-certs@0.1.0: dependencies: node-addon-api "1.6.2" -vscode-windows-registry@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.1.tgz#bc9f765563eb6dc1c9ad9a41f9eaacc84dfadc7c" - integrity sha512-q0aKXi9Py1OBdmXIJJFeJBzpPJMMUxMJNBU9FysWIXEwJyMQGEVevKzM2J3Qz/cHSc5LVqibmoUWzZ7g+97qRg== - dependencies: - nan "^2.12.1" +vscode-windows-registry@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.2.tgz#b863e704a6a69c50b3098a55fbddbe595b0c124a" + integrity sha512-/CLLvuOSM2Vme2z6aNyB+4Omd7hDxpf4Thrt8ImxnXeQtxzel2bClJpFQvQqK/s4oaXlkBKS7LqVLeZM+uSVIA== -xterm-addon-search@0.2.0-beta3: - version "0.2.0-beta3" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0-beta3.tgz#710ce14658e269c5a4791f5a9e2f520883a2e62b" - integrity sha512-KzVdkEtGbKJe9ER2TmrI7XjF/wUq1lir9U63vPJi0t2ymQvIECl1V63f9QtOp1vvpdhbZiXBxO+vGTj+y0tRow== +xterm-addon-search@0.2.0-beta5: + version "0.2.0-beta5" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0-beta5.tgz#258d7cb1511d9060cd4520f0f82e408000fd4f53" + integrity sha512-Tg+d8scch0rYOVmzBbX35Y1GXtq+eu/YlzbXznmTo/yD83j3BQlXOhgECu/Yv8EX5JwFmzbfVRWC+JWnfigwGg== xterm-addon-web-links@0.1.0-beta10: version "0.1.0-beta10" resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta89: - version "3.15.0-beta89" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta89.tgz#255962e2595deefb42b8c0043001256526163a3f" - integrity sha512-rNaoUamacPRg+ejbKDGRDNqR3SZ3Uf/pUW0mO+FF25/lIgdLq8x7RgZVBgFweCZ/dijPjxoyMcgfNDTH9h8LOg== +xterm@3.15.0-beta99: + version "3.15.0-beta99" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta99.tgz#0010a7ea5d56cbb08a1e3a525b353c96a158e7a0" + integrity sha512-Vm0ZWToWwO4uk/28Kqvqt9L92h5EU2z4WR9I6xcQaPIBmkJPINIARU4LWQnvaOfgFhRbpwBMveTfh8/jM97lPg== yauzl@^2.9.2: version "2.10.0" diff --git a/resources/completions/bash/code b/resources/completions/bash/code index f40963db05c..c9e4b167309 100644 --- a/resources/completions/bash/code +++ b/resources/completions/bash/code @@ -1,4 +1,4 @@ -_code() +_@@APPNAME@@() { local cur prev words cword split _init_completion -s || return @@ -58,4 +58,4 @@ _code() _filedir } && -complete -F _code code +complete -F _@@APPNAME@@ @@APPNAME@@ diff --git a/resources/completions/zsh/_code b/resources/completions/zsh/_code index 265c199f62f..c7abee1bc58 100644 --- a/resources/completions/zsh/_code +++ b/resources/completions/zsh/_code @@ -1,4 +1,4 @@ -#compdef code +#compdef @@APPNAME@@ local arguments @@ -14,10 +14,10 @@ arguments=( '--user-data-dir[specify the directory that user data is kept in]:directory:_directories' '(- *)'{-v,--version}'[print version]' '(- *)'{-h,--help}'[print usage]' - '(- *)'{--telemetry}'[Shows all telemetry events which VS code collects.]' + '--telemetry[show all telemetry events which VS code collects]' '--extensions-dir[set the root path for extensions]:root path:_directories' '--list-extensions[list the installed extensions]' - '--category[filters instaled extension list by category, when using --list-extension]' + '--category[filters installed extension list by category, when using --list-extension]' '--show-versions[show versions of installed extensions, when using --list-extension]' '--install-extension[install an extension]:id or path:_files -g "*.vsix(-.)"' '--uninstall-extension[uninstall an extension]:id or path:_files -g "*.vsix(-.)"' diff --git a/resources/linux/rpm/code.spec.template b/resources/linux/rpm/code.spec.template index c942d65a745..93e20cd4825 100644 --- a/resources/linux/rpm/code.spec.template +++ b/resources/linux/rpm/code.spec.template @@ -18,14 +18,14 @@ Visual Studio Code is a new choice of tool that combines the simplicity of a cod mkdir -p %{buildroot}/usr/share/@@NAME@@ mkdir -p %{buildroot}/usr/share/applications mkdir -p %{buildroot}/usr/share/pixmaps -#mkdir -p %{buildroot}/usr/share/bash-completion/completions -#mkdir -p %{buildroot}/usr/share/zsh/site-functions +mkdir -p %{buildroot}/usr/share/bash-completion/completions +mkdir -p %{buildroot}/usr/share/zsh/site-functions cp -r usr/share/@@NAME@@/* %{buildroot}/usr/share/@@NAME@@ cp -r usr/share/applications/@@NAME@@.desktop %{buildroot}/usr/share/applications cp -r usr/share/applications/@@NAME@@-url-handler.desktop %{buildroot}/usr/share/applications cp -r usr/share/pixmaps/@@ICON@@.png %{buildroot}/usr/share/pixmaps -#cp usr/share/bash-completion/completions/code %{buildroot}/usr/share/bash-completion/completions/code -#cp usr/share/zsh/site-functions/_code %{buildroot}/usr/share/zsh/site-functions/_code +cp usr/share/bash-completion/completions/@@NAME@@ %{buildroot}/usr/share/bash-completion/completions/@@NAME@@ +cp usr/share/zsh/site-functions/_@@NAME@@ %{buildroot}/usr/share/zsh/site-functions/_@@NAME@@ %post # Remove the legacy bin command if this is the stable build @@ -58,5 +58,5 @@ fi /usr/share/applications/@@NAME@@.desktop /usr/share/applications/@@NAME@@-url-handler.desktop /usr/share/pixmaps/@@ICON@@.png -#/usr/share/bash-completion/completions/code -#/usr/share/zsh/site-functions/_code +/usr/share/bash-completion/completions/@@NAME@@ +/usr/share/zsh/site-functions/_@@NAME@@ diff --git a/resources/win32/inno-big-100.bmp b/resources/win32/inno-big-100.bmp new file mode 100644 index 00000000000..99cf4ba6668 Binary files /dev/null and b/resources/win32/inno-big-100.bmp differ diff --git a/resources/win32/inno-big.bmp b/resources/win32/inno-big-125.bmp old mode 100644 new mode 100755 similarity index 67% rename from resources/win32/inno-big.bmp rename to resources/win32/inno-big-125.bmp index d9aa14eb708..d781943ada5 Binary files a/resources/win32/inno-big.bmp and b/resources/win32/inno-big-125.bmp differ diff --git a/resources/win32/inno-big-150.bmp b/resources/win32/inno-big-150.bmp new file mode 100755 index 00000000000..554461982f0 Binary files /dev/null and b/resources/win32/inno-big-150.bmp differ diff --git a/resources/win32/inno-big-175.bmp b/resources/win32/inno-big-175.bmp new file mode 100755 index 00000000000..be0e7df91cf Binary files /dev/null and b/resources/win32/inno-big-175.bmp differ diff --git a/resources/win32/inno-big-200.bmp b/resources/win32/inno-big-200.bmp new file mode 100755 index 00000000000..25ecb88e833 Binary files /dev/null and b/resources/win32/inno-big-200.bmp differ diff --git a/resources/win32/inno-big-225.bmp b/resources/win32/inno-big-225.bmp new file mode 100755 index 00000000000..89cf4efb1e0 Binary files /dev/null and b/resources/win32/inno-big-225.bmp differ diff --git a/resources/win32/inno-big-250.bmp b/resources/win32/inno-big-250.bmp new file mode 100644 index 00000000000..a791fb63ec6 Binary files /dev/null and b/resources/win32/inno-big-250.bmp differ diff --git a/resources/win32/inno-small-100.bmp b/resources/win32/inno-small-100.bmp new file mode 100755 index 00000000000..59f09fd8134 Binary files /dev/null and b/resources/win32/inno-small-100.bmp differ diff --git a/resources/win32/inno-small-125.bmp b/resources/win32/inno-small-125.bmp new file mode 100755 index 00000000000..69b3b1fa2ee Binary files /dev/null and b/resources/win32/inno-small-125.bmp differ diff --git a/resources/win32/inno-small-150.bmp b/resources/win32/inno-small-150.bmp new file mode 100755 index 00000000000..3f65efc5f4c Binary files /dev/null and b/resources/win32/inno-small-150.bmp differ diff --git a/resources/win32/inno-small-175.bmp b/resources/win32/inno-small-175.bmp new file mode 100755 index 00000000000..a11b53bcfe1 Binary files /dev/null and b/resources/win32/inno-small-175.bmp differ diff --git a/resources/win32/inno-small-200.bmp b/resources/win32/inno-small-200.bmp new file mode 100755 index 00000000000..c0ad5436b66 Binary files /dev/null and b/resources/win32/inno-small-200.bmp differ diff --git a/resources/win32/inno-small-225.bmp b/resources/win32/inno-small-225.bmp new file mode 100755 index 00000000000..9a974c4d9c0 Binary files /dev/null and b/resources/win32/inno-small-225.bmp differ diff --git a/resources/win32/inno-small-250.bmp b/resources/win32/inno-small-250.bmp new file mode 100755 index 00000000000..f32a791ea8f Binary files /dev/null and b/resources/win32/inno-small-250.bmp differ diff --git a/resources/win32/inno-small.bmp b/resources/win32/inno-small.bmp deleted file mode 100644 index 98feb812d25..00000000000 Binary files a/resources/win32/inno-small.bmp and /dev/null differ diff --git a/scripts/code.bat b/scripts/code.bat index 8c058365dca..f4689608e4a 100644 --- a/scripts/code.bat +++ b/scripts/code.bat @@ -33,6 +33,7 @@ set VSCODE_CLI=1 set ELECTRON_DEFAULT_ERROR_MODE=1 set ELECTRON_ENABLE_LOGGING=1 set ELECTRON_ENABLE_STACK_DUMPING=1 +set VSCODE_LOGS= :: Launch Code diff --git a/scripts/code.sh b/scripts/code.sh index 830271adff6..542efab5237 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -ex +set -e if [[ "$OSTYPE" == "darwin"* ]]; then realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; } diff --git a/src/buildfile.js b/src/buildfile.js index 0c77f1ae5fa..746975249ed 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -3,6 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +function entrypoint(name) { + return [{ name: name, include: [], exclude: ['vs/css', 'vs/nls'] }]; +} + exports.base = [{ name: 'vs/base/common/worker/simpleWorker', include: ['vs/editor/common/services/editorSimpleWorker'], @@ -19,11 +23,17 @@ exports.serviceWorker = [{ dest: 'vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.js' }]; -exports.workbench = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.main']); -exports.workbenchWeb = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.web.api']); +exports.workerExtensionHost = [entrypoint('vs/workbench/services/extensions/worker/extensionHostWorker')]; + +exports.workbench = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.desktop.main']); +exports.workbenchWeb = entrypoint('vs/workbench/workbench.web.api'); + +exports.keyboardMaps = [ + entrypoint('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux'), + entrypoint('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin'), + entrypoint('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win') +]; exports.code = require('./vs/code/buildfile').collectModules(); -exports.entrypoint = function (name) { - return [{ name: name, include: [], exclude: ['vs/css', 'vs/nls'] }]; -}; +exports.entrypoint = entrypoint; diff --git a/src/main.js b/src/main.js index 6902f1d6530..4be5f2ed955 100644 --- a/src/main.js +++ b/src/main.js @@ -133,7 +133,7 @@ function onReady() { } /** - * @typedef {import('minimist').ParsedArgs} ParsedArgs + * @typedef {{ [arg: string]: any; '--'?: string[]; _: string[]; }} ParsedArgs * * @param {ParsedArgs} cliArgs */ @@ -186,7 +186,7 @@ function getUserDataPath(cliArgs) { * @returns {ParsedArgs} */ function parseCLIArgs() { - const minimist = require('minimist'); + const minimist = require('vscode-minimist'); return minimist(process.argv, { string: [ diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json index cfd4b390a62..79d754c01f5 100644 --- a/src/tsconfig.base.json +++ b/src/tsconfig.base.json @@ -20,11 +20,10 @@ }, "types": [ "keytar", - "minimist", "mocha", "semver", "sinon", "winreg" ] } -} \ No newline at end of file +} diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 32983503724..aa0af48b342 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -14,7 +14,7 @@ }, "include": [ "typings/require.d.ts", - "./typings/require-monaco.d.ts", + "typings/require-monaco.d.ts", "typings/thenable.d.ts", "typings/es6-promise.d.ts", "typings/lib.es2018.promise.d.ts", diff --git a/src/typings/applicationinsights-web.d.ts b/src/typings/applicationinsights-web.d.ts new file mode 100644 index 00000000000..5af4903525c --- /dev/null +++ b/src/typings/applicationinsights-web.d.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. + *--------------------------------------------------------------------------------------------*/ + +declare module '@microsoft/applicationinsights-web' { + export interface IConfig { + instrumentationKey?: string; + endpointUrl?: string; + emitLineDelimitedJson?: boolean; + accountId?: string; + sessionRenewalMs?: number; + sessionExpirationMs?: number; + maxBatchSizeInBytes?: number; + maxBatchInterval?: number; + enableDebug?: boolean; + disableExceptionTracking?: boolean; + disableTelemetry?: boolean; + verboseLogging?: boolean; + diagnosticLogInterval?: number; + samplingPercentage?: number; + autoTrackPageVisitTime?: boolean; + disableAjaxTracking?: boolean; + overridePageViewDuration?: boolean; + maxAjaxCallsPerView?: number; + disableDataLossAnalysis?: boolean; + disableCorrelationHeaders?: boolean; + correlationHeaderExcludedDomains?: string[]; + disableFlushOnBeforeUnload?: boolean; + enableSessionStorageBuffer?: boolean; + isCookieUseDisabled?: boolean; + cookieDomain?: string; + isRetryDisabled?: boolean; + url?: string; + isStorageUseDisabled?: boolean; + isBeaconApiDisabled?: boolean; + sdkExtension?: string; + isBrowserLinkTrackingEnabled?: boolean; + appId?: string; + enableCorsCorrelation?: boolean; + } + + export interface ISnippet { + config: IConfig; + } + + export interface IEventTelemetry { + name: string; + properties?: { [key: string]: string }; + measurements?: { [key: string]: number }; + } + + export class ApplicationInsights { + constructor(config: ISnippet); + loadAppInsights(): void; + trackEvent(data: IEventTelemetry): void; + flush(): void; + } +} diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts index 5be0ac0e3fe..cc80376a765 100644 --- a/src/typings/electron.d.ts +++ b/src/typings/electron.d.ts @@ -1,4 +1,4 @@ -// Type definitions for Electron 4.2.7 +// Type definitions for Electron 4.2.9 // Project: http://electronjs.org/ // Definitions by: The Electron Team // Definitions: https://github.com/electron/electron-typescript-definitions diff --git a/src/typings/vscode-minimist.d.ts b/src/typings/vscode-minimist.d.ts new file mode 100644 index 00000000000..17558a1a738 --- /dev/null +++ b/src/typings/vscode-minimist.d.ts @@ -0,0 +1,92 @@ +// Type definitions for minimist 1.2.0 +// Project: https://github.com/substack/minimist +// Definitions by: Bart van der Schoor , Necroskillz , kamranayub +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/** + * Return an argument object populated with the array arguments from args + * + * @param args An optional argument array (typically `process.argv.slice(2)`) + * @param opts An optional options object to customize the parsing + */ +declare function minimist(args?: string[], opts?: minimist.Opts): minimist.ParsedArgs; + +/** + * Return an argument object populated with the array arguments from args. Strongly-typed + * to be the intersect of type T with minimist.ParsedArgs. + * + * @type T The type that will be intersected with minimist.ParsedArgs to represent the argument object + * @param args An optional argument array (typically `process.argv.slice(2)`) + * @param opts An optional options object to customize the parsing + */ +declare function minimist(args?: string[], opts?: minimist.Opts): T & minimist.ParsedArgs; + +/** + * Return an argument object populated with the array arguments from args. Strongly-typed + * to be the the type T which should extend minimist.ParsedArgs + * + * @type T The type that extends minimist.ParsedArgs and represents the argument object + * @param args An optional argument array (typically `process.argv.slice(2)`) + * @param opts An optional options object to customize the parsing + */ +declare function minimist(args?: string[], opts?: minimist.Opts): T; + +declare namespace minimist { + export interface Opts { + /** + * A string or array of strings argument names to always treat as strings + */ + string?: string | string[]; + + /** + * A boolean, string or array of strings to always treat as booleans. If true will treat + * all double hyphenated arguments without equals signs as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`) + */ + boolean?: boolean | string | string[]; + + /** + * An object mapping string names to strings or arrays of string argument names to use as aliases + */ + alias?: { [key: string]: string | string[] }; + + /** + * An object mapping string argument names to default values + */ + default?: { [key: string]: any }; + + /** + * When true, populate argv._ with everything after the first non-option + */ + stopEarly?: boolean; + + /** + * A function which is invoked with a command line parameter not defined in the opts + * configuration object. If the function returns false, the unknown option is not added to argv + */ + unknown?: (arg: string) => boolean; + + /** + * When true, populate argv._ with everything before the -- and argv['--'] with everything after the --. + * Note that with -- set, parsing for arguments still stops after the `--`. + */ + '--'?: boolean; + } + + export interface ParsedArgs { + [arg: string]: any; + + /** + * If opts['--'] is true, populated with everything after the -- + */ + '--'?: string[]; + + /** + * Contains all the arguments that didn't have an option associated with them + */ + _: string[]; + } +} + +declare module "vscode-minimist" { + export = minimist; +} diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 6d4436c23eb..1641b7304af 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -56,7 +56,7 @@ class WindowManager { } // --- Fullscreen - private _fullscreen: boolean; + private _fullscreen: boolean = false; private readonly _onDidChangeFullscreen = new Emitter(); public readonly onDidChangeFullscreen: Event = this._onDidChangeFullscreen.event; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index fa12f629000..3967d3b3f08 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -15,7 +15,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import * as platform from 'vs/base/common/platform'; import { coalesce } from 'vs/base/common/arrays'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; +import { Schemas, RemoteAuthorities } from 'vs/base/common/network'; export function clearNode(node: HTMLElement): void { while (node.firstChild) { @@ -50,8 +50,8 @@ interface IDomClassList { const _manualClassList = new class implements IDomClassList { - private _lastStart: number; - private _lastEnd: number; + private _lastStart: number = -1; + private _lastEnd: number = -1; private _findClassName(node: HTMLElement, className: string): void { @@ -855,6 +855,7 @@ export const EventType = { KEY_UP: 'keyup', // HTML Document LOAD: 'load', + BEFORE_UNLOAD: 'beforeunload', UNLOAD: 'unload', ABORT: 'abort', ERROR: 'error', @@ -1193,14 +1194,21 @@ export function asDomUri(uri: URI): URI { if (!uri) { return uri; } - if (!platform.isWeb) { - //todo@joh remove this once we have sw in electron going - return uri; - } if (Schemas.vscodeRemote === uri.scheme) { - // rewrite vscode-remote-uris to uris of the window location - // so that they can be intercepted by the service worker - return _location.with({ path: '/vscode-resources/fetch', query: `u=${JSON.stringify(uri)}` }); + if (platform.isWeb) { + // rewrite vscode-remote-uris to uris of the window location + // so that they can be intercepted by the service worker + return _location.with({ path: '/vscode-remote', query: JSON.stringify(uri) }); + } else { + return RemoteAuthorities.rewrite(uri.authority, uri.path); + } } return uri; } + +/** + * returns url('...') + */ +export function asCSSUrl(uri: URI): string { + return `url('${asDomUri(uri).toString(true).replace(/'/g, '%27')}')`; +} diff --git a/src/vs/base/browser/formattedTextRenderer.ts b/src/vs/base/browser/formattedTextRenderer.ts new file mode 100644 index 00000000000..0e9b0c9571c --- /dev/null +++ b/src/vs/base/browser/formattedTextRenderer.ts @@ -0,0 +1,220 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; +import { DisposableStore } from 'vs/base/common/lifecycle'; + +export interface IContentActionHandler { + callback: (content: string, event?: IMouseEvent) => void; + readonly disposeables: DisposableStore; +} + +export interface FormattedTextRenderOptions { + readonly className?: string; + readonly inline?: boolean; + readonly actionHandler?: IContentActionHandler; +} + +export function renderText(text: string, options: FormattedTextRenderOptions = {}): HTMLElement { + const element = createElement(options); + element.textContent = text; + return element; +} + +export function renderFormattedText(formattedText: string, options: FormattedTextRenderOptions = {}): HTMLElement { + const element = createElement(options); + _renderFormattedText(element, parseFormattedText(formattedText), options.actionHandler); + return element; +} + +export function createElement(options: FormattedTextRenderOptions): HTMLElement { + const tagName = options.inline ? 'span' : 'div'; + const element = document.createElement(tagName); + if (options.className) { + element.className = options.className; + } + return element; +} + +class StringStream { + private source: string; + private index: number; + + constructor(source: string) { + this.source = source; + this.index = 0; + } + + public eos(): boolean { + return this.index >= this.source.length; + } + + public next(): string { + const next = this.peek(); + this.advance(); + return next; + } + + public peek(): string { + return this.source[this.index]; + } + + public advance(): void { + this.index++; + } +} + +const enum FormatType { + Invalid, + Root, + Text, + Bold, + Italics, + Action, + ActionClose, + NewLine +} + +interface IFormatParseTree { + type: FormatType; + content?: string; + index?: number; + children?: IFormatParseTree[]; +} + +function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionHandler?: IContentActionHandler) { + let child: Node | undefined; + + if (treeNode.type === FormatType.Text) { + child = document.createTextNode(treeNode.content || ''); + } else if (treeNode.type === FormatType.Bold) { + child = document.createElement('b'); + } else if (treeNode.type === FormatType.Italics) { + child = document.createElement('i'); + } else if (treeNode.type === FormatType.Action && actionHandler) { + const a = document.createElement('a'); + a.href = '#'; + actionHandler.disposeables.add(DOM.addStandardDisposableListener(a, 'click', (event) => { + actionHandler.callback(String(treeNode.index), event); + })); + + child = a; + } else if (treeNode.type === FormatType.NewLine) { + child = document.createElement('br'); + } else if (treeNode.type === FormatType.Root) { + child = element; + } + + if (child && element !== child) { + element.appendChild(child); + } + + if (child && Array.isArray(treeNode.children)) { + treeNode.children.forEach((nodeChild) => { + _renderFormattedText(child!, nodeChild, actionHandler); + }); + } +} + +function parseFormattedText(content: string): IFormatParseTree { + + const root: IFormatParseTree = { + type: FormatType.Root, + children: [] + }; + + let actionViewItemIndex = 0; + let current = root; + const stack: IFormatParseTree[] = []; + const stream = new StringStream(content); + + while (!stream.eos()) { + let next = stream.next(); + + const isEscapedFormatType = (next === '\\' && formatTagType(stream.peek()) !== FormatType.Invalid); + if (isEscapedFormatType) { + next = stream.next(); // unread the backslash if it escapes a format tag type + } + + if (!isEscapedFormatType && isFormatTag(next) && next === stream.peek()) { + stream.advance(); + + if (current.type === FormatType.Text) { + current = stack.pop()!; + } + + const type = formatTagType(next); + if (current.type === type || (current.type === FormatType.Action && type === FormatType.ActionClose)) { + current = stack.pop()!; + } else { + const newCurrent: IFormatParseTree = { + type: type, + children: [] + }; + + if (type === FormatType.Action) { + newCurrent.index = actionViewItemIndex; + actionViewItemIndex++; + } + + current.children!.push(newCurrent); + stack.push(current); + current = newCurrent; + } + } else if (next === '\n') { + if (current.type === FormatType.Text) { + current = stack.pop()!; + } + + current.children!.push({ + type: FormatType.NewLine + }); + + } else { + if (current.type !== FormatType.Text) { + const textCurrent: IFormatParseTree = { + type: FormatType.Text, + content: next + }; + current.children!.push(textCurrent); + stack.push(current); + current = textCurrent; + + } else { + current.content += next; + } + } + } + + if (current.type === FormatType.Text) { + current = stack.pop()!; + } + + if (stack.length) { + // incorrectly formatted string literal + } + + return root; +} + +function isFormatTag(char: string): boolean { + return formatTagType(char) !== FormatType.Invalid; +} + +function formatTagType(char: string): FormatType { + switch (char) { + case '*': + return FormatType.Bold; + case '_': + return FormatType.Italics; + case '[': + return FormatType.Action; + case ']': + return FormatType.ActionClose; + default: + return FormatType.Invalid; + } +} diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts deleted file mode 100644 index 4506912e178..00000000000 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ /dev/null @@ -1,394 +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 DOM from 'vs/base/browser/dom'; -import { defaultGenerator } from 'vs/base/common/idGenerator'; -import { escape } from 'vs/base/common/strings'; -import { removeMarkdownEscapes, IMarkdownString, parseHrefAndDimensions } from 'vs/base/common/htmlContent'; -import * as marked from 'vs/base/common/marked/marked'; -import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { URI } from 'vs/base/common/uri'; -import { parse } from 'vs/base/common/marshalling'; -import { cloneAndChange } from 'vs/base/common/objects'; - -export interface IContentActionHandler { - callback: (content: string, event?: IMouseEvent) => void; - readonly disposeables: DisposableStore; -} - -export interface RenderOptions { - className?: string; - inline?: boolean; - actionHandler?: IContentActionHandler; - codeBlockRenderer?: (modeId: string, value: string) => Promise; - codeBlockRenderCallback?: () => void; -} - -function createElement(options: RenderOptions): HTMLElement { - const tagName = options.inline ? 'span' : 'div'; - const element = document.createElement(tagName); - if (options.className) { - element.className = options.className; - } - return element; -} - -export function renderText(text: string, options: RenderOptions = {}): HTMLElement { - const element = createElement(options); - element.textContent = text; - return element; -} - -export function renderFormattedText(formattedText: string, options: RenderOptions = {}): HTMLElement { - const element = createElement(options); - _renderFormattedText(element, parseFormattedText(formattedText), options.actionHandler); - return element; -} - -/** - * Create html nodes for the given content element. - */ -export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions = {}): HTMLElement { - const element = createElement(options); - - const _uriMassage = function (part: string): string { - let data: any; - try { - data = parse(decodeURIComponent(part)); - } catch (e) { - // ignore - } - if (!data) { - return part; - } - data = cloneAndChange(data, value => { - if (markdown.uris && markdown.uris[value]) { - return URI.revive(markdown.uris[value]); - } else { - return undefined; - } - }); - return encodeURIComponent(JSON.stringify(data)); - }; - - const _href = function (href: string, isDomUri: boolean): string { - const data = markdown.uris && markdown.uris[href]; - if (!data) { - return href; - } - let uri = URI.revive(data); - if (isDomUri) { - uri = DOM.asDomUri(uri); - } - if (uri.query) { - uri = uri.with({ query: _uriMassage(uri.query) }); - } - if (data) { - href = uri.toString(true); - } - return href; - }; - - // signal to code-block render that the - // element has been created - let signalInnerHTML: () => void; - const withInnerHTML = new Promise(c => signalInnerHTML = c); - - const renderer = new marked.Renderer(); - renderer.image = (href: string, title: string, text: string) => { - let dimensions: string[] = []; - let attributes: string[] = []; - if (href) { - ({ href, dimensions } = parseHrefAndDimensions(href)); - href = _href(href, true); - attributes.push(`src="${href}"`); - } - if (text) { - attributes.push(`alt="${text}"`); - } - if (title) { - attributes.push(`title="${title}"`); - } - if (dimensions.length) { - attributes = attributes.concat(dimensions); - } - return ''; - }; - renderer.link = (href, title, text): string => { - // Remove markdown escapes. Workaround for https://github.com/chjj/marked/issues/829 - if (href === text) { // raw link case - text = removeMarkdownEscapes(text); - } - href = _href(href, false); - title = removeMarkdownEscapes(title); - href = removeMarkdownEscapes(href); - if ( - !href - || href.match(/^data:|javascript:/i) - || (href.match(/^command:/i) && !markdown.isTrusted) - || href.match(/^command:(\/\/\/)?_workbench\.downloadResource/i) - ) { - // drop the link - return text; - - } else { - // HTML Encode href - href = href.replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - return `${text}`; - } - }; - renderer.paragraph = (text): string => { - return `

${text}

`; - }; - - if (options.codeBlockRenderer) { - renderer.code = (code, lang) => { - const value = options.codeBlockRenderer!(lang, code); - // when code-block rendering is async we return sync - // but update the node with the real result later. - const id = defaultGenerator.nextId(); - const promise = Promise.all([value, withInnerHTML]).then(values => { - const strValue = values[0]; - const span = element.querySelector(`div[data-code="${id}"]`); - if (span) { - span.innerHTML = strValue; - } - }).catch(err => { - // ignore - }); - - if (options.codeBlockRenderCallback) { - promise.then(options.codeBlockRenderCallback); - } - - return `
${escape(code)}
`; - }; - } - - if (options.actionHandler) { - options.actionHandler.disposeables.add(DOM.addStandardDisposableListener(element, 'click', event => { - let target: HTMLElement | null = event.target; - if (target.tagName !== 'A') { - target = target.parentElement; - if (!target || target.tagName !== 'A') { - return; - } - } - try { - const href = target.dataset['href']; - if (href) { - options.actionHandler!.callback(href, event); - } - } catch (err) { - onUnexpectedError(err); - } finally { - event.preventDefault(); - } - })); - } - - const markedOptions: marked.MarkedOptions = { - sanitize: true, - renderer - }; - - element.innerHTML = marked.parse(markdown.value, markedOptions); - signalInnerHTML!(); - - return element; -} - -// --- formatted string parsing - -class StringStream { - private source: string; - private index: number; - - constructor(source: string) { - this.source = source; - this.index = 0; - } - - public eos(): boolean { - return this.index >= this.source.length; - } - - public next(): string { - const next = this.peek(); - this.advance(); - return next; - } - - public peek(): string { - return this.source[this.index]; - } - - public advance(): void { - this.index++; - } -} - -const enum FormatType { - Invalid, - Root, - Text, - Bold, - Italics, - Action, - ActionClose, - NewLine -} - -interface IFormatParseTree { - type: FormatType; - content?: string; - index?: number; - children?: IFormatParseTree[]; -} - -function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionHandler?: IContentActionHandler) { - let child: Node | undefined; - - if (treeNode.type === FormatType.Text) { - child = document.createTextNode(treeNode.content || ''); - } - else if (treeNode.type === FormatType.Bold) { - child = document.createElement('b'); - } - else if (treeNode.type === FormatType.Italics) { - child = document.createElement('i'); - } - else if (treeNode.type === FormatType.Action && actionHandler) { - const a = document.createElement('a'); - a.href = '#'; - actionHandler.disposeables.add(DOM.addStandardDisposableListener(a, 'click', (event) => { - actionHandler.callback(String(treeNode.index), event); - })); - - child = a; - } - else if (treeNode.type === FormatType.NewLine) { - child = document.createElement('br'); - } - else if (treeNode.type === FormatType.Root) { - child = element; - } - - if (child && element !== child) { - element.appendChild(child); - } - - if (child && Array.isArray(treeNode.children)) { - treeNode.children.forEach((nodeChild) => { - _renderFormattedText(child!, nodeChild, actionHandler); - }); - } -} - -function parseFormattedText(content: string): IFormatParseTree { - - const root: IFormatParseTree = { - type: FormatType.Root, - children: [] - }; - - let actionViewItemIndex = 0; - let current = root; - const stack: IFormatParseTree[] = []; - const stream = new StringStream(content); - - while (!stream.eos()) { - let next = stream.next(); - - const isEscapedFormatType = (next === '\\' && formatTagType(stream.peek()) !== FormatType.Invalid); - if (isEscapedFormatType) { - next = stream.next(); // unread the backslash if it escapes a format tag type - } - - if (!isEscapedFormatType && isFormatTag(next) && next === stream.peek()) { - stream.advance(); - - if (current.type === FormatType.Text) { - current = stack.pop()!; - } - - const type = formatTagType(next); - if (current.type === type || (current.type === FormatType.Action && type === FormatType.ActionClose)) { - current = stack.pop()!; - } else { - const newCurrent: IFormatParseTree = { - type: type, - children: [] - }; - - if (type === FormatType.Action) { - newCurrent.index = actionViewItemIndex; - actionViewItemIndex++; - } - - current.children!.push(newCurrent); - stack.push(current); - current = newCurrent; - } - } else if (next === '\n') { - if (current.type === FormatType.Text) { - current = stack.pop()!; - } - - current.children!.push({ - type: FormatType.NewLine - }); - - } else { - if (current.type !== FormatType.Text) { - const textCurrent: IFormatParseTree = { - type: FormatType.Text, - content: next - }; - current.children!.push(textCurrent); - stack.push(current); - current = textCurrent; - - } else { - current.content += next; - } - } - } - - if (current.type === FormatType.Text) { - current = stack.pop()!; - } - - if (stack.length) { - // incorrectly formatted string literal - } - - return root; -} - -function isFormatTag(char: string): boolean { - return formatTagType(char) !== FormatType.Invalid; -} - -function formatTagType(char: string): FormatType { - switch (char) { - case '*': - return FormatType.Bold; - case '_': - return FormatType.Italics; - case '[': - return FormatType.Action; - case ']': - return FormatType.ActionClose; - default: - return FormatType.Invalid; - } -} diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts new file mode 100644 index 00000000000..5d94200e15a --- /dev/null +++ b/src/vs/base/browser/markdownRenderer.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { IMarkdownString, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent'; +import { defaultGenerator } from 'vs/base/common/idGenerator'; +import * as marked from 'vs/base/common/marked/marked'; +import * as insane from 'vs/base/common/insane/insane'; +import { parse } from 'vs/base/common/marshalling'; +import { cloneAndChange } from 'vs/base/common/objects'; +import { escape } from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; + +export interface MarkdownRenderOptions extends FormattedTextRenderOptions { + codeBlockRenderer?: (modeId: string, value: string) => Promise; + codeBlockRenderCallback?: () => void; +} + +/** + * Create html nodes for the given content element. + */ +export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}): HTMLElement { + const element = createElement(options); + + const _uriMassage = function (part: string): string { + let data: any; + try { + data = parse(decodeURIComponent(part)); + } catch (e) { + // ignore + } + if (!data) { + return part; + } + data = cloneAndChange(data, value => { + if (markdown.uris && markdown.uris[value]) { + return URI.revive(markdown.uris[value]); + } else { + return undefined; + } + }); + return encodeURIComponent(JSON.stringify(data)); + }; + + const _href = function (href: string, isDomUri: boolean): string { + const data = markdown.uris && markdown.uris[href]; + if (!data) { + return href; + } + let uri = URI.revive(data); + if (isDomUri) { + uri = DOM.asDomUri(uri); + } + if (uri.query) { + uri = uri.with({ query: _uriMassage(uri.query) }); + } + if (data) { + href = uri.toString(true); + } + return href; + }; + + // signal to code-block render that the + // element has been created + let signalInnerHTML: () => void; + const withInnerHTML = new Promise(c => signalInnerHTML = c); + + const renderer = new marked.Renderer(); + renderer.image = (href: string, title: string, text: string) => { + let dimensions: string[] = []; + let attributes: string[] = []; + if (href) { + ({ href, dimensions } = parseHrefAndDimensions(href)); + href = _href(href, true); + attributes.push(`src="${href}"`); + } + if (text) { + attributes.push(`alt="${text}"`); + } + if (title) { + attributes.push(`title="${title}"`); + } + if (dimensions.length) { + attributes = attributes.concat(dimensions); + } + return ''; + }; + renderer.link = (href, title, text): string => { + // Remove markdown escapes. Workaround for https://github.com/chjj/marked/issues/829 + if (href === text) { // raw link case + text = removeMarkdownEscapes(text); + } + href = _href(href, false); + title = removeMarkdownEscapes(title); + href = removeMarkdownEscapes(href); + if ( + !href + || href.match(/^data:|javascript:/i) + || (href.match(/^command:/i) && !markdown.isTrusted) + || href.match(/^command:(\/\/\/)?_workbench\.downloadResource/i) + ) { + // drop the link + return text; + + } else { + // HTML Encode href + href = href.replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + return `${text}`; + } + }; + renderer.paragraph = (text): string => { + return `

${text}

`; + }; + + if (options.codeBlockRenderer) { + renderer.code = (code, lang) => { + const value = options.codeBlockRenderer!(lang, code); + // when code-block rendering is async we return sync + // but update the node with the real result later. + const id = defaultGenerator.nextId(); + const promise = Promise.all([value, withInnerHTML]).then(values => { + const strValue = values[0]; + const span = element.querySelector(`div[data-code="${id}"]`); + if (span) { + span.innerHTML = strValue; + } + }).catch(err => { + // ignore + }); + + if (options.codeBlockRenderCallback) { + promise.then(options.codeBlockRenderCallback); + } + + return `
${escape(code)}
`; + }; + } + + const actionHandler = options.actionHandler; + if (actionHandler) { + actionHandler.disposeables.add(DOM.addStandardDisposableListener(element, 'click', event => { + let target: HTMLElement | null = event.target; + if (target.tagName !== 'A') { + target = target.parentElement; + if (!target || target.tagName !== 'A') { + return; + } + } + try { + const href = target.dataset['href']; + if (href) { + actionHandler.callback(href, event); + } + } catch (err) { + onUnexpectedError(err); + } finally { + event.preventDefault(); + } + })); + } + + const markedOptions: marked.MarkedOptions = { + sanitize: true, + renderer + }; + + const allowedSchemes = ['http', 'https', 'mailto']; + if (markdown.isTrusted) { + allowedSchemes.push('command'); + } + + const renderedMarkdown = marked.parse(markdown.value, markedOptions); + element.innerHTML = insane(renderedMarkdown, { + allowedSchemes, + allowedAttributes: { + 'a': ['href', 'name', 'target', 'data-href'], + 'iframe': ['allowfullscreen', 'frameborder', 'src'], + 'img': ['src', 'title', 'alt', 'width', 'height'] + } + }); + signalInnerHTML!(); + + return element; +} diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index 2499d715f8f..f1ea0ee56ca 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -69,7 +69,7 @@ export class Gesture extends Disposable { private static INSTANCE: Gesture; private static HOLD_DELAY = 700; - private dispatched: boolean; + private dispatched = false; private targets: HTMLElement[]; private handle: IDisposable | null; diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 413a256483f..538f7fa0dbe 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -37,7 +37,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { _context: any; _action: IAction; - private _actionRunner: IActionRunner; + private _actionRunner!: IActionRunner; constructor(context: any, action: IAction, protected options?: IBaseActionViewItemOptions) { super(); @@ -232,7 +232,7 @@ export interface IActionViewItemOptions extends IBaseActionViewItemOptions { export class ActionViewItem extends BaseActionViewItem { - protected label: HTMLElement; + protected label!: HTMLElement; protected options: IActionViewItemOptions; private cssClass?: string; diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 13e4b9c34be..2e72938f986 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -77,8 +77,8 @@ export class BreadcrumbsWidget { private _focusedItemIdx: number = -1; private _selectedItemIdx: number = -1; - private _pendingLayout: IDisposable; - private _dimension: dom.Dimension; + private _pendingLayout: IDisposable | undefined; + private _dimension: dom.Dimension | undefined; constructor( container: HTMLElement diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 82c1a85b8fb..eeb7724e5fd 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -223,4 +223,4 @@ export class ButtonGroup extends Disposable { } } } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts index 299b6534873..43f665fbd00 100644 --- a/src/vs/base/browser/ui/centered/centeredViewLayout.ts +++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts @@ -20,10 +20,12 @@ const GOLDEN_RATIO = { rightMarginRatio: 0.1909 }; -function createEmptyView(background: Color): ISplitViewView { +function createEmptyView(background: Color | undefined): ISplitViewView { const element = $('.centered-layout-margin'); element.style.height = '100%'; - element.style.backgroundColor = background.toString(); + if (background) { + element.style.backgroundColor = background.toString(); + } return { element, @@ -53,7 +55,7 @@ export class CenteredViewLayout implements IDisposable { private splitView?: SplitView; private width: number = 0; private height: number = 0; - private style: ICenteredViewStyles; + private style!: ICenteredViewStyles; private didLayout = false; private emptyViews: ISplitViewView[] | undefined; private readonly splitViewDisposables = new DisposableStore(); @@ -132,7 +134,8 @@ export class CenteredViewLayout implements IDisposable { this.splitView.layout(this.width); this.splitView.addView(toSplitViewView(this.view, () => this.height), 0); - this.emptyViews = [createEmptyView(this.style.background), createEmptyView(this.style.background)]; + const backgroundColor = this.style ? this.style.background : undefined; + this.emptyViews = [createEmptyView(backgroundColor), createEmptyView(backgroundColor)]; this.splitView.addView(this.emptyViews[0], this.state.leftMarginRatio * this.width, 0); this.splitView.addView(this.emptyViews[1], this.state.rightMarginRatio * this.width, 2); } else { diff --git a/src/vs/base/browser/ui/checkbox/check-dark.svg b/src/vs/base/browser/ui/checkbox/check-dark.svg new file mode 100644 index 00000000000..865cc83c347 --- /dev/null +++ b/src/vs/base/browser/ui/checkbox/check-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/base/browser/ui/checkbox/check-light.svg b/src/vs/base/browser/ui/checkbox/check-light.svg new file mode 100644 index 00000000000..e1a546660ed --- /dev/null +++ b/src/vs/base/browser/ui/checkbox/check-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/base/browser/ui/checkbox/checkbox.css b/src/vs/base/browser/ui/checkbox/checkbox.css index 65f1dcd2318..26f48b88c02 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.css +++ b/src/vs/base/browser/ui/checkbox/checkbox.css @@ -39,4 +39,24 @@ .hc-black .monaco-custom-checkbox:hover { background: none; -} \ No newline at end of file +} + +.monaco-custom-checkbox.monaco-simple-checkbox { + height: 18px; + width: 18px; + border: 1px solid transparent; + border-radius: 3px; + margin-right: 9px; + margin-left: 0px; + padding: 0px; + opacity: 1; + background-size: 16px !important; +} + +.monaco-custom-checkbox.monaco-simple-checkbox.checked { + background: url('check-light.svg') center center no-repeat; +} + +.monaco-custom-checkbox.monaco-simple-checkbox.checked { + background: url('check-dark.svg') center center no-repeat; +} diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index 723f64dc4fa..f5fe47cdd85 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -25,6 +25,12 @@ export interface ICheckboxStyles { inputActiveOptionBackground?: Color; } +export interface ISimpleCheckboxStyles { + checkboxBackground?: Color; + checkboxBorder?: Color; + checkboxForeground?: Color; +} + const defaultOpts = { inputActiveOptionBorder: Color.fromHex('#007ACC00'), inputActiveOptionBackground: Color.fromHex('#0E639C50') @@ -32,7 +38,7 @@ const defaultOpts = { export class CheckboxActionViewItem extends BaseActionViewItem { - private checkbox: Checkbox; + private checkbox!: Checkbox; private readonly disposables = new DisposableStore(); render(container: HTMLElement): void { @@ -45,7 +51,7 @@ export class CheckboxActionViewItem extends BaseActionViewItem { title: this._action.label }); this.disposables.add(this.checkbox); - this.disposables.add(this.checkbox.onChange(() => this._action.checked = this.checkbox.checked, this)); + this.disposables.add(this.checkbox.onChange(() => this._action.checked = this.checkbox!.checked, this)); this.element.appendChild(this.checkbox.domNode); } @@ -174,3 +180,46 @@ export class Checkbox extends Widget { this.domNode.setAttribute('aria-disabled', String(true)); } } + +export class SimpleCheckbox extends Widget { + private checkbox: Checkbox; + private styles: ISimpleCheckboxStyles; + + readonly domNode: HTMLElement; + + constructor(private title: string, private isChecked: boolean) { + super(); + + this.checkbox = new Checkbox({ title: this.title, isChecked: this.isChecked, actionClassName: 'monaco-simple-checkbox' }); + + this.domNode = this.checkbox.domNode; + + this.styles = {}; + + this.checkbox.onChange(() => { + this.applyStyles(); + }); + } + + get checked(): boolean { + return this.checkbox.checked; + } + + set checked(newIsChecked: boolean) { + this.checkbox.checked = newIsChecked; + + this.applyStyles(); + } + + style(styles: ISimpleCheckboxStyles): void { + this.styles = styles; + + this.applyStyles(); + } + + protected applyStyles(): void { + this.domNode.style.color = this.styles.checkboxForeground ? this.styles.checkboxForeground.toString() : null; + this.domNode.style.backgroundColor = this.styles.checkboxBackground ? this.styles.checkboxBackground.toString() : null; + this.domNode.style.borderColor = this.styles.checkboxBorder ? this.styles.checkboxBorder.toString() : null; + } +} diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 719126e63d9..8b8bb3b16eb 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -5,7 +5,7 @@ import 'vs/css!./contextview'; import * as DOM from 'vs/base/browser/dom'; -import { IDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Range } from 'vs/base/common/range'; export interface IAnchor { @@ -100,11 +100,11 @@ export class ContextView extends Disposable { private static readonly BUBBLE_UP_EVENTS = ['click', 'keydown', 'focus', 'blur']; private static readonly BUBBLE_DOWN_EVENTS = ['click']; - private container: HTMLElement | null; + private container: HTMLElement | null = null; private view: HTMLElement; - private delegate: IDelegate | null; - private toDisposeOnClean: IDisposable | null; - private toDisposeOnSetContainer: IDisposable; + private delegate: IDelegate | null = null; + private toDisposeOnClean: IDisposable = Disposable.None; + private toDisposeOnSetContainer: IDisposable = Disposable.None; constructor(container: HTMLElement) { super(); @@ -120,7 +120,7 @@ export class ContextView extends Disposable { setContainer(container: HTMLElement | null): void { if (this.container) { - dispose(this.toDisposeOnSetContainer); + this.toDisposeOnSetContainer.dispose(); this.container.removeChild(this.view); this.container = null; } @@ -159,7 +159,7 @@ export class ContextView extends Disposable { DOM.show(this.view); // Render content - this.toDisposeOnClean = delegate.render(this.view); + this.toDisposeOnClean = delegate.render(this.view) || Disposable.None; // Set active delegate this.delegate = delegate; @@ -267,10 +267,7 @@ export class ContextView extends Disposable { delegate.onHide(data); } - if (this.toDisposeOnClean) { - this.toDisposeOnClean.dispose(); - this.toDisposeOnClean = null; - } + this.toDisposeOnClean.dispose(); DOM.hide(this.view); } @@ -294,4 +291,4 @@ export class ContextView extends Disposable { super.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/countBadge/countBadge.ts b/src/vs/base/browser/ui/countBadge/countBadge.ts index c35b5733ff9..e4b1f68568f 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.ts +++ b/src/vs/base/browser/ui/countBadge/countBadge.ts @@ -29,7 +29,7 @@ const defaultOpts = { export class CountBadge { private element: HTMLElement; - private count: number; + private count: number = 0; private countFormat: string; private titleFormat: string; diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index e05ed90a949..83d8d7eca66 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -149,6 +149,11 @@ outline-style: solid; } +.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-checkbox-row { + padding: 15px 0px 0px; + display: flex; +} + /** Dialog: Buttons Row */ .monaco-workbench .dialog-box > .dialog-buttons-row { display: flex; @@ -175,4 +180,4 @@ margin: 4px 5px; /* allows button focus outline to be visible */ overflow: hidden; text-overflow: ellipsis; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 2e95c5955ac..f6b707d975d 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -16,15 +16,23 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { isMacintosh, isLinux } from 'vs/base/common/platform'; +import { SimpleCheckbox, ISimpleCheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox'; export interface IDialogOptions { cancelId?: number; detail?: string; + checkboxLabel?: string; + checkboxChecked?: boolean; type?: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending'; keyEventProcessor?: (event: StandardKeyboardEvent) => void; } -export interface IDialogStyles extends IButtonStyles { +export interface IDialogResult { + button: number; + checkboxChecked?: boolean; +} + +export interface IDialogStyles extends IButtonStyles, ISimpleCheckboxStyles { dialogForeground?: Color; dialogBackground?: Color; dialogShadow?: Color; @@ -42,6 +50,7 @@ export class Dialog extends Disposable { private buttonsContainer: HTMLElement | undefined; private messageDetailElement: HTMLElement | undefined; private iconElement: HTMLElement | undefined; + private checkbox: SimpleCheckbox | undefined; private toolbarContainer: HTMLElement | undefined; private buttonGroup: ButtonGroup | undefined; private styles: IDialogStyles | undefined; @@ -68,6 +77,19 @@ export class Dialog extends Disposable { this.messageDetailElement = messageContainer.appendChild($('.dialog-message-detail')); this.messageDetailElement.innerText = this.options.detail ? this.options.detail : message; + if (this.options.checkboxLabel) { + const checkboxRowElement = messageContainer.appendChild($('.dialog-checkbox-row')); + + this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked)); + + checkboxRowElement.appendChild(this.checkbox.domNode); + + const checkboxMessageElement = checkboxRowElement.appendChild($('.dialog-checkbox-message')); + checkboxMessageElement.innerText = this.options.checkboxLabel; + } + + + const toolbarRowElement = this.element.appendChild($('.dialog-toolbar-row')); this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar')); } @@ -78,12 +100,12 @@ export class Dialog extends Disposable { } } - async show(): Promise { + async show(): Promise { this.focusToReturn = document.activeElement as HTMLElement; - return new Promise((resolve) => { + return new Promise((resolve) => { if (!this.element || !this.buttonsContainer || !this.iconElement || !this.toolbarContainer) { - resolve(0); + resolve({ button: 0 }); return; } @@ -112,7 +134,7 @@ export class Dialog extends Disposable { this._register(button.onDidClick(e => { EventHelper.stop(e); - resolve(buttonMap[index].index); + resolve({ button: buttonMap[index].index, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); })); }); @@ -147,7 +169,7 @@ export class Dialog extends Disposable { const evt = new StandardKeyboardEvent(e); if (evt.equals(KeyCode.Escape)) { - resolve(this.options.cancelId || 0); + resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); } })); @@ -187,7 +209,7 @@ export class Dialog extends Disposable { const actionBar = new ActionBar(this.toolbarContainer, {}); const action = new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), 'dialog-close-action', true, () => { - resolve(this.options.cancelId || 0); + resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); return Promise.resolve(); }); @@ -195,6 +217,7 @@ export class Dialog extends Disposable { this.applyStyles(); + this.element.setAttribute('aria-label', this.message); show(this.element); // Focus first element @@ -220,6 +243,10 @@ export class Dialog extends Disposable { if (this.buttonGroup) { this.buttonGroup.buttons.forEach(button => button.style(style)); } + + if (this.checkbox) { + this.checkbox.style(style); + } } } } @@ -260,4 +287,4 @@ export class Dialog extends Disposable { return buttonMap; } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 1f9217cc9dd..7c984ea2163 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -29,7 +29,7 @@ export class BaseDropdown extends ActionRunner { private boxContainer?: HTMLElement; private _label?: HTMLElement; private contents?: HTMLElement; - private visible: boolean; + private visible: boolean | undefined; constructor(container: HTMLElement, options: IBaseDropdownOptions) { super(); @@ -109,7 +109,7 @@ export class BaseDropdown extends ActionRunner { } isVisible(): boolean { - return this.visible; + return !!this.visible; } protected onEvent(e: Event, activeElement: HTMLElement): void { @@ -272,7 +272,7 @@ export class DropdownMenu extends BaseDropdown { export class DropdownMenuActionViewItem extends BaseActionViewItem { private menuActionsOrProvider: any; - private dropdownMenu: DropdownMenu; + private dropdownMenu: DropdownMenu | undefined; private contextMenuProvider: IContextMenuProvider; private actionViewItemProvider?: IActionViewItemProvider; private keybindings?: (action: IAction) => ResolvedKeybinding | undefined; @@ -281,7 +281,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { constructor(action: IAction, menuActions: ReadonlyArray, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); - constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment) { + constructor(action: IAction, menuActionsOrProvider: ReadonlyArray | IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment) { super(null, action); this.menuActionsOrProvider = menuActionsOrProvider; diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 180e660f3a5..fcc27e402a7 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -24,6 +24,8 @@ export interface IFindInputOptions extends IFindInputStyles { readonly validation?: IInputValidator; readonly label: string; readonly flexibleHeight?: boolean; + readonly flexibleWidth?: boolean; + readonly flexibleMaxHeight?: number; readonly appendCaseSensitiveLabel?: string; readonly appendWholeWordsLabel?: string; @@ -114,7 +116,130 @@ export class FindInput extends Widget { this.inputValidationErrorBackground = options.inputValidationErrorBackground; this.inputValidationErrorForeground = options.inputValidationErrorForeground; - this.buildDomNode(options.appendCaseSensitiveLabel || '', options.appendWholeWordsLabel || '', options.appendRegexLabel || '', options.history || [], !!options.flexibleHeight); + const appendCaseSensitiveLabel = options.appendCaseSensitiveLabel || ''; + const appendWholeWordsLabel = options.appendWholeWordsLabel || ''; + const appendRegexLabel = options.appendRegexLabel || ''; + const history = options.history || []; + const flexibleHeight = !!options.flexibleHeight; + const flexibleWidth = !!options.flexibleWidth; + const flexibleMaxHeight = options.flexibleMaxHeight; + + this.domNode = document.createElement('div'); + dom.addClass(this.domNode, 'monaco-findInput'); + + this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, { + placeholder: this.placeholder || '', + ariaLabel: this.label || '', + validationOptions: { + validation: this.validation + }, + inputBackground: this.inputBackground, + inputForeground: this.inputForeground, + inputBorder: this.inputBorder, + inputValidationInfoBackground: this.inputValidationInfoBackground, + inputValidationInfoForeground: this.inputValidationInfoForeground, + inputValidationInfoBorder: this.inputValidationInfoBorder, + inputValidationWarningBackground: this.inputValidationWarningBackground, + inputValidationWarningForeground: this.inputValidationWarningForeground, + inputValidationWarningBorder: this.inputValidationWarningBorder, + inputValidationErrorBackground: this.inputValidationErrorBackground, + inputValidationErrorForeground: this.inputValidationErrorForeground, + inputValidationErrorBorder: this.inputValidationErrorBorder, + history, + flexibleHeight, + flexibleWidth, + flexibleMaxHeight + })); + + this.regex = this._register(new RegexCheckbox({ + appendTitle: appendRegexLabel, + isChecked: false, + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground + })); + this._register(this.regex.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + this._register(this.regex.onKeyDown(e => { + this._onRegexKeyDown.fire(e); + })); + + this.wholeWords = this._register(new WholeWordsCheckbox({ + appendTitle: appendWholeWordsLabel, + isChecked: false, + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground + })); + this._register(this.wholeWords.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + + this.caseSensitive = this._register(new CaseSensitiveCheckbox({ + appendTitle: appendCaseSensitiveLabel, + isChecked: false, + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground + })); + this._register(this.caseSensitive.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + this._register(this.caseSensitive.onKeyDown(e => { + this._onCaseSensitiveKeyDown.fire(e); + })); + + if (this._showOptionButtons) { + this.inputBox.paddingRight = this.caseSensitive.width() + this.wholeWords.width() + this.regex.width(); + } + + // Arrow-Key support to navigate between options + let indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; + this.onkeydown(this.domNode, (event: IKeyboardEvent) => { + if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) { + let index = indexes.indexOf(document.activeElement); + if (index >= 0) { + let newIndex: number = -1; + if (event.equals(KeyCode.RightArrow)) { + newIndex = (index + 1) % indexes.length; + } else if (event.equals(KeyCode.LeftArrow)) { + if (index === 0) { + newIndex = indexes.length - 1; + } else { + newIndex = index - 1; + } + } + + if (event.equals(KeyCode.Escape)) { + indexes[index].blur(); + } else if (newIndex >= 0) { + indexes[newIndex].focus(); + } + + dom.EventHelper.stop(event, true); + } + } + }); + + + let controls = document.createElement('div'); + controls.className = 'controls'; + controls.style.display = this._showOptionButtons ? 'block' : 'none'; + controls.appendChild(this.caseSensitive.domNode); + controls.appendChild(this.wholeWords.domNode); + controls.appendChild(this.regex.domNode); + + this.domNode.appendChild(controls); if (parent) { parent.appendChild(this.domNode); @@ -270,127 +395,6 @@ export class FindInput extends Widget { dom.addClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions)); } - private buildDomNode(appendCaseSensitiveLabel: string, appendWholeWordsLabel: string, appendRegexLabel: string, history: string[], flexibleHeight: boolean): void { - this.domNode = document.createElement('div'); - dom.addClass(this.domNode, 'monaco-findInput'); - - this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, { - placeholder: this.placeholder || '', - ariaLabel: this.label || '', - validationOptions: { - validation: this.validation - }, - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder, - history, - flexibleHeight - })); - - this.regex = this._register(new RegexCheckbox({ - appendTitle: appendRegexLabel, - isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionBackground: this.inputActiveOptionBackground - })); - this._register(this.regex.onChange(viaKeyboard => { - this._onDidOptionChange.fire(viaKeyboard); - if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { - this.inputBox.focus(); - } - this.validate(); - })); - this._register(this.regex.onKeyDown(e => { - this._onRegexKeyDown.fire(e); - })); - - this.wholeWords = this._register(new WholeWordsCheckbox({ - appendTitle: appendWholeWordsLabel, - isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionBackground: this.inputActiveOptionBackground - })); - this._register(this.wholeWords.onChange(viaKeyboard => { - this._onDidOptionChange.fire(viaKeyboard); - if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { - this.inputBox.focus(); - } - this.validate(); - })); - - this.caseSensitive = this._register(new CaseSensitiveCheckbox({ - appendTitle: appendCaseSensitiveLabel, - isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionBackground: this.inputActiveOptionBackground - })); - this._register(this.caseSensitive.onChange(viaKeyboard => { - this._onDidOptionChange.fire(viaKeyboard); - if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { - this.inputBox.focus(); - } - this.validate(); - })); - this._register(this.caseSensitive.onKeyDown(e => { - this._onCaseSensitiveKeyDown.fire(e); - })); - - if (this._showOptionButtons) { - const paddingRight = (this.caseSensitive.width() + this.wholeWords.width() + this.regex.width()) + 'px'; - this.inputBox.inputElement.style.paddingRight = paddingRight; - if (this.inputBox.mirrorElement) { - this.inputBox.mirrorElement.style.paddingRight = paddingRight; - } - } - - // Arrow-Key support to navigate between options - let indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; - this.onkeydown(this.domNode, (event: IKeyboardEvent) => { - if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) { - let index = indexes.indexOf(document.activeElement); - if (index >= 0) { - let newIndex: number = -1; - if (event.equals(KeyCode.RightArrow)) { - newIndex = (index + 1) % indexes.length; - } else if (event.equals(KeyCode.LeftArrow)) { - if (index === 0) { - newIndex = indexes.length - 1; - } else { - newIndex = index - 1; - } - } - - if (event.equals(KeyCode.Escape)) { - indexes[index].blur(); - } else if (newIndex >= 0) { - indexes[newIndex].focus(); - } - - dom.EventHelper.stop(event, true); - } - } - }); - - - let controls = document.createElement('div'); - controls.className = 'controls'; - controls.style.display = this._showOptionButtons ? 'block' : 'none'; - controls.appendChild(this.caseSensitive.domNode); - controls.appendChild(this.wholeWords.domNode); - controls.appendChild(this.regex.domNode); - - this.domNode.appendChild(controls); - } - public validate(): void { if (this.inputBox) { this.inputBox.validate(); diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts new file mode 100644 index 00000000000..d597fb8bd49 --- /dev/null +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -0,0 +1,373 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./findInput'; + +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { IMessage as InputBoxMessage, IInputValidator, IInputBoxStyles, HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { Widget } from 'vs/base/browser/ui/widget'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Color } from 'vs/base/common/color'; +import { ICheckboxStyles, Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; +import { IFindInputCheckboxOpts } from 'vs/base/browser/ui/findinput/findInputCheckboxes'; + +export interface IReplaceInputOptions extends IReplaceInputStyles { + readonly placeholder?: string; + readonly width?: number; + readonly validation?: IInputValidator; + readonly label: string; + readonly flexibleHeight?: boolean; + readonly flexibleWidth?: boolean; + readonly flexibleMaxHeight?: number; + + readonly history?: string[]; +} + +export interface IReplaceInputStyles extends IInputBoxStyles { + inputActiveOptionBorder?: Color; +} + +const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); +const NLS_PRESERVE_CASE_LABEL = nls.localize('label.preserveCaseCheckbox', "Preserve Case"); + +export class PreserveCaseCheckbox extends Checkbox { + constructor(opts: IFindInputCheckboxOpts) { + super({ + // TODO: does this need its own icon? + actionClassName: 'monaco-case-sensitive', + title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle, + isChecked: opts.isChecked, + inputActiveOptionBorder: opts.inputActiveOptionBorder + }); + } +} + +export class ReplaceInput extends Widget { + + static readonly OPTION_CHANGE: string = 'optionChange'; + + private contextViewProvider: IContextViewProvider | undefined; + private placeholder: string; + private validation?: IInputValidator; + private label: string; + private fixFocusOnOptionClickEnabled = true; + + private inputActiveOptionBorder?: Color; + private inputBackground?: Color; + private inputForeground?: Color; + private inputBorder?: Color; + + private inputValidationInfoBorder?: Color; + private inputValidationInfoBackground?: Color; + private inputValidationInfoForeground?: Color; + private inputValidationWarningBorder?: Color; + private inputValidationWarningBackground?: Color; + private inputValidationWarningForeground?: Color; + private inputValidationErrorBorder?: Color; + private inputValidationErrorBackground?: Color; + private inputValidationErrorForeground?: Color; + + private preserveCase: PreserveCaseCheckbox; + private cachedOptionsWidth: number = 0; + public domNode: HTMLElement; + public inputBox: HistoryInputBox; + + private readonly _onDidOptionChange = this._register(new Emitter()); + public readonly onDidOptionChange: Event = this._onDidOptionChange.event; + + private readonly _onKeyDown = this._register(new Emitter()); + public readonly onKeyDown: Event = this._onKeyDown.event; + + private readonly _onMouseDown = this._register(new Emitter()); + public readonly onMouseDown: Event = this._onMouseDown.event; + + private readonly _onInput = this._register(new Emitter()); + public readonly onInput: Event = this._onInput.event; + + private readonly _onKeyUp = this._register(new Emitter()); + public readonly onKeyUp: Event = this._onKeyUp.event; + + private _onPreserveCaseKeyDown = this._register(new Emitter()); + public readonly onPreserveCaseKeyDown: Event = this._onPreserveCaseKeyDown.event; + + constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, private readonly _showOptionButtons: boolean, options: IReplaceInputOptions) { + super(); + this.contextViewProvider = contextViewProvider; + this.placeholder = options.placeholder || ''; + this.validation = options.validation; + this.label = options.label || NLS_DEFAULT_LABEL; + + this.inputActiveOptionBorder = options.inputActiveOptionBorder; + this.inputBackground = options.inputBackground; + this.inputForeground = options.inputForeground; + this.inputBorder = options.inputBorder; + + this.inputValidationInfoBorder = options.inputValidationInfoBorder; + this.inputValidationInfoBackground = options.inputValidationInfoBackground; + this.inputValidationInfoForeground = options.inputValidationInfoForeground; + this.inputValidationWarningBorder = options.inputValidationWarningBorder; + this.inputValidationWarningBackground = options.inputValidationWarningBackground; + this.inputValidationWarningForeground = options.inputValidationWarningForeground; + this.inputValidationErrorBorder = options.inputValidationErrorBorder; + this.inputValidationErrorBackground = options.inputValidationErrorBackground; + this.inputValidationErrorForeground = options.inputValidationErrorForeground; + + const flexibleHeight = !!options.flexibleHeight; + const flexibleWidth = !!options.flexibleWidth; + const flexibleMaxHeight = options.flexibleMaxHeight; + + this.buildDomNode(options.history || [], flexibleHeight, flexibleWidth, flexibleMaxHeight); + + if (parent) { + parent.appendChild(this.domNode); + } + + this.onkeydown(this.inputBox.inputElement, (e) => this._onKeyDown.fire(e)); + this.onkeyup(this.inputBox.inputElement, (e) => this._onKeyUp.fire(e)); + this.oninput(this.inputBox.inputElement, (e) => this._onInput.fire()); + this.onmousedown(this.inputBox.inputElement, (e) => this._onMouseDown.fire(e)); + } + + public enable(): void { + dom.removeClass(this.domNode, 'disabled'); + this.inputBox.enable(); + this.preserveCase.enable(); + } + + public disable(): void { + dom.addClass(this.domNode, 'disabled'); + this.inputBox.disable(); + this.preserveCase.disable(); + } + + public setFocusInputOnOptionClick(value: boolean): void { + this.fixFocusOnOptionClickEnabled = value; + } + + public setEnabled(enabled: boolean): void { + if (enabled) { + this.enable(); + } else { + this.disable(); + } + } + + public clear(): void { + this.clearValidation(); + this.setValue(''); + this.focus(); + } + + public getValue(): string { + return this.inputBox.value; + } + + public setValue(value: string): void { + if (this.inputBox.value !== value) { + this.inputBox.value = value; + } + } + + public onSearchSubmit(): void { + this.inputBox.addToHistory(); + } + + public style(styles: IReplaceInputStyles): void { + this.inputActiveOptionBorder = styles.inputActiveOptionBorder; + this.inputBackground = styles.inputBackground; + this.inputForeground = styles.inputForeground; + this.inputBorder = styles.inputBorder; + + this.inputValidationInfoBackground = styles.inputValidationInfoBackground; + this.inputValidationInfoForeground = styles.inputValidationInfoForeground; + this.inputValidationInfoBorder = styles.inputValidationInfoBorder; + this.inputValidationWarningBackground = styles.inputValidationWarningBackground; + this.inputValidationWarningForeground = styles.inputValidationWarningForeground; + this.inputValidationWarningBorder = styles.inputValidationWarningBorder; + this.inputValidationErrorBackground = styles.inputValidationErrorBackground; + this.inputValidationErrorForeground = styles.inputValidationErrorForeground; + this.inputValidationErrorBorder = styles.inputValidationErrorBorder; + + this.applyStyles(); + } + + protected applyStyles(): void { + if (this.domNode) { + const checkBoxStyles: ICheckboxStyles = { + inputActiveOptionBorder: this.inputActiveOptionBorder, + }; + this.preserveCase.style(checkBoxStyles); + + const inputBoxStyles: IInputBoxStyles = { + inputBackground: this.inputBackground, + inputForeground: this.inputForeground, + inputBorder: this.inputBorder, + inputValidationInfoBackground: this.inputValidationInfoBackground, + inputValidationInfoForeground: this.inputValidationInfoForeground, + inputValidationInfoBorder: this.inputValidationInfoBorder, + inputValidationWarningBackground: this.inputValidationWarningBackground, + inputValidationWarningForeground: this.inputValidationWarningForeground, + inputValidationWarningBorder: this.inputValidationWarningBorder, + inputValidationErrorBackground: this.inputValidationErrorBackground, + inputValidationErrorForeground: this.inputValidationErrorForeground, + inputValidationErrorBorder: this.inputValidationErrorBorder + }; + this.inputBox.style(inputBoxStyles); + } + } + + public select(): void { + this.inputBox.select(); + } + + public focus(): void { + this.inputBox.focus(); + } + + public getPreserveCase(): boolean { + return this.preserveCase.checked; + } + + public setPreserveCase(value: boolean): void { + this.preserveCase.checked = value; + } + + public focusOnPreserve(): void { + this.preserveCase.focus(); + } + + private _lastHighlightFindOptions: number = 0; + public highlightFindOptions(): void { + dom.removeClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions)); + this._lastHighlightFindOptions = 1 - this._lastHighlightFindOptions; + dom.addClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions)); + } + + private buildDomNode(history: string[], flexibleHeight: boolean, flexibleWidth: boolean, flexibleMaxHeight: number | undefined): void { + this.domNode = document.createElement('div'); + dom.addClass(this.domNode, 'monaco-findInput'); + + this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, { + ariaLabel: this.label || '', + placeholder: this.placeholder || '', + validationOptions: { + validation: this.validation + }, + inputBackground: this.inputBackground, + inputForeground: this.inputForeground, + inputBorder: this.inputBorder, + inputValidationInfoBackground: this.inputValidationInfoBackground, + inputValidationInfoForeground: this.inputValidationInfoForeground, + inputValidationInfoBorder: this.inputValidationInfoBorder, + inputValidationWarningBackground: this.inputValidationWarningBackground, + inputValidationWarningForeground: this.inputValidationWarningForeground, + inputValidationWarningBorder: this.inputValidationWarningBorder, + inputValidationErrorBackground: this.inputValidationErrorBackground, + inputValidationErrorForeground: this.inputValidationErrorForeground, + inputValidationErrorBorder: this.inputValidationErrorBorder, + history, + flexibleHeight, + flexibleWidth, + flexibleMaxHeight + })); + + this.preserveCase = this._register(new PreserveCaseCheckbox({ + appendTitle: '', + isChecked: false, + inputActiveOptionBorder: this.inputActiveOptionBorder + })); + this._register(this.preserveCase.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + this._register(this.preserveCase.onKeyDown(e => { + this._onPreserveCaseKeyDown.fire(e); + })); + + if (this._showOptionButtons) { + this.cachedOptionsWidth = this.preserveCase.width(); + } else { + this.cachedOptionsWidth = 0; + } + + // Arrow-Key support to navigate between options + let indexes = [this.preserveCase.domNode]; + this.onkeydown(this.domNode, (event: IKeyboardEvent) => { + if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) { + let index = indexes.indexOf(document.activeElement); + if (index >= 0) { + let newIndex: number = -1; + if (event.equals(KeyCode.RightArrow)) { + newIndex = (index + 1) % indexes.length; + } else if (event.equals(KeyCode.LeftArrow)) { + if (index === 0) { + newIndex = indexes.length - 1; + } else { + newIndex = index - 1; + } + } + + if (event.equals(KeyCode.Escape)) { + indexes[index].blur(); + } else if (newIndex >= 0) { + indexes[newIndex].focus(); + } + + dom.EventHelper.stop(event, true); + } + } + }); + + + let controls = document.createElement('div'); + controls.className = 'controls'; + controls.style.display = this._showOptionButtons ? 'block' : 'none'; + controls.appendChild(this.preserveCase.domNode); + + this.domNode.appendChild(controls); + } + + public validate(): void { + if (this.inputBox) { + this.inputBox.validate(); + } + } + + public showMessage(message: InputBoxMessage): void { + if (this.inputBox) { + this.inputBox.showMessage(message); + } + } + + public clearMessage(): void { + if (this.inputBox) { + this.inputBox.hideMessage(); + } + } + + private clearValidation(): void { + if (this.inputBox) { + this.inputBox.hideMessage(); + } + } + + public set width(newWidth: number) { + this.inputBox.paddingRight = this.cachedOptionsWidth; + this.inputBox.width = newWidth; + this.domNode.style.width = newWidth + 'px'; + } + + public dispose(): void { + super.dispose(); + } +} diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index eb8476e1b8b..4586d752d0b 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -7,7 +7,7 @@ import 'vs/css!./gridview'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Disposable } from 'vs/base/common/lifecycle'; import { tail2 as tail, equals } from 'vs/base/common/arrays'; -import { orthogonal, IView as IGridViewView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize, ILayoutController, LayoutController } from './gridview'; +import { orthogonal, IView as IGridViewView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles, IViewSize, LayoutController, IGridViewOptions } from './gridview'; import { Event } from 'vs/base/common/event'; import { InvisibleSizing } from 'vs/base/browser/ui/splitview/splitview'; @@ -193,11 +193,8 @@ export namespace Sizing { export interface IGridStyles extends IGridViewStyles { } -export interface IGridOptions { - readonly styles?: IGridStyles; - readonly proportionalLayout?: boolean; +export interface IGridOptions extends IGridViewOptions { readonly firstViewVisibleCachedSize?: number; - readonly layoutController?: ILayoutController; } export class Grid extends Disposable { @@ -277,6 +274,24 @@ export class Grid extends Disposable { this._addView(newView, viewSize, location); } + addViewAt(newView: T, size: number | DistributeSizing | InvisibleSizing, location: number[]): void { + if (this.views.has(newView)) { + throw new Error('Can\'t add same view twice'); + } + + let viewSize: number | GridViewSizing; + + if (typeof size === 'number') { + viewSize = size; + } else if (size.type === 'distribute') { + viewSize = GridViewSizing.Distribute; + } else { + viewSize = size; + } + + this._addView(newView, viewSize, location); + } + protected _addView(newView: T, size: number | GridViewSizing, location: number[]): void { this.views.set(newView, newView.element); this.gridview.addView(newView, size, location); @@ -308,6 +323,26 @@ export class Grid extends Disposable { } } + moveViewTo(view: T, location: number[]): void { + const sourceLocation = this.getViewLocation(view); + const [sourceParentLocation, from] = tail(sourceLocation); + const [targetParentLocation, to] = tail(location); + + if (equals(sourceParentLocation, targetParentLocation)) { + this.gridview.moveView(sourceParentLocation, from, to); + } else { + const size = this.getViewSize(view); + const orientation = getLocationOrientation(this.gridview.orientation, sourceLocation); + const cachedViewSize = this.getViewCachedVisibleSize(view); + const sizing = typeof cachedViewSize === 'undefined' + ? (orientation === Orientation.HORIZONTAL ? size.width : size.height) + : Sizing.Invisible(cachedViewSize); + + this.removeView(view); + this.addViewAt(view, sizing, location); + } + } + swapViews(from: T, to: T): void { const fromLocation = this.getViewLocation(from); const toLocation = this.getViewLocation(to); @@ -319,11 +354,20 @@ export class Grid extends Disposable { return this.gridview.resizeView(location, size); } - getViewSize(view: T): IViewSize { + getViewSize(view?: T): IViewSize { + if (!view) { + return this.gridview.getViewSize(); + } + const location = this.getViewLocation(view); return this.gridview.getViewSize(location); } + getViewCachedVisibleSize(view: T): number | undefined { + const location = this.getViewLocation(view); + return this.gridview.getViewCachedVisibleSize(location); + } + maximizeViewSize(view: T): void { const location = this.getViewLocation(view); this.gridview.maximizeViewSize(location); @@ -373,7 +417,7 @@ export class Grid extends Disposable { .map(node => node.view); } - private getViewLocation(view: T): number[] { + getViewLocation(view: T): number[] { const element = this.views.get(view); if (!element) { @@ -422,7 +466,7 @@ export interface ISerializableView extends IView { } export interface IViewDeserializer { - fromJSON(json: object | null): T; + fromJSON(json: any): T; } interface InitialLayoutContext { @@ -433,7 +477,7 @@ interface InitialLayoutContext { export interface ISerializedLeafNode { type: 'leaf'; - data: object | null; + data: any; size: number; visible?: boolean; } diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 81a6950a65b..934601cf17c 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -11,6 +11,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { $ } from 'vs/base/browser/dom'; import { tail2 as tail } from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; +import { clamp } from 'vs/base/common/numbers'; export { Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; export { Orientation } from 'vs/base/browser/ui/sash/sash'; @@ -75,6 +76,11 @@ export class LayoutController implements ILayoutController { constructor(public isLayoutEnabled: boolean) { } } +export class MultiplexLayoutController implements ILayoutController { + get isLayoutEnabled(): boolean { return this.layoutControllers.every(l => l.isLayoutEnabled); } + constructor(private layoutControllers: ILayoutController[]) { } +} + export interface IGridViewOptions { readonly styles?: IGridViewStyles; readonly proportionalLayout?: boolean; // default true @@ -169,6 +175,7 @@ class BranchNode implements ISplitView, IDisposable { constructor( readonly orientation: Orientation, + readonly layoutController: ILayoutController, styles: IGridViewStyles, readonly proportionalLayout: boolean, size: number = 0, @@ -180,7 +187,7 @@ class BranchNode implements ISplitView, IDisposable { this.element = $('.monaco-grid-branch-node'); this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout }); - this.splitview.layout(size); + this.splitview.layout(size, orthogonalSize); const onDidSashReset = Event.map(this.splitview.onDidSashReset, i => [i]); this.splitviewSashResetDisposable = onDidSashReset(this._onDidSashReset.fire, this._onDidSashReset); @@ -197,12 +204,20 @@ class BranchNode implements ISplitView, IDisposable { } } - layout(size: number): void { + layout(size: number, orthogonalSize: number | undefined): void { + if (!this.layoutController.isLayoutEnabled) { + return; + } + + if (typeof orthogonalSize !== 'number') { + throw new Error('Invalid state'); + } + + // branch nodes should flip the normal/orthogonal directions + this._size = orthogonalSize; this._orthogonalSize = size; - for (const child of this.children) { - child.orthogonalLayout(size); - } + this.splitview.layout(orthogonalSize, size); } setVisible(visible: boolean): void { @@ -211,11 +226,6 @@ class BranchNode implements ISplitView, IDisposable { } } - orthogonalLayout(size: number): void { - this._size = size; - this.splitview.layout(size); - } - addChild(node: Node, size: number | Sizing, index: number): void { if (index < 0 || index > this.children.length) { throw new Error('Invalid index'); @@ -277,9 +287,7 @@ class BranchNode implements ISplitView, IDisposable { throw new Error('Invalid from index'); } - if (to < 0 || to > this.children.length) { - throw new Error('Invalid to index'); - } + to = clamp(to, 0, this.children.length); if (from < to) { to--; @@ -300,9 +308,7 @@ class BranchNode implements ISplitView, IDisposable { throw new Error('Invalid from index'); } - if (to < 0 || to >= this.children.length) { - throw new Error('Invalid to index'); - } + to = clamp(to, 0, this.children.length); this.splitview.swapViews(from, to); [this.children[from].orthogonalStartSash, this.children[from].orthogonalEndSash, this.children[to].orthogonalStartSash, this.children[to].orthogonalEndSash] = [this.children[to].orthogonalStartSash, this.children[to].orthogonalEndSash, this.children[from].orthogonalStartSash, this.children[from].orthogonalEndSash]; @@ -350,7 +356,12 @@ class BranchNode implements ISplitView, IDisposable { throw new Error('Invalid index'); } + if (this.splitview.isViewVisible(index) === visible) { + return; + } + this.splitview.setViewVisible(index, visible); + this._onDidChange.fire(undefined); } getChildCachedVisibleSize(index: number): number | undefined { @@ -442,9 +453,6 @@ class LeafNode implements ISplitView, IDisposable { private _size: number = 0; get size(): number { return this._size; } - private _cachedVisibleSize: number | undefined; - get cachedVisibleSize(): number | undefined { return this._cachedVisibleSize; } - private _orthogonalSize: number; get orthogonalSize(): number { return this._orthogonalSize; } @@ -544,34 +552,26 @@ class LeafNode implements ISplitView, IDisposable { // noop } - layout(size: number): void { - this._size = size; - - if (this.layoutController.isLayoutEnabled) { - this.view.layout(this.width, this.height, orthogonal(this.orientation)); + layout(size: number, orthogonalSize: number | undefined): void { + if (!this.layoutController.isLayoutEnabled) { + return; } + + if (typeof orthogonalSize !== 'number') { + throw new Error('Invalid state'); + } + + this._size = size; + this._orthogonalSize = orthogonalSize; + this.view.layout(this.width, this.height, orthogonal(this.orientation)); } setVisible(visible: boolean): void { - if (visible) { - this._cachedVisibleSize = undefined; - } else { - this._cachedVisibleSize = this._size; - } - if (this.view.setVisible) { this.view.setVisible(visible); } } - orthogonalLayout(size: number): void { - this._orthogonalSize = size; - - if (this.layoutController.isLayoutEnabled) { - this.view.layout(this.width, this.height, orthogonal(this.orientation)); - } - } - dispose(): void { } } @@ -579,7 +579,7 @@ type Node = BranchNode | LeafNode; function flipNode(node: T, size: number, orthogonalSize: number): T { if (node instanceof BranchNode) { - const result = new BranchNode(orthogonal(node.orientation), node.styles, node.proportionalLayout, size, orthogonalSize); + const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.proportionalLayout, size, orthogonalSize); let totalSize = 0; @@ -600,7 +600,7 @@ function flipNode(node: T, size: number, orthogonalSize: number) return result as T; } else { - return new LeafNode((node as LeafNode).view, orthogonal(node.orientation), (node as LeafNode).layoutController, orthogonalSize) as T; + return new LeafNode((node as LeafNode).view, orthogonal(node.orientation), node.layoutController, orthogonalSize) as T; } } @@ -610,7 +610,7 @@ export class GridView implements IDisposable { private styles: IGridViewStyles; private proportionalLayout: boolean; - private _root: BranchNode; + private _root!: BranchNode; private onDidSashResetRelay = new Relay(); readonly onDidSashReset: Event = this.onDidSashResetRelay.event; @@ -645,8 +645,7 @@ export class GridView implements IDisposable { const { size, orthogonalSize } = this._root; this.root = flipNode(this._root, orthogonalSize, size); - this.root.layout(size); - this.root.orthogonalLayout(orthogonalSize); + this.root.layout(size, orthogonalSize); } get width(): number { return this.root.width; } @@ -660,14 +659,25 @@ export class GridView implements IDisposable { private _onDidChange = new Relay(); readonly onDidChange = this._onDidChange.event; + /** + * The first layout controller makes sure layout only propagates + * to the views after the very first call to gridview.layout() + */ + private firstLayoutController: LayoutController; private layoutController: LayoutController; constructor(options: IGridViewOptions = {}) { this.element = $('.monaco-grid-view'); this.styles = options.styles || defaultStyles; this.proportionalLayout = typeof options.proportionalLayout !== 'undefined' ? !!options.proportionalLayout : true; - this.root = new BranchNode(Orientation.VERTICAL, this.styles, this.proportionalLayout); - this.layoutController = options.layoutController || new LayoutController(true); + + this.firstLayoutController = new LayoutController(false); + this.layoutController = new MultiplexLayoutController([ + this.firstLayoutController, + ...(options.layoutController ? [options.layoutController] : []) + ]); + + this.root = new BranchNode(Orientation.VERTICAL, this.layoutController, this.styles, this.proportionalLayout); } style(styles: IGridViewStyles): void { @@ -676,9 +686,10 @@ export class GridView implements IDisposable { } layout(width: number, height: number): void { + this.firstLayoutController.isLayoutEnabled = true; + const [size, orthogonalSize] = this.root.orientation === Orientation.HORIZONTAL ? [height, width] : [width, height]; - this.root.layout(size); - this.root.orthogonalLayout(orthogonalSize); + this.root.layout(size, orthogonalSize); } addView(view: IView, size: number | Sizing, location: number[]): void { @@ -705,9 +716,8 @@ export class GridView implements IDisposable { grandParent.removeChild(parentIndex); - const newParent = new BranchNode(parent.orientation, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize); + const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize); grandParent.addChild(newParent, parent.size, parentIndex); - newParent.orthogonalLayout(parent.orthogonalSize); const newSibling = new LeafNode(parent.view, grandParent.orientation, this.layoutController, parent.size); newParent.addChild(newSibling, newSiblingSize, 0); @@ -765,6 +775,7 @@ export class GridView implements IDisposable { const [, parentIndex] = tail(rest); const sibling = parent.children[0]; + const isSiblingVisible = parent.isChildVisible(0); parent.removeChild(0); const sizes = grandParent.children.map((_, i) => grandParent.getChildSize(i)); @@ -779,7 +790,8 @@ export class GridView implements IDisposable { } } else { const newSibling = new LeafNode(sibling.view, orthogonal(sibling.orientation), this.layoutController, sibling.size); - grandParent.addChild(newSibling, sibling.orthogonalSize, parentIndex); + const sizing = isSiblingVisible ? sibling.orthogonalSize : Sizing.Invisible(sibling.orthogonalSize); + grandParent.addChild(newSibling, sizing, parentIndex); } for (let i = 0; i < sizes.length; i++) { @@ -836,9 +848,6 @@ export class GridView implements IDisposable { fromParent.addChild(toNode, fromSize, fromIndex); toParent.addChild(fromNode, toSize, toIndex); - - fromParent.layout(fromParent.orthogonalSize); - toParent.layout(toParent.orthogonalSize); } } @@ -868,11 +877,26 @@ export class GridView implements IDisposable { } } - getViewSize(location: number[]): IViewSize { + getViewSize(location?: number[]): IViewSize { + if (!location) { + return { width: this.root.width, height: this.root.height }; + } + const [, node] = this.getNode(location); return { width: node.width, height: node.height }; } + getViewCachedVisibleSize(location: number[]): number | undefined { + const [rest, index] = tail(location); + const [, parent] = this.getNode(rest); + + if (!(parent instanceof BranchNode)) { + throw new Error('Invalid location'); + } + + return parent.getChildCachedVisibleSize(index); + } + maximizeViewSize(location: number[]): void { const [ancestors, node] = this.getNode(location); @@ -929,12 +953,13 @@ export class GridView implements IDisposable { return this._getViews(node, this.orientation, { top: 0, left: 0, width: this.width, height: this.height }); } - private _getViews(node: Node, orientation: Orientation, box: Box): GridNode { + private _getViews(node: Node, orientation: Orientation, box: Box, cachedVisibleSize?: number): GridNode { if (node instanceof LeafNode) { - return { view: node.view, box, cachedVisibleSize: node.cachedVisibleSize }; + return { view: node.view, box, cachedVisibleSize }; } const children: GridNode[] = []; + let i = 0; let offset = 0; for (const child of node.children) { @@ -942,8 +967,9 @@ export class GridView implements IDisposable { const childBox: Box = orientation === Orientation.HORIZONTAL ? { top: box.top, left: box.left + offset, width: child.width, height: box.height } : { top: box.top + offset, left: box.left, width: box.width, height: child.height }; + const cachedVisibleSize = node.getChildCachedVisibleSize(i++); - children.push(this._getViews(child, childOrientation, childBox)); + children.push(this._getViews(child, childOrientation, childBox, cachedVisibleSize)); offset += orientation === Orientation.HORIZONTAL ? child.width : child.height; } diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index a61b1bba525..ec4a3c2f6de 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -12,7 +12,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; export interface IIconLabelCreationOptions { supportHighlights?: boolean; supportDescriptionHighlights?: boolean; - donotSupportOcticons?: boolean; + supportOcticons?: boolean; } export interface IIconLabelValueOptions { @@ -100,13 +100,13 @@ export class IconLabel extends Disposable { this.labelDescriptionContainer = this._register(new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container')))); if (options && options.supportHighlights) { - this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !options.donotSupportOcticons); + this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !!options.supportOcticons); } else { this.labelNode = this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')))); } if (options && options.supportDescriptionHighlights) { - this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !options.donotSupportOcticons); + this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !!options.supportOcticons); } else { this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')))); } diff --git a/src/vs/base/browser/ui/inputbox/inputBox.css b/src/vs/base/browser/ui/inputbox/inputBox.css index 294ca475662..dc1240dfbc5 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.css +++ b/src/vs/base/browser/ui/inputbox/inputBox.css @@ -58,7 +58,12 @@ .monaco-inputbox > .wrapper > textarea.input { display: block; - overflow: hidden; + -ms-overflow-style: none; /* IE 10+ */ + overflow: -moz-scrollbars-none; /* Firefox */ +} + +.monaco-inputbox > .wrapper > textarea.input::-webkit-scrollbar { + display: none; } .monaco-inputbox > .wrapper > .mirror { @@ -116,4 +121,4 @@ background-repeat: no-repeat; width: 16px; height: 16px; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 011d1f56dc1..430f250ce38 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -8,7 +8,8 @@ import 'vs/css!./inputBox'; import * as nls from 'vs/nls'; import * as Bal from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; -import { RenderOptions, renderFormattedText, renderText } from 'vs/base/browser/htmlContentRenderer'; +import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; +import { renderFormattedText, renderText } from 'vs/base/browser/formattedTextRenderer'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IAction } from 'vs/base/common/actions'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -19,6 +20,9 @@ import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; import { HistoryNavigator } from 'vs/base/common/history'; import { IHistoryNavigationWidget } from 'vs/base/browser/history'; +import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { domEvent } from 'vs/base/browser/event'; const $ = dom.$; @@ -28,6 +32,8 @@ export interface IInputOptions extends IInputBoxStyles { readonly type?: string; readonly validationOptions?: IInputValidationOptions; readonly flexibleHeight?: boolean; + readonly flexibleWidth?: boolean; + readonly flexibleMaxHeight?: number; readonly actions?: ReadonlyArray; } @@ -86,7 +92,6 @@ export class InputBox extends Widget { private contextViewProvider?: IContextViewProvider; element: HTMLElement; private input: HTMLInputElement; - private mirror: HTMLElement; private actionbar?: ActionBar; private options: IInputOptions; private message: IMessage | null; @@ -94,7 +99,12 @@ export class InputBox extends Widget { private ariaLabel: string; private validation?: IInputValidator; private state: 'idle' | 'open' | 'closed' = 'idle'; - private cachedHeight: number | null; + + private mirror: HTMLElement | undefined; + private cachedHeight: number | undefined; + private cachedContentHeight: number | undefined; + private maxHeight: number = Number.POSITIVE_INFINITY; + private scrollableElement: ScrollableElement | undefined; private inputBackground?: Color; private inputForeground?: Color; @@ -123,7 +133,6 @@ export class InputBox extends Widget { this.options = options || Object.create(null); mixin(this.options, defaultOpts, false); this.message = null; - this.cachedHeight = null; this.placeholder = this.options.placeholder || ''; this.ariaLabel = this.options.ariaLabel || ''; @@ -159,8 +168,33 @@ export class InputBox extends Widget { this.onblur(this.input, () => dom.removeClass(this.element, 'synthetic-focus')); if (this.options.flexibleHeight) { + this.maxHeight = typeof this.options.flexibleMaxHeight === 'number' ? this.options.flexibleMaxHeight : Number.POSITIVE_INFINITY; + this.mirror = dom.append(wrapper, $('div.mirror')); this.mirror.innerHTML = ' '; + + this.scrollableElement = new ScrollableElement(this.element, { vertical: ScrollbarVisibility.Auto }); + + if (this.options.flexibleWidth) { + this.input.setAttribute('wrap', 'off'); + this.mirror.style.whiteSpace = 'pre'; + this.mirror.style.wordWrap = 'initial'; + } + + dom.append(container, this.scrollableElement.getDomNode()); + this._register(this.scrollableElement); + + // from ScrollableElement to DOM + this._register(this.scrollableElement.onScroll(e => this.input.scrollTop = e.scrollTop)); + + const onSelectionChange = Event.filter(domEvent(document, 'selectionchange'), () => { + const selection = document.getSelection(); + return !!selection && selection.anchorNode === wrapper; + }); + + // from DOM to ScrollableElement + this._register(onSelectionChange(this.updateScrollDimensions, this)); + this._register(this.onDidHeightChange(this.updateScrollDimensions, this)); } else { this.input.type = this.options.type || 'text'; this.input.setAttribute('wrap', 'off'); @@ -230,7 +264,7 @@ export class InputBox extends Widget { } } - public get mirrorElement(): HTMLElement { + public get mirrorElement(): HTMLElement | undefined { return this.mirror; } @@ -250,7 +284,7 @@ export class InputBox extends Widget { } public get height(): number { - return this.cachedHeight === null ? dom.getTotalHeight(this.element) : this.cachedHeight; + return typeof this.cachedHeight === 'number' ? this.cachedHeight : dom.getTotalHeight(this.element); } public focus(): void { @@ -295,12 +329,49 @@ export class InputBox extends Widget { } public set width(width: number) { - this.input.style.width = width + 'px'; + if (this.options.flexibleHeight && this.options.flexibleWidth) { + // textarea with horizontal scrolling + let horizontalPadding = 0; + if (this.mirror) { + const paddingLeft = parseFloat(this.mirror.style.paddingLeft || '') || 0; + const paddingRight = parseFloat(this.mirror.style.paddingRight || '') || 0; + horizontalPadding = paddingLeft + paddingRight; + } + this.input.style.width = (width - horizontalPadding) + 'px'; + } else { + this.input.style.width = width + 'px'; + } + if (this.mirror) { this.mirror.style.width = width + 'px'; } } + public set paddingRight(paddingRight: number) { + if (this.options.flexibleHeight && this.options.flexibleWidth) { + this.input.style.width = `calc(100% - ${paddingRight}px)`; + } else { + this.input.style.paddingRight = paddingRight + 'px'; + } + + if (this.mirror) { + this.mirror.style.paddingRight = paddingRight + 'px'; + } + } + + private updateScrollDimensions(): void { + if (typeof this.cachedContentHeight !== 'number' || typeof this.cachedHeight !== 'number') { + return; + } + + const scrollHeight = this.cachedContentHeight; + const height = this.cachedHeight; + const scrollTop = this.input.scrollTop; + + this.scrollableElement!.setScrollDimensions({ scrollHeight, height }); + this.scrollableElement!.setScrollPosition({ scrollTop }); + } + public showMessage(message: IMessage, force?: boolean): void { this.message = message; @@ -400,7 +471,7 @@ export class InputBox extends Widget { div = dom.append(container, $('.monaco-inputbox-container')); layout(); - const renderOptions: RenderOptions = { + const renderOptions: MarkdownRenderOptions = { inline: true, className: 'monaco-inputbox-message' }; @@ -510,12 +581,13 @@ export class InputBox extends Widget { return; } - const previousHeight = this.cachedHeight; - this.cachedHeight = dom.getTotalHeight(this.mirror); + const previousHeight = this.cachedContentHeight; + this.cachedContentHeight = dom.getTotalHeight(this.mirror); - if (previousHeight !== this.cachedHeight) { + if (previousHeight !== this.cachedContentHeight) { + this.cachedHeight = Math.min(this.cachedContentHeight, this.maxHeight); this.input.style.height = this.cachedHeight + 'px'; - this._onDidHeightChange.fire(this.cachedHeight); + this._onDidHeightChange.fire(this.cachedContentHeight); } } diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 90736af0a46..788c0045f2d 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -73,7 +73,7 @@ class PagedRenderer implements IListRenderer implements IDisposable { private list: List; - private _model: IPagedModel; + private _model!: IPagedModel; constructor( container: HTMLElement, diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index d42777bec4b..4f5394f773d 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -57,6 +57,7 @@ export interface IListViewOptions { readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; readonly ariaProvider?: IAriaProvider; + readonly additionalScrollHeight?: number; } const DefaultOptions = { @@ -163,19 +164,19 @@ export class ListView implements ISpliceable, IDisposable { private lastRenderTop: number; private lastRenderHeight: number; private renderWidth = 0; - private gesture: Gesture; private rowsContainer: HTMLElement; private scrollableElement: ScrollableElement; - private _scrollHeight: number; + private _scrollHeight: number = 0; private scrollableElementUpdateDisposable: IDisposable | null = null; private scrollableElementWidthDelayer = new Delayer(50); private splicing = false; private dragOverAnimationDisposable: IDisposable | undefined; private dragOverAnimationStopDisposable: IDisposable = Disposable.None; - private dragOverMouseY: number; + private dragOverMouseY: number = 0; private setRowLineHeight: boolean; private supportDynamicHeights: boolean; private horizontalScrolling: boolean; + private additionalScrollHeight: number; private ariaProvider: IAriaProvider; private scrollWidth: number | undefined; private canUseTranslate3d: boolean | undefined = undefined; @@ -229,6 +230,8 @@ export class ListView implements ISpliceable, IDisposable { this.horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling); DOM.toggleClass(this.domNode, 'horizontal-scrolling', this.horizontalScrolling); + this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight; + this.ariaProvider = options.ariaProvider || { getSetSize: (e, i, length) => length, getPosInSet: (_, index) => index + 1 }; this.rowsContainer = document.createElement('div'); @@ -245,7 +248,7 @@ export class ListView implements ISpliceable, IDisposable { this.domNode.appendChild(this.scrollableElement.getDomNode()); container.appendChild(this.domNode); - this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache]; + this.disposables = [this.rangeMap, this.scrollableElement, this.cache]; this.scrollableElement.onScroll(this.onScroll, this, this.disposables); domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables); @@ -689,7 +692,7 @@ export class ListView implements ISpliceable, IDisposable { } get scrollHeight(): number { - return this._scrollHeight + (this.horizontalScrolling ? 10 : 0); + return this._scrollHeight + (this.horizontalScrolling ? 10 : 0) + this.additionalScrollHeight; } // Events diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 6b920ee1e7a..6e82c6de867 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -519,7 +519,7 @@ const DefaultOpenController: IOpenController = { export class MouseController implements IDisposable { private multipleSelectionSupport: boolean; - readonly multipleSelectionController: IMultipleSelectionController; + readonly multipleSelectionController: IMultipleSelectionController | undefined; private openController: IOpenController; private mouseSupport: boolean; private readonly disposables = new DisposableStore(); @@ -618,7 +618,7 @@ export class MouseController implements IDisposable { } } - private onDoubleClick(e: IListMouseEvent): void { + protected onDoubleClick(e: IListMouseEvent): void { if (isInputElement(e.browserEvent.target as HTMLElement)) { return; } @@ -653,6 +653,8 @@ export class MouseController implements IDisposable { const selection = this.list.getSelection(); const newSelection = selection.filter(i => i !== focus); + this.list.setFocus([focus]); + if (selection.length === newSelection.length) { this.list.setSelection([...newSelection, focus], e.browserEvent); } else { diff --git a/src/vs/base/browser/ui/menu/check.svg b/src/vs/base/browser/ui/menu/check.svg index 865cc83c347..cea818ef596 100644 --- a/src/vs/base/browser/ui/menu/check.svg +++ b/src/vs/base/browser/ui/menu/check.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 7913bb42fd5..49d35fd57a8 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -20,22 +20,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; -function createMenuMnemonicRegExp() { - try { - return new RegExp('\\(&([^\\s&])\\)|(? { + if (!this.element) { + return; + } + + this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => { + EventHelper.stop(e, true); + this.onClick(e); + })); + }, 50); + + this._register(this.runOnceToEnableMouseUp); } render(container: HTMLElement): void { @@ -425,10 +426,8 @@ class BaseMenuActionViewItem extends BaseActionViewItem { append(this.item, $('span.keybinding')).textContent = this.options.keybinding; } - this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => { - EventHelper.stop(e, true); - this.onClick(e); - })); + // Adds mouse up listener to actually run the action + this.runOnceToEnableMouseUp.schedule(); this.updateClass(); this.updateLabel(); @@ -467,9 +466,23 @@ class BaseMenuActionViewItem extends BaseActionViewItem { const matches = MENU_MNEMONIC_REGEX.exec(label); if (matches) { - label = strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, ''); + label = strings.escape(label); + + // This is global, reset it + MENU_ESCAPED_MNEMONIC_REGEX.lastIndex = 0; + let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label); + + // We can't use negative lookbehind so if we match our negative and skip + while (escMatch && escMatch[1]) { + escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(label); + } + + if (escMatch) { + label = `${label.substr(0, escMatch.index)}${label.substr(escMatch.index + escMatch[0].length)}`; + } + label = label.replace(/&&/g, '&'); - this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[2]).toLocaleLowerCase()); + this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[3]).toLocaleLowerCase()); } else { label = label.replace(/&&/g, '&'); } @@ -626,8 +639,11 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => { let event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Enter)) { - EventHelper.stop(e, true); + + if (document.activeElement === this.item) { + if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Enter)) { + EventHelper.stop(e, true); + } } })); @@ -799,7 +815,7 @@ export function cleanMnemonic(label: string): string { return label; } - const mnemonicInText = matches[0].charAt(0) === '&'; + const mnemonicInText = !matches[1]; - return label.replace(regex, mnemonicInText ? '$2' : '').trim(); -} \ No newline at end of file + return label.replace(regex, mnemonicInText ? '$2$3' : '').trim(); +} diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 1ffcb47b9a1..c6644247b2b 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -209,7 +209,7 @@ export class MenuBar extends Disposable { // Register mnemonics if (mnemonicMatches) { - let mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[2]; + let mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[3]; this.registerMnemonic(this.menuCache.length, mnemonic); } @@ -472,15 +472,34 @@ export class MenuBar extends Disposable { const cleanMenuLabel = cleanMnemonic(label); // Update the button label to reflect mnemonics - titleElement.innerHTML = this.options.enableMnemonics ? - strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, '').replace(/&&/g, '&') : - cleanMenuLabel.replace(/&&/g, '&'); + + if (this.options.enableMnemonics) { + let innerHtml = strings.escape(label); + + // This is global so reset it + MENU_ESCAPED_MNEMONIC_REGEX.lastIndex = 0; + let escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(innerHtml); + + // We can't use negative lookbehind so we match our negative and skip + while (escMatch && escMatch[1]) { + escMatch = MENU_ESCAPED_MNEMONIC_REGEX.exec(innerHtml); + } + + if (escMatch) { + innerHtml = `${innerHtml.substr(0, escMatch.index)}${innerHtml.substr(escMatch.index + escMatch[0].length)}`; + } + + innerHtml = innerHtml.replace(/&&/g, '&'); + titleElement.innerHTML = innerHtml; + } else { + titleElement.innerHTML = cleanMenuLabel.replace(/&&/g, '&'); + } let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label); // Register mnemonics if (mnemonicMatches) { - let mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[2]; + let mnemonic = !!mnemonicMatches[1] ? mnemonicMatches[1] : mnemonicMatches[3]; if (this.options.enableMnemonics) { buttonElement.setAttribute('aria-keyshortcuts', 'Alt+' + mnemonic.toLocaleLowerCase()); @@ -1012,4 +1031,4 @@ class ModifierKeyEmitter extends Emitter { super.dispose(); this._subscriptions.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts b/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts index 0b1154a98fd..75b3ba027dd 100644 --- a/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts +++ b/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./octicons/octicons'; +import 'vs/css!./octicons/octicons2'; +import 'vs/css!./octicons/octicons-main'; import 'vs/css!./octicons/octicons-animations'; import { escape } from 'vs/base/common/strings'; @@ -30,4 +32,4 @@ export class OcticonLabel { set title(title: string) { this._container.title = title; } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons-main.css b/src/vs/base/browser/ui/octiconLabel/octicons/octicons-main.css new file mode 100644 index 00000000000..841e5e575cd --- /dev/null +++ b/src/vs/base/browser/ui/octiconLabel/octicons/octicons-main.css @@ -0,0 +1,21 @@ +body[data-octicons-update="enabled"] { + --version: octicons2; +} + +body { + --version: octicons; +} + +.octicon, .mega-octicon { + font-family: var(--version) !important; +} + +body[data-octicons-update="enabled"] .monaco-workbench .part.statusbar > .items-container > .statusbar-item span.octicon { + font-size: 16px; +} + +body[data-octicons-update="enabled"] .monaco-workbench .part.statusbar > .items-container > .statusbar-item > a { + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css index d9cc1b7a4fa..29ed93db1b1 100644 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css +++ b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css @@ -1,7 +1,7 @@ @font-face { font-family: "octicons"; - src: url("./octicons.ttf?1b0f2a9535896866c74dd24eedeb4374") format("truetype"), -url("./octicons.svg?1b0f2a9535896866c74dd24eedeb4374#octicons") format("svg"); + src: url("./octicons.ttf?dda6b6d46f87b1fa91a76fc0389eeb1d") format("truetype"), +url("./octicons.svg?dda6b6d46f87b1fa91a76fc0389eeb1d#octicons") format("svg"); } .octicon, .mega-octicon { @@ -169,7 +169,7 @@ url("./octicons.svg?1b0f2a9535896866c74dd24eedeb4374#octicons") format("svg"); .octicon-person-outline:before { content: "\f018" } .octicon-pin:before { content: "\f041" } .octicon-plug:before { content: "\f0d4" } -.octicon-plus-small:before { content: "\f28a" } +.octicon-plus-small:before { content: "\f05d" } .octicon-plus:before { content: "\f05d" } .octicon-primitive-dot:before { content: "\f052" } .octicon-primitive-square:before { content: "\f053" } @@ -233,16 +233,19 @@ url("./octicons.svg?1b0f2a9535896866c74dd24eedeb4374#octicons") format("svg"); .octicon-watch:before { content: "\f0e0" } .octicon-x:before { content: "\f081" } .octicon-zap:before { content: "\26a1" } +.octicon-error:before { content: "\26a2" } +.octicon-eye-closed:before { content: "\26a3" } +.octicon-fold-down:before { content: "\26a4" } +.octicon-fold-up:before { content: "\26a5" } +.octicon-github-action:before { content: "\26a6" } +.octicon-info-outline:before { content: "\26a7" } +.octicon-play:before { content: "\26a8" } +.octicon-remote:before { content: "\26a9" } +.octicon-request-changes:before { content: "\26aa" } +.octicon-smiley-outline:before { content: "\f27d" } +.octicon-warning:before { content: "\f02d" } +.octicon-controls:before { content: "\26ad" } +.octicon-event:before { content: "\26ae" } +.octicon-record-keys:before { content: "\26af" } .octicon-archive:before { content: "\f101" } .octicon-arrow-both:before { content: "\f102" } -.octicon-error:before { content: "\f103" } -.octicon-eye-closed:before { content: "\f104" } -.octicon-fold-down:before { content: "\f105" } -.octicon-fold-up:before { content: "\f106" } -.octicon-github-action:before { content: "\f107" } -.octicon-info-outline:before { content: "\f108" } -.octicon-play:before { content: "\f109" } -.octicon-remote:before { content: "\f10a" } -.octicon-request-changes:before { content: "\f10b" } -.octicon-smiley-outline:before { content: "\f10c" } -.octicon-warning:before { content: "\f10d" } diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg index 3f4ab4f1807..48f7d1b2220 100644 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg +++ b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg @@ -167,10 +167,10 @@ unicode="" horiz-adv-x="750" d=" M687.5 507.5H62.5C28.125 507.5 0 479.375 0 445V195C0 160.625 28.125 132.5 62.5 132.5H687.5C721.875 132.5 750 160.625 750 195V445C750 479.375 721.875 507.5 687.5 507.5zM250 257.5H125V382.5H250V257.5zM437.5 257.5H312.5V382.5H437.5V257.5zM625 257.5H500V382.5H625V257.5z" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.ttf b/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.ttf new file mode 100644 index 00000000000..daa9a772ad2 Binary files /dev/null and b/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.ttf differ diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index 874bc9482e1..e394df1e790 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -61,7 +61,7 @@ export class Sash extends Disposable { private el: HTMLElement; private layoutProvider: ISashLayoutProvider; private hidden: boolean; - private orientation: Orientation; + private orientation!: Orientation; private _state: SashState = SashState.Enabled; get state(): SashState { return this._state; } diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts index 21ccdfa0eb7..4e9245ade07 100644 --- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts @@ -49,7 +49,7 @@ export abstract class AbstractScrollbar extends Widget { private _mouseMoveMonitor: GlobalMouseMoveMonitor; public domNode: FastDomNode; - public slider: FastDomNode; + public slider!: FastDomNode; protected _shouldRender: boolean; diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 1c1ded9eb2c..f7216dfdb10 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -148,9 +148,9 @@ export abstract class AbstractScrollableElement extends Widget { private readonly _horizontalScrollbar: HorizontalScrollbar; private readonly _domNode: HTMLElement; - private readonly _leftShadowDomNode: FastDomNode; - private readonly _topShadowDomNode: FastDomNode; - private readonly _topLeftShadowDomNode: FastDomNode; + private readonly _leftShadowDomNode: FastDomNode | null; + private readonly _topShadowDomNode: FastDomNode | null; + private readonly _topLeftShadowDomNode: FastDomNode | null; private readonly _listenOnDomNode: HTMLElement; @@ -207,6 +207,10 @@ export abstract class AbstractScrollableElement extends Widget { this._topLeftShadowDomNode = createFastDomNode(document.createElement('div')); this._topLeftShadowDomNode.setClassName('shadow top-left-corner'); this._domNode.appendChild(this._topLeftShadowDomNode.domNode); + } else { + this._leftShadowDomNode = null; + this._topShadowDomNode = null; + this._topLeftShadowDomNode = null; } this._listenOnDomNode = this._options.listenOnDomNode || this._domNode; @@ -430,9 +434,9 @@ export abstract class AbstractScrollableElement extends Widget { let enableTop = scrollState.scrollTop > 0; let enableLeft = scrollState.scrollLeft > 0; - this._leftShadowDomNode.setClassName('shadow' + (enableLeft ? ' left' : '')); - this._topShadowDomNode.setClassName('shadow' + (enableTop ? ' top' : '')); - this._topLeftShadowDomNode.setClassName('shadow top-left-corner' + (enableTop ? ' top' : '') + (enableLeft ? ' left' : '')); + this._leftShadowDomNode!.setClassName('shadow' + (enableLeft ? ' left' : '')); + this._topShadowDomNode!.setClassName('shadow' + (enableTop ? ' top' : '')); + this._topLeftShadowDomNode!.setClassName('shadow top-left-corner' + (enableTop ? ' top' : '') + (enableLeft ? ' left' : '')); } } diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index 7832680e2ae..d450f76d360 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -8,7 +8,7 @@ import 'vs/css!./selectBox'; import { Event } from 'vs/base/common/event'; import { Widget } from 'vs/base/browser/ui/widget'; import { Color } from 'vs/base/common/color'; -import { deepClone, mixin } from 'vs/base/common/objects'; +import { deepClone } from 'vs/base/common/objects'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { SelectBoxNative } from 'vs/base/browser/ui/selectBox/selectBoxNative'; @@ -72,14 +72,11 @@ export interface ISelectData { } export class SelectBox extends Widget implements ISelectBoxDelegate { - private styles: ISelectBoxStyles; private selectBoxDelegate: ISelectBoxDelegate; constructor(options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles), selectBoxOptions?: ISelectBoxOptions) { super(); - mixin(this.styles, defaultStyles, false); - // Default to native SelectBox for OSX unless overridden if (isMacintosh && !(selectBoxOptions && selectBoxOptions.useCustomDrawn)) { this.selectBoxDelegate = new SelectBoxNative(options, selected, styles, selectBoxOptions); diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index f26942d54e9..d144eb63050 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -18,7 +18,7 @@ import { domEvent } from 'vs/base/browser/event'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox'; import { isMacintosh } from 'vs/base/common/platform'; -import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; const $ = dom.$; @@ -89,21 +89,21 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private _isVisible: boolean; private selectBoxOptions: ISelectBoxOptions; private selectElement: HTMLSelectElement; - private options: ISelectOptionItem[]; + private options: ISelectOptionItem[] = []; private selected: number; private readonly _onDidSelect: Emitter; private styles: ISelectBoxStyles; - private listRenderer: SelectListRenderer; - private contextViewProvider: IContextViewProvider; - private selectDropDownContainer: HTMLElement; - private styleElement: HTMLStyleElement; - private selectList: List; - private selectDropDownListContainer: HTMLElement; - private widthControlElement: HTMLElement; - private _currentSelection: number; - private _dropDownPosition: AnchorPosition; + private listRenderer!: SelectListRenderer; + private contextViewProvider!: IContextViewProvider; + private selectDropDownContainer!: HTMLElement; + private styleElement!: HTMLStyleElement; + private selectList!: List; + private selectDropDownListContainer!: HTMLElement; + private widthControlElement!: HTMLElement; + private _currentSelection = 0; + private _dropDownPosition!: AnchorPosition; private _hasDetails: boolean = false; - private selectionDetailsPane: HTMLElement; + private selectionDetailsPane!: HTMLElement; private _skipLayout: boolean = false; private _sticky: boolean = false; // for dev purposes only @@ -241,7 +241,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } public setOptions(options: ISelectOptionItem[], selected?: number): void { - if (!this.options || !arrays.equals(this.options, options)) { + if (!arrays.equals(this.options, options)) { this.options = options; this.selectElement.options.length = 0; this._hasDetails = false; @@ -266,7 +266,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi // Mirror options in drop-down // Populate select list for non-native select mode - if (this.selectList && !!this.options) { + if (this.selectList) { this.selectList.splice(0, this.selectList.length, this.options); } } @@ -689,7 +689,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private setWidthControlElement(container: HTMLElement): number { let elementWidth = 0; - if (container && !!this.options) { + if (container) { let longest = 0; let longestLength = 0; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts index c17caf43770..afaed056d4c 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -16,7 +16,7 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { private selectElement: HTMLSelectElement; private selectBoxOptions: ISelectBoxOptions; private options: ISelectOptionItem[]; - private selected: number; + private selected = 0; private readonly _onDidSelect: Emitter; private styles: ISelectBoxStyles; diff --git a/src/vs/base/browser/ui/splitview/panelview.ts b/src/vs/base/browser/ui/splitview/panelview.ts index 4d2fc8a6144..7c337dc62da 100644 --- a/src/vs/base/browser/ui/splitview/panelview.ts +++ b/src/vs/base/browser/ui/splitview/panelview.ts @@ -42,8 +42,8 @@ export abstract class Panel extends Disposable implements IView { private static readonly HEADER_SIZE = 22; readonly element: HTMLElement; - private header: HTMLElement; - private body: HTMLElement; + private header!: HTMLElement; + private body!: HTMLElement; protected _expanded: boolean; @@ -109,7 +109,7 @@ export abstract class Panel extends Disposable implements IView { return headerSize + maximumBodySize; } - width: number; + width: number = 0; constructor(options: IPanelOptions = {}) { super(); @@ -371,7 +371,7 @@ export class PanelView extends Disposable { private dndContext: IDndContext = { draggable: null }; private el: HTMLElement; private panelItems: IPanelItem[] = []; - private width: number; + private width: number = 0; private splitview: SplitView; private animationTimer: number | undefined = undefined; diff --git a/src/vs/base/browser/ui/splitview/splitview.css b/src/vs/base/browser/ui/splitview/splitview.css index 6fb8f1c61d0..2988cfef8eb 100644 --- a/src/vs/base/browser/ui/splitview/splitview.css +++ b/src/vs/base/browser/ui/splitview/splitview.css @@ -72,4 +72,4 @@ .monaco-split-view2.separator-border.vertical > .split-view-container > .split-view-view:not(:first-child)::before { height: 1px; width: 100%; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 6f031dac570..81090f39c2c 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -24,12 +24,12 @@ const defaultStyles: ISplitViewStyles = { }; export interface ISplitViewOptions { - orientation?: Orientation; // default Orientation.VERTICAL - styles?: ISplitViewStyles; - orthogonalStartSash?: Sash; - orthogonalEndSash?: Sash; - inverseAltBehavior?: boolean; - proportionalLayout?: boolean; // default true + readonly orientation?: Orientation; // default Orientation.VERTICAL + readonly styles?: ISplitViewStyles; + readonly orthogonalStartSash?: Sash; + readonly orthogonalEndSash?: Sash; + readonly inverseAltBehavior?: boolean; + readonly proportionalLayout?: boolean; // default true } /** @@ -48,7 +48,7 @@ export interface IView { readonly onDidChange: Event; readonly priority?: LayoutPriority; readonly snap?: boolean; - layout(size: number, orientation: Orientation): void; + layout(size: number, orthogonalSize: number | undefined): void; setVisible?(visible: boolean): void; } @@ -79,7 +79,7 @@ abstract class ViewItem { return typeof this._cachedVisibleSize === 'undefined'; } - set visible(visible: boolean) { + setVisible(visible: boolean, size?: number): void { if (visible === this.visible) { return; } @@ -88,7 +88,7 @@ abstract class ViewItem { this.size = clamp(this._cachedVisibleSize!, this.viewMinimumSize, this.viewMaximumSize); this._cachedVisibleSize = undefined; } else { - this._cachedVisibleSize = this.size; + this._cachedVisibleSize = typeof size === 'number' ? size : this.size; this.size = 0; } @@ -117,18 +117,20 @@ abstract class ViewItem { if (typeof size === 'number') { this._size = size; this._cachedVisibleSize = undefined; + dom.addClass(container, 'visible'); } else { this._size = 0; this._cachedVisibleSize = size.cachedVisibleSize; } - - dom.addClass(container, 'visible'); } - abstract layout(): void; + layout(_orthogonalSize: number | undefined): void { + this.container.scrollTop = 0; + this.container.scrollLeft = 0; + } - layoutView(orientation: Orientation): void { - this.view.layout(this.size, orientation); + layoutView(orthogonalSize: number | undefined): void { + this.view.layout(this.size, orthogonalSize); } dispose(): IView { @@ -139,17 +141,19 @@ abstract class ViewItem { class VerticalViewItem extends ViewItem { - layout(): void { + layout(orthogonalSize: number | undefined): void { + super.layout(orthogonalSize); this.container.style.height = `${this.size}px`; - this.layoutView(Orientation.VERTICAL); + this.layoutView(orthogonalSize); } } class HorizontalViewItem extends ViewItem { - layout(): void { + layout(orthogonalSize: number | undefined): void { + super.layout(orthogonalSize); this.container.style.width = `${this.size}px`; - this.layoutView(Orientation.HORIZONTAL); + this.layoutView(orthogonalSize); } } @@ -161,6 +165,7 @@ interface ISashItem { interface ISashDragSnapState { readonly index: number; readonly limitDelta: number; + readonly size: number; } interface ISashDragState { @@ -199,11 +204,12 @@ export class SplitView extends Disposable { private sashContainer: HTMLElement; private viewContainer: HTMLElement; private size = 0; + private orthogonalSize: number | undefined; private contentSize = 0; private proportions: undefined | number[] = undefined; private viewItems: ViewItem[] = []; private sashItems: ISashItem[] = []; - private sashDragState: ISashDragState; + private sashDragState: ISashDragState | undefined; private state: State = State.Idle; private inverseAltBehavior: boolean; private proportionalLayout: boolean; @@ -414,9 +420,10 @@ export class SplitView extends Disposable { throw new Error('Cant modify splitview'); } - const size = this.getViewSize(from); + const cachedVisibleSize = this.getViewCachedVisibleSize(from); + const sizing = typeof cachedVisibleSize === 'undefined' ? this.getViewSize(from) : Sizing.Invisible(cachedVisibleSize); const view = this.removeView(from); - this.addView(view, size, to); + this.addView(view, sizing, to); } swapViews(from: number, to: number): void { @@ -452,10 +459,11 @@ export class SplitView extends Disposable { } const viewItem = this.viewItems[index]; - viewItem.visible = visible; + viewItem.setVisible(visible); this.distributeEmptySpace(index); this.layoutViews(); + this.saveProportions(); } getViewCachedVisibleSize(index: number): number | undefined { @@ -467,9 +475,10 @@ export class SplitView extends Disposable { return viewItem.cachedVisibleSize; } - layout(size: number): void { + layout(size: number, orthogonalSize?: number): void { const previousSize = Math.max(this.size, this.contentSize); this.size = size; + this.orthogonalSize = orthogonalSize; if (!this.proportions) { const indexes = range(this.viewItems.length); @@ -499,8 +508,8 @@ export class SplitView extends Disposable { // This way, we can press Alt while we resize a sash, macOS style! const disposable = combinedDisposable( - domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState.current, e.altKey)), - domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState.current, false)) + domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState!.current, e.altKey)), + domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState!.current, false)) ); const resetSashDragState = (start: number, alt: boolean) => { @@ -550,7 +559,8 @@ export class SplitView extends Disposable { snapBefore = { index: snapBeforeIndex, - limitDelta: viewItem.visible ? minDelta - halfSize : minDelta + halfSize + limitDelta: viewItem.visible ? minDelta - halfSize : minDelta + halfSize, + size: viewItem.size }; } @@ -560,7 +570,8 @@ export class SplitView extends Disposable { snapAfter = { index: snapAfterIndex, - limitDelta: viewItem.visible ? maxDelta + halfSize : maxDelta - halfSize + limitDelta: viewItem.visible ? maxDelta + halfSize : maxDelta - halfSize, + size: viewItem.size }; } } @@ -572,8 +583,8 @@ export class SplitView extends Disposable { } private onSashChange({ current }: ISashEvent): void { - const { index, start, sizes, alt, minDelta, maxDelta, snapBefore, snapAfter } = this.sashDragState; - this.sashDragState.current = current; + const { index, start, sizes, alt, minDelta, maxDelta, snapBefore, snapAfter } = this.sashDragState!; + this.sashDragState!.current = current; const delta = current - start; const newDelta = this.resize(index, delta, sizes, undefined, undefined, minDelta, maxDelta, snapBefore, snapAfter); @@ -596,7 +607,7 @@ export class SplitView extends Disposable { private onSashEnd(index: number): void { this._onDidSashChange.fire(index); - this.sashDragState.disposable.dispose(); + this.sashDragState!.disposable.dispose(); this.saveProportions(); } @@ -738,14 +749,14 @@ export class SplitView extends Disposable { const snapView = this.viewItems[snapBefore.index]; const visible = delta >= snapBefore.limitDelta; snapped = visible !== snapView.visible; - snapView.visible = visible; + snapView.setVisible(visible, snapBefore.size); } if (!snapped && snapAfter) { const snapView = this.viewItems[snapAfter.index]; const visible = delta < snapAfter.limitDelta; snapped = visible !== snapView.visible; - snapView.visible = visible; + snapView.setVisible(visible, snapAfter.size); } if (snapped) { @@ -810,7 +821,7 @@ export class SplitView extends Disposable { this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); // Layout views - this.viewItems.forEach(item => item.layout()); + this.viewItems.forEach(item => item.layout(this.orthogonalSize)); // Layout sashes this.sashItems.forEach(item => item.sash.layout()); diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 4aa201e1a68..b6329a0ce4d 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -33,7 +33,7 @@ export class ToolBar extends Disposable { private actionBar: ActionBar; private toggleMenuAction: ToggleMenuAction; private toggleMenuActionViewItem = this._register(new MutableDisposable()); - private hasSecondaryActions: boolean; + private hasSecondaryActions: boolean = false; private lookupKeybindings: boolean; constructor(container: HTMLElement, contextMenuProvider: IContextMenuProvider, options: IToolBarOptions = { orientation: ActionsOrientation.HORIZONTAL }) { @@ -162,6 +162,7 @@ class ToggleMenuAction extends Action { title = title || nls.localize('moreActions', "More Actions..."); super(ToggleMenuAction.ID, title, undefined, true); + this._menuActions = []; this.toggleDropdownMenu = toggleDropdownMenu; } @@ -178,4 +179,4 @@ class ToggleMenuAction extends Action { set menuActions(actions: ReadonlyArray) { this._menuActions = actions; } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 7624d5b1fbe..7402d493a98 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -244,7 +244,7 @@ class TreeRenderer implements IListRenderer, HTMLDivElement>(); - private activeParentNodes = new Set>(); + private activeIndentNodes = new Set>(); private indentGuidesDisposable: IDisposable = Disposable.None; private disposables: IDisposable[] = []; @@ -353,6 +353,7 @@ class TreeRenderer implements IListRenderer implements IListRenderer('.indent-guide', { style: `width: ${this.indent}px` }); - if (this.activeParentNodes.has(parent)) { + if (this.activeIndentNodes.has(parent)) { addClass(guide, 'active'); } @@ -413,24 +414,26 @@ class TreeRenderer implements IListRenderer>(); nodes.forEach(node => { - if (node.parent) { + if (node.collapsible && node.children.length > 0 && !node.collapsed) { + set.add(node); + } else if (node.parent) { set.add(node.parent); } }); - this.activeParentNodes.forEach(node => { + this.activeIndentNodes.forEach(node => { if (!set.has(node)) { this.renderedIndentGuides.forEach(node, line => removeClass(line, 'active')); } }); set.forEach(node => { - if (!this.activeParentNodes.has(node)) { + if (!this.activeIndentNodes.has(node)) { this.renderedIndentGuides.forEach(node, line => addClass(line, 'active')); } }); - this.activeParentNodes = set; + this.activeIndentNodes = set; } dispose(): void { @@ -448,8 +451,8 @@ class TypeFilter implements ITreeFilter, IDisposable { private _matchCount = 0; get matchCount(): number { return this._matchCount; } - private _pattern: string; - private _lowercasePattern: string; + private _pattern: string = ''; + private _lowercasePattern: string = ''; private disposables: IDisposable[] = []; set pattern(pattern: string) { @@ -540,7 +543,7 @@ class TypeFilterController implements IDisposable { private _filterOnType: boolean; get filterOnType(): boolean { return this._filterOnType; } - private _empty: boolean; + private _empty: boolean = false; get empty(): boolean { return this._empty; } private _onDidChangeEmptyState = new Emitter(); @@ -894,6 +897,7 @@ export interface IAbstractTreeOptions extends IAbstractTr readonly autoExpandSingleChildren?: boolean; readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter; readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean); + readonly additionalScrollHeight?: number; } function dfs(node: ITreeNode, fn: (node: ITreeNode) => void): void { @@ -1060,6 +1064,16 @@ class TreeNodeListMouseController extends MouseController< super.onPointer(e); } + + protected onDoubleClick(e: IListMouseEvent>): void { + const onTwistie = hasClass(e.browserEvent.target as HTMLElement, 'monaco-tl-twistie'); + + if (onTwistie) { + return; + } + + super.onDoubleClick(e); + } } interface ITreeNodeListOptions extends IListOptions> { diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 4421bce8de1..b26b80d2c49 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -238,7 +238,8 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T) ) ), - ariaProvider: undefined + ariaProvider: undefined, + additionalScrollHeight: options.additionalScrollHeight }; } @@ -477,7 +478,7 @@ export class AsyncDataTree implements IDisposable // View rerender(element?: T): void { - if (element === undefined) { + if (element === undefined || element === this.root.element) { this.tree.rerender(); return; } @@ -602,9 +603,9 @@ export class AsyncDataTree implements IDisposable return nodes.map(n => n!.element as T); } - open(elements: T[]): void { + open(elements: T[], browserEvent?: UIEvent): void { const nodes = elements.map(e => this.getDataNode(e)); - this.tree.open(nodes); + this.tree.open(nodes, browserEvent); } reveal(element: T, relativeTop?: number): void { diff --git a/src/vs/base/browser/ui/tree/compressedObjectTree.ts b/src/vs/base/browser/ui/tree/compressedObjectTree.ts index 5d1b80462c5..dd98e865909 100644 --- a/src/vs/base/browser/ui/tree/compressedObjectTree.ts +++ b/src/vs/base/browser/ui/tree/compressedObjectTree.ts @@ -17,7 +17,7 @@ export interface IObjectTreeOptions extends IAbstractTree export class CompressedObjectTree, TFilterData = void> extends AbstractTree | null, TFilterData, T | null> { - protected model: CompressedTreeModel; + protected model!: CompressedTreeModel; get onDidChangeCollapseState(): Event | null, TFilterData>> { return this.model.onDidChangeCollapseState; } diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index 9f51021a2e0..9a67a381edb 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -23,7 +23,7 @@ export interface IDataTreeViewState { export class DataTree extends AbstractTree { - protected model: ObjectTreeModel; + protected model!: ObjectTreeModel; private input: TInput | undefined; private identityProvider: IIdentityProvider | undefined; diff --git a/src/vs/base/browser/ui/tree/indexTree.ts b/src/vs/base/browser/ui/tree/indexTree.ts index 4378ffd3e1a..b0b86e0a6f9 100644 --- a/src/vs/base/browser/ui/tree/indexTree.ts +++ b/src/vs/base/browser/ui/tree/indexTree.ts @@ -15,7 +15,7 @@ export interface IIndexTreeOptions extends IAbstractTreeO export class IndexTree extends AbstractTree { - protected model: IndexTreeModel; + protected model!: IndexTreeModel; constructor( container: HTMLElement, diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index 51873462959..25397807106 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -17,7 +17,7 @@ export interface IObjectTreeOptions extends IAbstractTree export class ObjectTree, TFilterData = void> extends AbstractTree { - protected model: IObjectTreeModel; + protected model!: IObjectTreeModel; get onDidChangeCollapseState(): Event> { return this.model.onDidChangeCollapseState; } diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index b0e1b6f161e..e6e769ea897 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -64,11 +64,11 @@ export class Action extends Disposable implements IAction { protected readonly _id: string; protected _label: string; - protected _tooltip: string; + protected _tooltip: string | undefined; protected _cssClass: string | undefined; - protected _enabled: boolean; - protected _checked: boolean; - protected _radio: boolean; + protected _enabled: boolean = true; + protected _checked: boolean = false; + protected _radio: boolean = false; protected readonly _actionCallback?: (event?: any) => Promise; constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Promise) { @@ -100,7 +100,7 @@ export class Action extends Disposable implements IAction { } get tooltip(): string { - return this._tooltip; + return this._tooltip || ''; } set tooltip(value: string) { diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 7aa42597714..cb3c94b3d15 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -293,12 +293,9 @@ function topStep(array: ReadonlyArray, compare: (a: T, b: T) => number, re } /** - * @returns a new array with all falsy values removed. The original array IS NOT modified. + * @returns New array with all falsy values removed. The original array IS NOT modified. */ export function coalesce(array: ReadonlyArray): T[] { - if (!array) { - return array; - } return array.filter(e => !!e); } @@ -306,9 +303,6 @@ export function coalesce(array: ReadonlyArray): T[] { * Remove all falsey values from `array`. The original array IS modified. */ export function coalesceInPlace(array: Array): void { - if (!array) { - return; - } let to = 0; for (let i = 0; i < array.length; i++) { if (!!array[i]) { diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts index 163ecbe74cd..97528a4534c 100644 --- a/src/vs/base/common/color.ts +++ b/src/vs/base/common/color.ts @@ -293,7 +293,7 @@ export class Color { } } - equals(other: Color): boolean { + equals(other: Color | null): boolean { return !!other && RGBA.equals(this.rgba, other.rgba) && HSLA.equals(this.hsla, other.hsla) && HSVA.equals(this.hsva, other.hsva); } @@ -608,4 +608,4 @@ export namespace Color { } } } -} \ No newline at end of file +} diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index da6f814332f..b8077dd5376 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -194,3 +194,13 @@ export function getErrorMessage(err: any): string { return String(err); } + + +export class NotImplementedError extends Error { + constructor(message?: string) { + super('NotImplemented'); + if (message) { + this.message = message; + } + } +} diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 2a423637149..4d43bd8c132 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -645,7 +645,7 @@ export interface IWaitUntil { export class AsyncEmitter extends Emitter { - private _asyncDeliveryQueue: [Listener, T, Promise[]][]; + private _asyncDeliveryQueue?: [Listener, T, Promise[]][]; async fireAsync(eventFn: (thenables: Promise[], listener: Function) => T): Promise { if (!this._listeners) { diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index 19dada92a3e..4aa6b3216e5 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -7,9 +7,9 @@ import { INavigator, ArrayNavigator } from 'vs/base/common/iterator'; export class HistoryNavigator implements INavigator { - private _history: Set; + private _history!: Set; private _limit: number; - private _navigator: ArrayNavigator; + private _navigator!: ArrayNavigator; constructor(history: T[] = [], limit: number = 10) { this._initialize(history); diff --git a/src/vs/base/common/insane/cgmanifest.json b/src/vs/base/common/insane/cgmanifest.json new file mode 100644 index 00000000000..bb94b817088 --- /dev/null +++ b/src/vs/base/common/insane/cgmanifest.json @@ -0,0 +1,17 @@ +{ + "registrations": [ + { + "component": { + "type": "git", + "git": { + "name": "insane", + "repositoryUrl": "https://github.com/bevacqua/insane", + "commitHash": "7f5a809f44a37e7d11ee5343b2d8bca4be6ba4ae" + } + }, + "license": "MIT", + "version": "2.6.2" + } + ], + "version": 1 +} diff --git a/src/vs/base/common/insane/insane.d.ts b/src/vs/base/common/insane/insane.d.ts new file mode 100644 index 00000000000..def347529e9 --- /dev/null +++ b/src/vs/base/common/insane/insane.d.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export as namespace insane; + +export = insane; + +declare function insane( + html: string, + options?: { + readonly allowedSchemes?: readonly string[], + readonly allowedTags?: readonly string[], + readonly allowedAttributes?: { readonly [key: string]: string[] }, + }, + strict?: boolean, +): string; + +declare namespace insane { } diff --git a/src/vs/base/common/insane/insane.js b/src/vs/base/common/insane/insane.js new file mode 100644 index 00000000000..3cfdf5886bb --- /dev/null +++ b/src/vs/base/common/insane/insane.js @@ -0,0 +1,478 @@ +/* +The MIT License (MIT) + +Copyright © 2015 Nicolas Bevacqua + +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. +*/ + +// ESM-comment-begin +let __insane_exports; +// ESM-comment-end + + + +(function () { function r(e, n, t) { function o(i, f) { if (!n[i]) { if (!e[i]) { var c = "function" == typeof require && require; if (!f && c) return c(i, !0); if (u) return u(i, !0); var a = new Error("Cannot find module '" + i + "'"); throw a.code = "MODULE_NOT_FOUND", a } var p = n[i] = { exports: {} }; e[i][0].call(p.exports, function (r) { var n = e[i][1][r]; return o(n || r) }, p, p.exports, r, e, n, t) } return n[i].exports } for (var u = "function" == typeof require && require, i = 0; i < t.length; i++)o(t[i]); return o } return r })()({ + 1: [function (require, module, exports) { + 'use strict'; + + var toMap = require('./toMap'); + var uris = ['background', 'base', 'cite', 'href', 'longdesc', 'src', 'usemap']; + + module.exports = { + uris: toMap(uris) // attributes that have an href and hence need to be sanitized + }; + + }, { "./toMap": 10 }], 2: [function (require, module, exports) { + 'use strict'; + + var defaults = { + allowedAttributes: { + '*': ['title', 'accesskey'], + a: ['href', 'name', 'target', 'aria-label'], + iframe: ['allowfullscreen', 'frameborder', 'src'], + img: ['src', 'alt', 'title', 'aria-label'] + }, + allowedClasses: {}, + allowedSchemes: ['http', 'https', 'mailto'], + allowedTags: [ + 'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code', 'del', 'details', 'div', 'em', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark', + 'ol', 'p', 'pre', 'section', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table', + 'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul' + ], + filter: null + }; + + module.exports = defaults; + + }, {}], 3: [function (require, module, exports) { + 'use strict'; + + var toMap = require('./toMap'); + var voids = ['area', 'br', 'col', 'hr', 'img', 'wbr', 'input', 'base', 'basefont', 'link', 'meta']; + + module.exports = { + voids: toMap(voids) + }; + + }, { "./toMap": 10 }], 4: [function (require, module, exports) { + 'use strict'; + + var he = require('he'); + var assign = require('assignment'); + var parser = require('./parser'); + var sanitizer = require('./sanitizer'); + var defaults = require('./defaults'); + + function insane(html, options, strict) { + var buffer = []; + var configuration = strict === true ? options : assign({}, defaults, options); + var handler = sanitizer(buffer, configuration); + + parser(html, handler); + + return buffer.join(''); + } + + insane.defaults = defaults; + module.exports = insane; + __insane_exports = insane; + + }, { "./defaults": 2, "./parser": 7, "./sanitizer": 8, "assignment": 6, "he": 9 }], 5: [function (require, module, exports) { + 'use strict'; + + module.exports = function lowercase(string) { + return typeof string === 'string' ? string.toLowerCase() : string; + }; + + }, {}], 6: [function (require, module, exports) { + 'use strict'; + + function assignment(result) { + var stack = Array.prototype.slice.call(arguments, 1); + var item; + var key; + while (stack.length) { + item = stack.shift(); + for (key in item) { + if (item.hasOwnProperty(key)) { + if (Object.prototype.toString.call(result[key]) === '[object Object]') { + result[key] = assignment(result[key], item[key]); + } else { + result[key] = item[key]; + } + } + } + } + return result; + } + + module.exports = assignment; + + }, {}], 7: [function (require, module, exports) { + 'use strict'; + + var he = require('he'); + var lowercase = require('./lowercase'); + var attributes = require('./attributes'); + var elements = require('./elements'); + var rstart = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/; + var rend = /^<\s*\/\s*([\w:-]+)[^>]*>/; + var rattrs = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g; + var rtag = /^'); + if (index >= 0) { + if (handler.comment) { + handler.comment(html.substring(4, index)); + } + html = html.substring(index + 3); + chars = false; + } + } + + function parseTagDecode() { + if (!chars) { + return; + } + var text; + var index = html.indexOf('<'); + if (index >= 0) { + text = html.substring(0, index); + html = html.substring(index); + } else { + text = html; + html = ''; + } + if (handler.chars) { + handler.chars(text); + } + } + + function parseStartTag(tag, tagName, rest, unary) { + var attrs = {}; + var low = lowercase(tagName); + var u = elements.voids[low] || !!unary; + + rest.replace(rattrs, attrReplacer); + + if (!u) { + stack.push(low); + } + if (handler.start) { + handler.start(low, attrs, u); + } + + function attrReplacer(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { + if (doubleQuotedValue === void 0 && singleQuotedValue === void 0 && unquotedValue === void 0) { + attrs[name] = void 0; // attribute is like + } else { + attrs[name] = he.decode(doubleQuotedValue || singleQuotedValue || unquotedValue || ''); + } + } + } + + function parseEndTag(tag, tagName) { + var i; + var pos = 0; + var low = lowercase(tagName); + if (low) { + for (pos = stack.length - 1; pos >= 0; pos--) { + if (stack[pos] === low) { + break; // find the closest opened tag of the same type + } + } + } + if (pos >= 0) { + for (i = stack.length - 1; i >= pos; i--) { + if (handler.end) { // close all the open elements, up the stack + handler.end(stack[i]); + } + } + stack.length = pos; + } + } + } + + module.exports = parser; + + }, { "./attributes": 1, "./elements": 3, "./lowercase": 5, "he": 9 }], 8: [function (require, module, exports) { + 'use strict'; + + var he = require('he'); + var lowercase = require('./lowercase'); + var attributes = require('./attributes'); + var elements = require('./elements'); + + function sanitizer(buffer, options) { + var last; + var context; + var o = options || {}; + + reset(); + + return { + start: start, + end: end, + chars: chars + }; + + function out(value) { + buffer.push(value); + } + + function start(tag, attrs, unary) { + var low = lowercase(tag); + + if (context.ignoring) { + ignore(low); return; + } + if ((o.allowedTags || []).indexOf(low) === -1) { + ignore(low); return; + } + if (o.filter && !o.filter({ tag: low, attrs: attrs })) { + ignore(low); return; + } + + out('<'); + out(low); + Object.keys(attrs).forEach(parse); + out(unary ? '/>' : '>'); + + function parse(key) { + var value = attrs[key]; + var classesOk = (o.allowedClasses || {})[low] || []; + var attrsOk = (o.allowedAttributes || {})[low] || []; + attrsOk = attrsOk.concat((o.allowedAttributes || {})['*'] || []); + var valid; + var lkey = lowercase(key); + if (lkey === 'class' && attrsOk.indexOf(lkey) === -1) { + value = value.split(' ').filter(isValidClass).join(' ').trim(); + valid = value.length; + } else { + valid = attrsOk.indexOf(lkey) !== -1 && (attributes.uris[lkey] !== true || testUrl(value)); + } + if (valid) { + out(' '); + out(key); + if (typeof value === 'string') { + out('="'); + out(he.encode(value)); + out('"'); + } + } + function isValidClass(className) { + return classesOk && classesOk.indexOf(className) !== -1; + } + } + } + + function end(tag) { + var low = lowercase(tag); + var allowed = (o.allowedTags || []).indexOf(low) !== -1; + if (allowed) { + if (context.ignoring === false) { + out(''); + } else { + unignore(low); + } + } else { + unignore(low); + } + } + + function testUrl(text) { + var start = text[0]; + if (start === '#' || start === '/') { + return true; + } + var colon = text.indexOf(':'); + if (colon === -1) { + return true; + } + var questionmark = text.indexOf('?'); + if (questionmark !== -1 && colon > questionmark) { + return true; + } + var hash = text.indexOf('#'); + if (hash !== -1 && colon > hash) { + return true; + } + return o.allowedSchemes.some(matches); + + function matches(scheme) { + return text.indexOf(scheme + ':') === 0; + } + } + + function chars(text) { + if (context.ignoring === false) { + out(o.transformText ? o.transformText(text) : text); + } + } + + function ignore(tag) { + if (elements.voids[tag]) { + return; + } + if (context.ignoring === false) { + context = { ignoring: tag, depth: 1 }; + } else if (context.ignoring === tag) { + context.depth++; + } + } + + function unignore(tag) { + if (context.ignoring === tag) { + if (--context.depth <= 0) { + reset(); + } + } + } + + function reset() { + context = { ignoring: false, depth: 0 }; + } + } + + module.exports = sanitizer; + + }, { "./attributes": 1, "./elements": 3, "./lowercase": 5, "he": 9 }], 9: [function (require, module, exports) { + 'use strict'; + + var escapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + var unescapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'" + }; + var rescaped = /(&|<|>|"|')/g; + var runescaped = /[&<>"']/g; + + function escapeHtmlChar(match) { + return escapes[match]; + } + function unescapeHtmlChar(match) { + return unescapes[match]; + } + + function escapeHtml(text) { + return text == null ? '' : String(text).replace(runescaped, escapeHtmlChar); + } + + function unescapeHtml(html) { + return html == null ? '' : String(html).replace(rescaped, unescapeHtmlChar); + } + + escapeHtml.options = unescapeHtml.options = {}; + + module.exports = { + encode: escapeHtml, + escape: escapeHtml, + decode: unescapeHtml, + unescape: unescapeHtml, + version: '1.0.0-browser' + }; + + }, {}], 10: [function (require, module, exports) { + 'use strict'; + + function toMap(list) { + return list.reduce(asKey, {}); + } + + function asKey(accumulator, item) { + accumulator[item] = true; + return accumulator; + } + + module.exports = toMap; + + }, {}] +}, {}, [4]); + +// BEGIN MONACOCHANGE +// __marked_exports = marked; +// }).call(this); + +// ESM-comment-begin +define(function() { return __insane_exports; }); +// ESM-comment-end diff --git a/src/vs/base/common/insane/insane.license.txt b/src/vs/base/common/insane/insane.license.txt new file mode 100644 index 00000000000..b980cef0bc7 --- /dev/null +++ b/src/vs/base/common/insane/insane.license.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright © 2015 Nicolas Bevacqua + +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. diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 6be438cee41..44cfdada8ab 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -127,8 +127,7 @@ export class DisposableStore implements IDisposable { markTracked(t); if (this._isDisposed) { - console.warn(new Error('Registering disposable on object that has already been disposed of').stack); - t.dispose(); + console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack); } else { this._toDispose.add(t); } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 39ec64381aa..f1cce9af4ce 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -112,9 +112,9 @@ export class StringIterator implements IKeyIterator { export class PathIterator implements IKeyIterator { - private _value: string; - private _from: number; - private _to: number; + private _value!: string; + private _from!: number; + private _to!: number; reset(key: string): this { this._value = key.replace(/\\$|\/$/, ''); @@ -176,9 +176,9 @@ export class PathIterator implements IKeyIterator { } class TernarySearchTreeNode { - segment: string; + segment!: string; value: E | undefined; - key: string; + key!: string; left: TernarySearchTreeNode | undefined; mid: TernarySearchTreeNode | undefined; right: TernarySearchTreeNode | undefined; diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 46d2933a05e..9c83d42dc7c 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { URI } from 'vs/base/common/uri'; + export namespace Schemas { /** @@ -47,5 +49,42 @@ export namespace Schemas { export const vscodeRemote: string = 'vscode-remote'; + export const vscodeRemoteResource: string = 'vscode-remote-resource'; + export const userData: string = 'vscode-userdata'; } + +class RemoteAuthoritiesImpl { + private readonly _hosts: { [authority: string]: string; }; + private readonly _ports: { [authority: string]: number; }; + private readonly _connectionTokens: { [authority: string]: string; }; + + constructor() { + this._hosts = Object.create(null); + this._ports = Object.create(null); + this._connectionTokens = Object.create(null); + } + + public set(authority: string, host: string, port: number): void { + this._hosts[authority] = host; + this._ports[authority] = port; + } + + public setConnectionToken(authority: string, connectionToken: string): void { + this._connectionTokens[authority] = connectionToken; + } + + public rewrite(authority: string, path: string): URI { + const host = this._hosts[authority]; + const port = this._ports[authority]; + const connectionToken = this._connectionTokens[authority]; + return URI.from({ + scheme: Schemas.vscodeRemoteResource, + authority: `${host}:${port}`, + path: `/vscode-remote2`, + query: `path=${encodeURIComponent(path)}&tkn=${encodeURIComponent(connectionToken)}` + }); + } +} + +export const RemoteAuthorities = new RemoteAuthoritiesImpl(); diff --git a/src/vs/base/common/path.ts b/src/vs/base/common/path.ts index c8532347224..5ab3ac2621c 100644 --- a/src/vs/base/common/path.ts +++ b/src/vs/base/common/path.ts @@ -58,6 +58,8 @@ class ErrorInvalidArgType extends Error { msg += `. Received type ${typeof actual}`; super(msg); + + this.code = 'ERR_INVALID_ARG_TYPE'; } } diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index d7371552d30..07759dffe59 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -93,14 +93,12 @@ export function PlatformToString(platform: Platform) { } let _platform: Platform = Platform.Web; -if (_isNative) { - if (_isMacintosh) { - _platform = Platform.Mac; - } else if (_isWindows) { - _platform = Platform.Windows; - } else if (_isLinux) { - _platform = Platform.Linux; - } +if (_isMacintosh) { + _platform = Platform.Mac; +} else if (_isWindows) { + _platform = Platform.Windows; +} else if (_isLinux) { + _platform = Platform.Linux; } export const isWindows = _isWindows; diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts index a8447d58eea..7b9edd30963 100644 --- a/src/vs/base/common/process.ts +++ b/src/vs/base/common/process.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isWindows, isMacintosh, setImmediate } from 'vs/base/common/platform'; +import { isWindows, isMacintosh, setImmediate, IProcessEnvironment } from 'vs/base/common/platform'; interface IProcess { platform: string; - env: object; + env: IProcessEnvironment; cwd(): string; nextTick(callback: (...args: any[]) => void): number; diff --git a/src/vs/base/common/search.ts b/src/vs/base/common/search.ts new file mode 100644 index 00000000000..c18233d61b9 --- /dev/null +++ b/src/vs/base/common/search.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as strings from './strings'; + +export function buildReplaceStringWithCasePreserved(matches: string[] | null, pattern: string): string { + if (matches && (matches[0] !== '')) { + if (matches[0].toUpperCase() === matches[0]) { + return pattern.toUpperCase(); + } else if (matches[0].toLowerCase() === matches[0]) { + return pattern.toLowerCase(); + } else if (strings.containsUppercaseCharacter(matches[0][0])) { + return pattern[0].toUpperCase() + pattern.substr(1); + } else { + // we don't understand its pattern yet. + return pattern; + } + } else { + return pattern; + } +} diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index d4397d3279d..71185d07046 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -729,3 +729,18 @@ export function getNLines(str: string, n = 1): string { str.substr(0, idx) : str; } + +/** + * Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc. + */ +export function singleLetterHash(n: number): string { + const LETTERS_CNT = (CharCode.Z - CharCode.A + 1); + + n = n % (2 * LETTERS_CNT); + + if (n < LETTERS_CNT) { + return String.fromCharCode(CharCode.a + n); + } + + return String.fromCharCode(CharCode.A + n - LETTERS_CNT); +} diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 2cc1b188cac..6c9815d3aaa 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -16,7 +16,7 @@ export interface IWorker extends IDisposable { } export interface IWorkerCallback { - (message: string): void; + (message: any): void; } export interface IWorkerFactory { diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 8e23d0adeb2..83428b3590c 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -14,6 +14,12 @@ export const UTF8_with_bom = 'utf8bom'; export const UTF16be = 'utf16be'; export const UTF16le = 'utf16le'; +export type UTF_ENCODING = typeof UTF8 | typeof UTF8_with_bom | typeof UTF16be | typeof UTF16le; + +export function isUTFEncoding(encoding: string): encoding is UTF_ENCODING { + return [UTF8, UTF8_with_bom, UTF16be, UTF16le].some(utfEncoding => utfEncoding === encoding); +} + export const UTF16be_BOM = [0xFE, 0xFF]; export const UTF16le_BOM = [0xFF, 0xFE]; export const UTF8_BOM = [0xEF, 0xBB, 0xBF]; @@ -41,8 +47,8 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions return new Promise((resolve, reject) => { const writer = new class extends Writable { - private decodeStream: NodeJS.ReadWriteStream; - private decodeStreamPromise: Promise; + private decodeStream: NodeJS.ReadWriteStream | undefined; + private decodeStreamPromise: Promise | undefined; private bufferedChunks: Buffer[] = []; private bytesBuffered = 0; @@ -116,7 +122,7 @@ export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions // detection. thus, wrap up starting the stream even // without all the data to get things going else { - this._startDecodeStream(() => this.decodeStream.end(callback)); + this._startDecodeStream(() => this.decodeStream!.end(callback)); } } }; diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 1af9f028c70..14dca3d5f49 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -138,20 +138,6 @@ export async function readdir(path: string): Promise { return handleDirectoryChildren(await promisify(fs.readdir)(path)); } -export async function readdirWithFileTypes(path: string): Promise { - const children = await promisify(fs.readdir)(path, { withFileTypes: true }); - - // Mac: uses NFD unicode form on disk, but we want NFC - // See also https://github.com/nodejs/node/issues/2165 - if (platform.isMacintosh) { - for (const child of children) { - child.name = normalizeNFC(child.name); - } - } - - return children; -} - export function readdirSync(path: string): string[] { return handleDirectoryChildren(fs.readdirSync(path)); } diff --git a/src/vs/base/node/stream.ts b/src/vs/base/node/stream.ts index 12f66a76556..16b73835bd1 100644 --- a/src/vs/base/node/stream.ts +++ b/src/vs/base/node/stream.ts @@ -4,6 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; +import { VSBufferReadableStream, VSBufferReadable, VSBuffer } from 'vs/base/common/buffer'; +import { Readable } from 'stream'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { UTF8, UTF8_with_bom, UTF8_BOM, UTF16be, UTF16le_BOM, UTF16be_BOM, UTF16le, UTF_ENCODING } from 'vs/base/node/encoding'; /** * Reads a file until a matching string is found. @@ -66,4 +70,107 @@ export function readToMatchingString(file: string, matchingString: string, chunk readChunk(); }) ); -} \ No newline at end of file +} + +export function streamToNodeReadable(stream: VSBufferReadableStream): Readable { + return new class extends Readable { + private listening = false; + + _read(size?: number): void { + if (!this.listening) { + this.listening = true; + + // Data + stream.on('data', data => { + try { + if (!this.push(data.buffer)) { + stream.pause(); // pause the stream if we should not push anymore + } + } catch (error) { + this.emit(error); + } + }); + + // End + stream.on('end', () => { + try { + this.push(null); // signal EOS + } catch (error) { + this.emit(error); + } + }); + + // Error + stream.on('error', error => this.emit('error', error)); + } + + // ensure the stream is flowing + stream.resume(); + } + + _destroy(error: Error | null, callback: (error: Error | null) => void): void { + stream.destroy(); + + callback(null); + } + }; +} + +export function nodeReadableToString(stream: NodeJS.ReadableStream): Promise { + return new Promise((resolve, reject) => { + let result = ''; + + stream.on('data', chunk => result += chunk); + stream.on('error', reject); + stream.on('end', () => resolve(result)); + }); +} + +export function nodeStreamToVSBufferReadable(stream: NodeJS.ReadWriteStream, addBOM?: { encoding: UTF_ENCODING }): VSBufferReadable { + let bytesRead = 0; + let done = false; + + return { + read(): VSBuffer | null { + if (done) { + return null; + } + + const res = stream.read(); + if (isUndefinedOrNull(res)) { + done = true; + + // If we are instructed to add a BOM but we detect that no + // bytes have been read, we must ensure to return the BOM + // ourselves so that we comply with the contract. + if (bytesRead === 0 && addBOM) { + switch (addBOM.encoding) { + case UTF8: + case UTF8_with_bom: + return VSBuffer.wrap(Buffer.from(UTF8_BOM)); + case UTF16be: + return VSBuffer.wrap(Buffer.from(UTF16be_BOM)); + case UTF16le: + return VSBuffer.wrap(Buffer.from(UTF16le_BOM)); + } + } + + return null; + } + + // Handle String + if (typeof res === 'string') { + bytesRead += res.length; + + return VSBuffer.fromString(res); + } + + // Handle Buffer + else { + bytesRead += res.byteLength; + + return VSBuffer.wrap(res); + } + } + }; +} diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 459a5b94bd8..207fceb65b9 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -8,8 +8,7 @@ import { IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; import * as platform from 'vs/base/common/platform'; - -declare var process: any; +import * as process from 'vs/base/common/process'; export interface ISocket extends IDisposable { onData(listener: (e: VSBuffer) => void): IDisposable; @@ -434,11 +433,7 @@ export function createBufferedEvent(source: Event): Event { // it is important to deliver these messages after this call, but before // other messages have a chance to be received (to guarantee in order delivery) // that's why we're using here nextTick and not other types of timeouts - if (typeof process !== 'undefined') { - process.nextTick(deliverMessages); - } else { - platform.setImmediate(deliverMessages); - } + process.nextTick(deliverMessages); }, onLastListenerRemove: () => { hasListeners = false; diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 9715105baa3..e0205092202 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -268,7 +268,9 @@ export class ChannelServer implements IChannelServer): void { this.channels.set(channelName, channel); - this.flushPendingRequests(channelName); + + // https://github.com/microsoft/vscode/issues/72531 + setTimeout(() => this.flushPendingRequests(channelName), 0); } private sendResponse(response: IRawResponse): void { diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 9104deb503c..42671a2e1a6 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -86,7 +86,7 @@ export interface IIPCOptions { export class Client implements IChannelClient, IDisposable { - private disposeDelayer: Delayer; + private disposeDelayer: Delayer | undefined; private activeRequests = new Set(); private child: ChildProcess | null; private _client: IPCClient | null; @@ -137,7 +137,7 @@ export class Client implements IChannelClient, IDisposable { cancellationTokenListener.dispose(); this.activeRequests.delete(disposable); - if (this.activeRequests.size === 0) { + if (this.activeRequests.size === 0 && this.disposeDelayer) { this.disposeDelayer.trigger(() => this.disposeClient()); } }); @@ -271,8 +271,10 @@ export class Client implements IChannelClient, IDisposable { dispose() { this._onDidProcessExit.dispose(); - this.disposeDelayer.cancel(); - this.disposeDelayer = null!; // StrictNullOverride: nulling out ok in dispose + if (this.disposeDelayer) { + this.disposeDelayer.cancel(); + this.disposeDelayer = undefined; + } this.disposeClient(); this.activeRequests.clear(); } diff --git a/src/vs/base/parts/ipc/test/node/ipc.test.ts b/src/vs/base/parts/ipc/test/node/ipc.test.ts index 14f059f5e08..e30252caa76 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.test.ts @@ -31,7 +31,7 @@ class QueueProtocol implements IMessagePassingProtocol { }); readonly onMessage = this._onMessage.event; - other: QueueProtocol; + other!: QueueProtocol; send(buffer: VSBuffer): void { this.other.receive(buffer); diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 165db403b5d..bb9dec75605 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -57,7 +57,7 @@ export class QuickOpenEntry { private labelHighlights: IHighlight[]; private descriptionHighlights?: IHighlight[]; private detailHighlights?: IHighlight[]; - private hidden: boolean; + private hidden: boolean | undefined; constructor(highlights: IHighlight[] = []) { this.id = (IDS++).toString(); @@ -148,7 +148,7 @@ export class QuickOpenEntry { * Allows to reuse the same model while filtering. Hidden entries will not show up in the viewer. */ isHidden(): boolean { - return this.hidden; + return !!this.hidden; } /** @@ -352,7 +352,7 @@ class Renderer implements IRenderer { row1.appendChild(icon); // Label - const label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true }); + const label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportOcticons: true }); // Keybinding const keybindingContainer = document.createElement('span'); diff --git a/src/vs/base/parts/quickopen/common/quickOpen.ts b/src/vs/base/parts/quickopen/common/quickOpen.ts index dd39a215ef1..d6039c68802 100644 --- a/src/vs/base/parts/quickopen/common/quickOpen.ts +++ b/src/vs/base/parts/quickopen/common/quickOpen.ts @@ -68,6 +68,8 @@ export interface IDataSource { export interface IRenderer { getHeight(entry: T): number; getTemplateId(entry: T): string; + // rationale: will be replaced by quickinput later + // tslint:disable-next-line: no-dom-globals renderTemplate(templateId: string, container: HTMLElement, styles: any): any; renderElement(entry: T, templateId: string, templateData: any, styles: any): void; disposeTemplate(templateId: string, templateData: any): void; @@ -92,4 +94,4 @@ export interface IModel { runner: IRunner; filter?: IFilter; accessibilityProvider?: IAccessiblityProvider; -} \ No newline at end of file +} diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index 6cebac12c9f..edf313654aa 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -206,7 +206,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { return this.doUpdateItems(recoveryConnection, { insert: recovery() }).then(() => closeRecoveryConnection(), error => { // In case of an error updating items, still ensure to close the connection - // to prevent SQLITE_BUSY errors when the connection is restablished + // to prevent SQLITE_BUSY errors when the connection is reestablished closeRecoveryConnection(); return Promise.reject(error); diff --git a/src/vs/base/parts/tree/browser/treeModel.ts b/src/vs/base/parts/tree/browser/treeModel.ts index 54ec61756da..6f157d62e05 100644 --- a/src/vs/base/parts/tree/browser/treeModel.ts +++ b/src/vs/base/parts/tree/browser/treeModel.ts @@ -862,10 +862,10 @@ export interface IRefreshEvent extends IBaseEvent { export class TreeModel { private context: _.ITreeContext; - private lock: Lock; + private lock!: Lock; private input: Item | null; - private registry: ItemRegistry; - private registryDisposable: IDisposable; + private registry!: ItemRegistry; + private registryDisposable!: IDisposable; private traitsToItems: ITraitMap; private _onSetInput = new Emitter(); diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index 2042f576d55..71f583acc26 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -121,15 +121,15 @@ export class ViewItem implements IViewItem { public top: number; public height: number; public width: number = 0; - public onDragStart: (e: DragEvent) => void; + public onDragStart!: (e: DragEvent) => void; - public needsRender: boolean; - public uri: string | null; + public needsRender: boolean = false; + public uri: string | null = null; public unbindDragStart: Lifecycle.IDisposable = Lifecycle.Disposable.None; public loadingTimer: any; public _styles: any; - private _draggable: boolean; + private _draggable: boolean = false; constructor(context: IViewContext, model: Model.Item) { this.context = context; @@ -174,7 +174,7 @@ export class ViewItem implements IViewItem { return (this.row && this.row.element)!; } - private _templateId: string; + private _templateId: string | undefined; private get templateId(): string { return this._templateId || (this._templateId = (this.context.renderer!.getTemplateId && this.context.renderer!.getTemplateId(this.context.tree, this.model.getElement()))); } @@ -415,8 +415,8 @@ export class TreeView extends HeightMap { private treeStyler: _.ITreeStyler; private rowsContainer: HTMLElement; private scrollableElement: ScrollableElement; - private msGesture: MSGesture; - private lastPointerType: string; + private msGesture: MSGesture | undefined; + private lastPointerType: string = ''; private lastClickTimeStamp: number = 0; private horizontalScrolling: boolean; @@ -425,14 +425,14 @@ export class TreeView extends HeightMap { private lastRenderTop: number; private lastRenderHeight: number; - private inputItem: ViewItem; + private inputItem!: ViewItem; private items: { [id: string]: ViewItem; }; private isRefreshing = false; private refreshingPreviousChildrenIds: { [id: string]: string[] } = {}; private currentDragAndDropData: IDragAndDropData | null = null; private currentDropElement: any; - private currentDropElementReaction: _.IDragOverReaction; + private currentDropElementReaction!: _.IDragOverReaction; private currentDropTarget: ViewItem | null = null; private shouldInvalidateDropReaction: boolean; private currentDropTargets: ViewItem[] | null = null; @@ -443,7 +443,7 @@ export class TreeView extends HeightMap { private didJustPressContextMenuKey: boolean; - private highlightedItemWasDraggable: boolean; + private highlightedItemWasDraggable: boolean = false; private onHiddenScrollTop: number | null = null; private readonly _onDOMFocus = new Emitter(); @@ -633,7 +633,7 @@ export class TreeView extends HeightMap { private setupMSGesture(): void { if ((window).MSGesture) { this.msGesture = new MSGesture(); - setTimeout(() => this.msGesture.target = this.wrapper, 100); // TODO@joh, TODO@IETeam + setTimeout(() => this.msGesture!.target = this.wrapper, 100); // TODO@joh, TODO@IETeam } } diff --git a/src/vs/base/test/browser/htmlContent.test.ts b/src/vs/base/test/browser/formattedTextRenderer.test.ts similarity index 65% rename from src/vs/base/test/browser/htmlContent.test.ts rename to src/vs/base/test/browser/formattedTextRenderer.test.ts index d5dec0729d9..0be97478522 100644 --- a/src/vs/base/test/browser/htmlContent.test.ts +++ b/src/vs/base/test/browser/formattedTextRenderer.test.ts @@ -2,12 +2,12 @@ * 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 marked from 'vs/base/common/marked/marked'; -import { renderMarkdown, renderText, renderFormattedText } from 'vs/base/browser/htmlContentRenderer'; +import { renderText, renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { DisposableStore } from 'vs/base/common/lifecycle'; -suite('HtmlContent', () => { +suite('FormattedTextRenderer', () => { const store = new DisposableStore(); setup(() => { @@ -101,36 +101,4 @@ suite('HtmlContent', () => { assert.strictEqual(result.children.length, 0); assert.strictEqual(result.innerHTML, '**bold**'); }); - test('image rendering conforms to default', () => { - const markdown = { value: `![image](someimageurl 'caption')` }; - const result: HTMLElement = renderMarkdown(markdown); - const renderer = new marked.Renderer(); - const imageFromMarked = marked(markdown.value, { - sanitize: true, - renderer - }).trim(); - assert.strictEqual(result.innerHTML, imageFromMarked); - }); - test('image rendering conforms to default without title', () => { - const markdown = { value: `![image](someimageurl)` }; - const result: HTMLElement = renderMarkdown(markdown); - const renderer = new marked.Renderer(); - const imageFromMarked = marked(markdown.value, { - sanitize: true, - renderer - }).trim(); - assert.strictEqual(result.innerHTML, imageFromMarked); - }); - test('image width from title params', () => { - let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` }); - assert.strictEqual(result.innerHTML, `

image

`); - }); - test('image height from title params', () => { - let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` }); - assert.strictEqual(result.innerHTML, `

image

`); - }); - test('image width and height from title params', () => { - let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` }); - assert.strictEqual(result.innerHTML, `

image

`); - }); }); diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts new file mode 100644 index 00000000000..525eb86e741 --- /dev/null +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * 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 marked from 'vs/base/common/marked/marked'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; + +suite('MarkdownRenderer', () => { + test('image rendering conforms to default', () => { + const markdown = { value: `![image](someimageurl 'caption')` }; + const result: HTMLElement = renderMarkdown(markdown); + const renderer = new marked.Renderer(); + const imageFromMarked = marked(markdown.value, { + sanitize: true, + renderer + }).trim(); + assert.strictEqual(result.innerHTML, imageFromMarked); + }); + + test('image rendering conforms to default without title', () => { + const markdown = { value: `![image](someimageurl)` }; + const result: HTMLElement = renderMarkdown(markdown); + const renderer = new marked.Renderer(); + const imageFromMarked = marked(markdown.value, { + sanitize: true, + renderer + }).trim(); + assert.strictEqual(result.innerHTML, imageFromMarked); + }); + + test('image width from title params', () => { + let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` }); + assert.strictEqual(result.innerHTML, `

image

`); + }); + + test('image height from title params', () => { + let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` }); + assert.strictEqual(result.innerHTML, `

image

`); + }); + + test('image width and height from title params', () => { + let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` }); + assert.strictEqual(result.innerHTML, `

image

`); + }); +}); diff --git a/src/vs/base/test/browser/ui/grid/grid.test.ts b/src/vs/base/test/browser/ui/grid/grid.test.ts index 3c2b1b4ec44..d8dbad25721 100644 --- a/src/vs/base/test/browser/ui/grid/grid.test.ts +++ b/src/vs/base/test/browser/ui/grid/grid.test.ts @@ -9,6 +9,7 @@ import { TestView, nodesToArrays } from './util'; import { deepClone } from 'vs/base/common/objects'; // Simple example: +// // +-----+---------------+ // | 4 | 2 | // +-----+---------+-----+ @@ -16,6 +17,16 @@ import { deepClone } from 'vs/base/common/objects'; // +---------------+ 3 | // | 5 | | // +---------------+-----+ +// +// V +// +-H +// | +-4 +// | +-2 +// +-H +// | +-V +// | +-1 +// | +-5 +// +-3 suite('Grid', function () { let container: HTMLElement; @@ -511,7 +522,8 @@ suite('SerializableGrid', function () { container.appendChild(grid.element); grid.layout(800, 600); - assert.deepEqual(grid.serialize(), { + const actual = grid.serialize(); + assert.deepEqual(actual, { orientation: 0, width: 800, height: 600, @@ -834,6 +846,24 @@ suite('SerializableGrid', function () { assert.deepEqual(view4.size, [200, 200]); assert.deepEqual(view5.size, [600, 0]); + grid.setViewVisible(view5, true); + + assert.deepEqual(view1.size, [600, 300]); + assert.deepEqual(view2.size, [600, 200]); + assert.deepEqual(view3.size, [200, 400]); + assert.deepEqual(view4.size, [200, 200]); + assert.deepEqual(view5.size, [600, 100]); + + grid.setViewVisible(view5, false); + + assert.deepEqual(view1.size, [600, 400]); + assert.deepEqual(view2.size, [600, 200]); + assert.deepEqual(view3.size, [200, 400]); + assert.deepEqual(view4.size, [200, 200]); + assert.deepEqual(view5.size, [600, 0]); + + grid.setViewVisible(view5, false); + const json = grid.serialize(); assert.deepEqual(json, { orientation: 0, diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts index 504e665f21a..c5e9cfce763 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Emitter } from 'vs/base/common/event'; -import { SplitView, IView, Orientation, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; +import { SplitView, IView, Sizing, LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; import { Sash, SashState } from 'vs/base/browser/ui/sash/sash'; class TestView implements IView { @@ -27,7 +27,9 @@ class TestView implements IView { private _size = 0; get size(): number { return this._size; } - private _onDidLayout = new Emitter<{ size: number; orientation: Orientation }>(); + private _orthogonalSize: number | undefined = 0; + get orthogonalSize(): number | undefined { return this._orthogonalSize; } + private _onDidLayout = new Emitter<{ size: number; orthogonalSize: number | undefined }>(); readonly onDidLayout = this._onDidLayout.event; private _onDidFocus = new Emitter(); @@ -41,9 +43,10 @@ class TestView implements IView { assert(_minimumSize <= _maximumSize, 'splitview view minimum size must be <= maximum size'); } - layout(size: number, orientation: Orientation): void { + layout(size: number, orthogonalSize: number | undefined): void { this._size = size; - this._onDidLayout.fire({ size, orientation }); + this._orthogonalSize = orthogonalSize; + this._onDidLayout.fire({ size, orthogonalSize }); } focus(): void { @@ -523,4 +526,24 @@ suite('Splitview', () => { view2.dispose(); view1.dispose(); }); -}); \ No newline at end of file + + test('orthogonal size propagates to views', () => { + const view1 = new TestView(20, Number.POSITIVE_INFINITY); + const view2 = new TestView(20, Number.POSITIVE_INFINITY); + const view3 = new TestView(20, Number.POSITIVE_INFINITY, LayoutPriority.Low); + const splitview = new SplitView(container, { proportionalLayout: false }); + splitview.layout(200); + + splitview.addView(view1, Sizing.Distribute); + splitview.addView(view2, Sizing.Distribute); + splitview.addView(view3, Sizing.Distribute); + + splitview.layout(200, 100); + assert.deepEqual([view1.orthogonalSize, view2.orthogonalSize, view3.orthogonalSize], [100, 100, 100]); + + splitview.dispose(); + view3.dispose(); + view2.dispose(); + view1.dispose(); + }); +}); diff --git a/src/vs/base/test/common/lifecycle.test.ts b/src/vs/base/test/common/lifecycle.test.ts index 46d8a254b5b..4d15ad2046c 100644 --- a/src/vs/base/test/common/lifecycle.test.ts +++ b/src/vs/base/test/common/lifecycle.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 { IDisposable, dispose, ReferenceCollection, Disposable as DisposableBase, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, ReferenceCollection } from 'vs/base/common/lifecycle'; class Disposable implements IDisposable { isDisposed = false; @@ -50,38 +50,6 @@ suite('Lifecycle', () => { }); }); -suite('DisposableBase', () => { - test('register should not leak if object has already been disposed', () => { - let aCount = 0; - let bCount = 0; - - const disposable = new class extends DisposableBase { - register(other: IDisposable) { - this._register(other); - } - }; - - disposable.register(toDisposable(() => ++aCount)); - - assert.strictEqual(aCount, 0); - assert.strictEqual(bCount, 0); - - disposable.dispose(); - assert.strictEqual(aCount, 1); - assert.strictEqual(bCount, 0); - - // Any newly added disposables should be disposed of immediately - disposable.register(toDisposable(() => ++bCount)); - assert.strictEqual(aCount, 1); - assert.strictEqual(bCount, 1); - - // Further dispose calls should have no effect - disposable.dispose(); - assert.strictEqual(aCount, 1); - assert.strictEqual(bCount, 1); - }); -}); - suite('Reference Collection', () => { class Collection extends ReferenceCollection { private _count = 0; @@ -118,4 +86,4 @@ suite('Reference Collection', () => { ref4.dispose(); assert.equal(collection.count, 0); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/common/path.test.ts b/src/vs/base/test/common/path.test.ts index cfceaedc9e9..f5230c4a35c 100644 --- a/src/vs/base/test/common/path.test.ts +++ b/src/vs/base/test/common/path.test.ts @@ -30,6 +30,7 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; +import * as process from 'vs/base/common/process'; suite('Paths (Node Implementation)', () => { test('join', () => { diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index 0fb02a0193d..26b225a1fa9 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -12,8 +12,8 @@ export type ValueCallback = (value: T | Promise) => void; export class DeferredPromise { - private completeCallback: ValueCallback; - private errorCallback: (err: any) => void; + private completeCallback!: ValueCallback; + private errorCallback!: (err: any) => void; public p: Promise; diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index 060913bbe72..4f09ad2b56d 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -16,7 +16,6 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { canNormalize } from 'vs/base/common/normalization'; import { VSBuffer } from 'vs/base/common/buffer'; -import { join } from 'path'; const chunkSize = 64 * 1024; const readError = 'Error while reading'; @@ -387,31 +386,6 @@ suite('PFS', () => { } }); - test('readdirWithFileTypes', async () => { - if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const testDir = join(parentDir, 'pfs', id); - - const newDir = path.join(testDir, 'öäü'); - await pfs.mkdirp(newDir, 493); - - await pfs.writeFile(join(testDir, 'somefile.txt'), 'contents'); - - assert.ok(fs.existsSync(newDir)); - - const children = await pfs.readdirWithFileTypes(testDir); - - assert.equal(children.some(n => n.name === 'öäü'), true); // Mac always converts to NFD, so - assert.equal(children.some(n => n.isDirectory()), true); - - assert.equal(children.some(n => n.name === 'somefile.txt'), true); - assert.equal(children.some(n => n.isFile()), true); - - await pfs.rimraf(parentDir); - } - }); - test('writeFile (string)', async () => { const smallData = 'Hello World'; const bigData = (new Array(100 * 1024)).join('Large String\n'); diff --git a/src/vs/base/worker/defaultWorkerFactory.ts b/src/vs/base/worker/defaultWorkerFactory.ts index c2a7ddc54e6..bce60b6b855 100644 --- a/src/vs/base/worker/defaultWorkerFactory.ts +++ b/src/vs/base/worker/defaultWorkerFactory.ts @@ -19,26 +19,31 @@ function getWorker(workerId: string, label: string): Worker | Promise { // ESM-comment-begin if (typeof require === 'function') { // check if the JS lives on a different origin - const workerMain = require.toUrl('./' + workerId); - if (/^(http:)|(https:)|(file:)/.test(workerMain)) { - const currentUrl = String(window.location); - const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length); - if (workerMain.substring(0, currentOrigin.length) !== currentOrigin) { - // this is the cross-origin case - // i.e. the webpage is running at a different origin than where the scripts are loaded from - const workerBaseUrl = workerMain.substr(0, workerMain.length - 'vs/base/worker/workerMain.js'.length); - const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${workerMain}');/*${label}*/`; - const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`; - return new Worker(url); - } - } - return new Worker(workerMain + '#' + label); + const workerUrl = getWorkerBootstrapUrl(workerMain, label); + return new Worker(workerUrl, { name: label }); } // ESM-comment-end throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`); } +export function getWorkerBootstrapUrl(scriptPath: string, label: string): string { + if (/^(http:)|(https:)|(file:)/.test(scriptPath)) { + const currentUrl = String(window.location); + const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length); + if (scriptPath.substring(0, currentOrigin.length) !== currentOrigin) { + // this is the cross-origin case + // i.e. the webpage is running at a different origin than where the scripts are loaded from + const myPath = 'vs/base/worker/defaultWorkerFactory.js'; + const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length); + const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${scriptPath}');/*${label}*/`; + const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`; + return url; + } + } + return scriptPath + '#' + label; +} + function isPromiseLike(obj: any): obj is PromiseLike { if (typeof obj.then === 'function') { return true; diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index 0ec8b7834dc..8d51252951a 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -20,8 +20,8 @@ let loadCode = function (moduleId: string) { require([moduleId], function (ws) { setTimeout(function () { - let messageHandler = ws.create((msg: any) => { - (self).postMessage(msg); + let messageHandler = ws.create((msg: any, transfer?: Transferable[]) => { + (self).postMessage(msg, transfer); }, null); self.onmessage = (e) => messageHandler.onmessage(e.data); diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 5b06636edbb..8f8f45fb15f 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -10,7 +10,7 @@ + content="default-src 'none'; img-src 'self' https: data: blob:; media-src 'none'; frame-src 'self' {{WEBVIEW_ENDPOINT}} https://*.vscode-webview-test.com; script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https:; style-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss: https:; font-src 'self' blob:;"> @@ -24,9 +24,6 @@ - - - diff --git a/src/vs/code/browser/workbench/workbench.js b/src/vs/code/browser/workbench/workbench.js index 65fae7c82df..240ca34df05 100644 --- a/src/vs/code/browser/workbench/workbench.js +++ b/src/vs/code/browser/workbench/workbench.js @@ -16,6 +16,7 @@ 'xterm-addon-search': `${window.location.origin}/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, 'xterm-addon-web-links': `${window.location.origin}/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, 'semver-umd': `${window.location.origin}/node_modules/semver-umd/lib/semver-umd.js`, + '@microsoft/applicationinsights-web': `${window.location.origin}/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`, } }); @@ -24,4 +25,4 @@ api.create(document.body, options); }); -})(); \ No newline at end of file +})(); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 2302d162f34..591cbed96f3 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -31,7 +31,7 @@ import { WindowsService } from 'vs/platform/windows/electron-browser/windowsServ import { MainProcessService, IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel'; -import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; +import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; @@ -39,7 +39,7 @@ import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil'; import { Button } from 'vs/base/browser/ui/button/button'; import { withUndefinedAsNull } from 'vs/base/common/types'; -import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; const MAX_URL_LENGTH = 2045; @@ -64,9 +64,9 @@ export function startup(configuration: IssueReporterConfiguration) { } export class IssueReporter extends Disposable { - private environmentService: IEnvironmentService; - private telemetryService: ITelemetryService; - private logService: ILogService; + private environmentService!: IEnvironmentService; + private telemetryService!: ITelemetryService; + private logService!: ILogService; private readonly issueReporterModel: IssueReporterModel; private numberOfSearchResultsDisplayed = 0; private receivedSystemInfo = false; @@ -74,7 +74,7 @@ export class IssueReporter extends Disposable { private shouldQueueSearch = false; private hasBeenSubmitted = false; - private readonly previewButton: Button; + private readonly previewButton!: Button; constructor(configuration: IssueReporterConfiguration) { super(); diff --git a/src/vs/code/electron-browser/issue/issueReporterModel.ts b/src/vs/code/electron-browser/issue/issueReporterModel.ts index c1f78969153..6c7fa528e37 100644 --- a/src/vs/code/electron-browser/issue/issueReporterModel.ts +++ b/src/vs/code/electron-browser/issue/issueReporterModel.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { assign } from 'vs/base/common/objects'; -import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; -import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; +import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; export interface IssueReporterData { issueType: IssueType; diff --git a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts index 95857638e41..c6945463991 100644 --- a/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-browser/issue/test/testReporterModel.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil'; -import { IssueType } from 'vs/platform/issue/common/issue'; +import { IssueType } from 'vs/platform/issue/node/issue'; suite('IssueReporter', () => { diff --git a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts index 321a5611e74..557957dfd0b 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts @@ -9,7 +9,7 @@ import { repeat } from 'vs/base/common/strings'; import { totalmem } from 'os'; import product from 'vs/platform/product/node/product'; import { localize } from 'vs/nls'; -import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/node/issue'; import * as browser from 'vs/base/browser/browser'; import * as platform from 'vs/base/common/platform'; import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; @@ -17,7 +17,7 @@ import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu'; import { ProcessItem } from 'vs/base/common/processes'; import { addDisposableListener } from 'vs/base/browser/dom'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; let mapPidToWindowTitle = new Map(); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index b85c29ffc4a..c5514f4eff7 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -48,15 +48,13 @@ import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/ import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; -import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; -import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { DiagnosticsService, IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { DiagnosticsChannel } from 'vs/platform/diagnostics/node/diagnosticsIpc'; import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/product'; -import { ProductService } from 'vs/platform/product/node/productService'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -77,7 +75,7 @@ const eventPrefix = 'monacoworkbench'; class MainProcessService implements IMainProcessService { constructor(private server: Server, private mainRouter: StaticRouter) { } - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; getChannel(channelName: string): IChannel { return this.server.getChannel(channelName, this.mainRouter); @@ -112,10 +110,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat await configurationService.initialize(); services.set(IEnvironmentService, environmentService); + services.set(IProductService, { _serviceBrand: undefined, ...product }); services.set(ILogService, logService); services.set(IConfigurationService, configurationService); services.set(IRequestService, new SyncDescriptor(RequestService)); - services.set(IProductService, new SyncDescriptor(ProductService)); const mainProcessService = new MainProcessService(server, mainRouter); services.set(IMainProcessService, mainProcessService); diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index dfac360344a..693082bb9ed 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -3,11 +3,11 @@ - + - \ No newline at end of file + diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index dfe41b3d93d..98e67927fe1 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -16,9 +16,9 @@ process['lazyEnv'] = getLazyEnv(); // Load workbench main bootstrapWindow.load([ - 'vs/workbench/workbench.main', - 'vs/nls!vs/workbench/workbench.main', - 'vs/css!vs/workbench/workbench.main' + 'vs/workbench/workbench.desktop.main', + 'vs/nls!vs/workbench/workbench.desktop.main', + 'vs/css!vs/workbench/workbench.desktop.main' ], function (workbench, configuration) { perf.mark('didLoadWorkbenchMain'); @@ -27,7 +27,7 @@ bootstrapWindow.load([ perf.mark('main/startup'); // @ts-ignore - return require('vs/workbench/electron-browser/main').main(configuration); + return require('vs/workbench/electron-browser/desktop.main').main(configuration); }); }, { removeDeveloperKeybindingsAfterLoad: true, diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 785a214c7a3..6d43b392dfb 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -7,7 +7,7 @@ import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, p import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform'; import { WindowsManager } from 'vs/code/electron-main/windows'; import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows'; -import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc'; +import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc'; import { WindowsService } from 'vs/platform/windows/electron-main/windowsService'; import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { getShellEnvironment } from 'vs/code/node/shellEnv'; @@ -39,7 +39,6 @@ import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService } from 'vs/platform/history/common/history'; -import { withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc'; import { IWorkspacesMainService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; @@ -47,15 +46,14 @@ import { getMachineId } from 'vs/base/node/id'; import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32'; import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux'; import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin'; -import { IIssueService } from 'vs/platform/issue/common/issue'; +import { IIssueService } from 'vs/platform/issue/node/issue'; import { IssueChannel } from 'vs/platform/issue/node/issueIpc'; import { IssueService } from 'vs/platform/issue/electron-main/issueService'; import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors'; import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener'; import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; -import { connectRemoteAgentManagement, ManagementPersistentConnection, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; -import { IMenubarService } from 'vs/platform/menubar/common/menubar'; +import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService'; import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc'; import { hasArgs } from 'vs/platform/environment/node/argv'; @@ -65,8 +63,6 @@ import { homedir } from 'os'; import { join, sep } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; -import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; -import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap'; import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService'; import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc'; @@ -76,13 +72,9 @@ import { IBackupMainService } from 'vs/platform/backup/common/backup'; import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; import { URLService } from 'vs/platform/url/common/urlService'; import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; -import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; -import { VSBuffer } from 'vs/base/common/buffer'; import { statSync } from 'fs'; -import { ISignService } from 'vs/platform/sign/common/sign'; -import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnosticsService'; import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc'; +import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; @@ -103,8 +95,7 @@ export class CodeApplication extends Disposable { @IEnvironmentService private readonly environmentService: IEnvironmentService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IStateService private readonly stateService: IStateService, - @ISignService private readonly signService: ISignService + @IStateService private readonly stateService: IStateService ) { super(); @@ -167,11 +158,13 @@ export class CodeApplication extends Disposable { event.preventDefault(); }); app.on('remote-get-current-web-contents', event => { - // The driver needs access to web contents - if (!this.environmentService.args.driver) { - this.logService.trace(`App#on(remote-get-current-web-contents): prevented`); - event.preventDefault(); + if (this.environmentService.args.driver) { + return; // the driver needs access to web contents } + + this.logService.trace(`App#on(remote-get-current-web-contents): prevented`); + + event.preventDefault(); }); app.on('web-contents-created', (_event: Electron.Event, contents) => { contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => { @@ -567,7 +560,7 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel('url', urlChannel); const storageMainService = accessor.get(IStorageMainService); - const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService as StorageMainService)); + const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService)); electronIpcServer.registerChannel('storage', storageChannel); // Log level management @@ -695,115 +688,11 @@ export class CodeApplication extends Disposable { } private handleRemoteAuthorities(): void { - const connectionPool: Map = new Map(); - - const isBuilt = this.environmentService.isBuilt; - - class ActiveConnection { - private readonly _authority: string; - private readonly _connection: Promise; - private readonly _disposeRunner: RunOnceScheduler; - - constructor(authority: string, host: string, port: number, signService: ISignService) { - this._authority = authority; - - const options: IConnectionOptions = { - isBuilt, - commit: product.commit, - socketFactory: nodeSocketFactory, - addressProvider: { - getAddress: () => { - return Promise.resolve({ host, port }); - } - }, - signService - }; - - this._connection = connectRemoteAgentManagement(options, authority, `main`); - this._disposeRunner = new RunOnceScheduler(() => this.dispose(), 5000); - } - - dispose(): void { - this._disposeRunner.dispose(); - connectionPool.delete(this._authority); - this._connection.then(connection => connection.dispose()); - } - - async getClient(): Promise> { - this._disposeRunner.schedule(); - const connection = await this._connection; - - return connection.client; - } - } - - const resolvedAuthorities = new Map(); - ipc.on('vscode:remoteAuthorityResolved', (event: Electron.Event, data: ResolvedAuthority) => { - this.logService.info('Received resolved authority', data.authority); - - resolvedAuthorities.set(data.authority, data); - - // Make sure to close and remove any existing connections - if (connectionPool.has(data.authority)) { - connectionPool.get(data.authority)!.dispose(); - } - }); - - const resolveAuthority = (authority: string): ResolvedAuthority | null => { - this.logService.info('Resolving authority', authority); - - if (authority.indexOf('+') >= 0) { - if (resolvedAuthorities.has(authority)) { - return withUndefinedAsNull(resolvedAuthorities.get(authority)); - } - - this.logService.info('Didnot find resolved authority for', authority); - - return null; - } else { - const [host, strPort] = authority.split(':'); - const port = parseInt(strPort, 10); - - return { authority, host, port }; - } - }; - - protocol.registerBufferProtocol(Schemas.vscodeRemote, async (request, callback) => { - if (request.method !== 'GET') { - return callback(undefined); - } - - const uri = URI.parse(request.url); - - let activeConnection: ActiveConnection | undefined; - if (connectionPool.has(uri.authority)) { - activeConnection = connectionPool.get(uri.authority); - } else { - const resolvedAuthority = resolveAuthority(uri.authority); - if (!resolvedAuthority) { - callback(undefined); - return; - } - - activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port, this.signService); - connectionPool.set(uri.authority, activeConnection); - } - - try { - const rawClient = await activeConnection!.getClient(); - if (connectionPool.has(uri.authority)) { // not disposed in the meantime - const channel = rawClient.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); - - // TODO@alex don't use call directly, wrap it around a `RemoteExtensionsFileSystemProvider` - const fileContents = await channel.call('readFile', [uri]); - callback(fileContents.buffer); - } else { - callback(undefined); - } - } catch (err) { - onUnexpectedError(err); - callback(undefined); - } + protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => { + callback({ + url: request.url.replace(/^vscode-remote-resource:/, 'http:'), + method: request.method + }); }); } } diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 71891c9bafd..c6301eb4a33 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -23,7 +23,7 @@ import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } import { StateService } from 'vs/platform/state/node/stateService'; import { IStateService } from 'vs/platform/state/common/state'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; -import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { EnvironmentService, xdgRuntimeDir } from 'vs/platform/environment/node/environmentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { IRequestService } from 'vs/platform/request/common/request'; @@ -330,11 +330,19 @@ class CodeMain { private handleStartupDataDirError(environmentService: IEnvironmentService, error: NodeJS.ErrnoException): void { if (error.code === 'EACCES' || error.code === 'EPERM') { + const directories = [environmentService.userDataPath]; + + if (environmentService.extensionsPath) { + directories.push(environmentService.extensionsPath); + } + + if (xdgRuntimeDir) { + directories.push(xdgRuntimeDir); + } + this.showStartupWarningDialog( localize('startupDataDirError', "Unable to write program user data."), - environmentService.extensionsPath - ? localize('startupUserDataAndExtensionsDirErrorDetail', "Please make sure the directories {0} and {1} are writeable.", environmentService.userDataPath, environmentService.extensionsPath) - : localize('startupUserDataDirErrorDetail', "Please make sure the directory {0} is writeable.", environmentService.userDataPath) + localize('startupUserDataAndExtensionsDirErrorDetail', "Please make sure the following directories are writeable:\n\n{0}", directories.join('\n')) ); } } @@ -392,4 +400,4 @@ class CodeMain { // Main Startup const code = new CodeMain(); -code.main(); \ No newline at end of file +code.main(); diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 60926b0d146..f5b010da563 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -18,7 +18,7 @@ export class SharedProcess implements ISharedProcess { private barrier = new Barrier(); - private window: Electron.BrowserWindow | null; + private window: Electron.BrowserWindow | null = null; constructor( private readonly machineId: string, diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 48055ea9071..c6d2f29b1ba 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -360,17 +360,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.pendingLoadConfig = undefined; } - - // To prevent flashing, we set the window visible after the page has finished to load but before Code is loaded - if (this._win && !this._win.isVisible()) { - if (this.windowState.mode === WindowMode.Maximized) { - this._win.maximize(); - } - - if (!this._win.isVisible()) { // maximize also makes visible - this._win.show(); - } - } }); // Window Focus @@ -838,17 +827,16 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private useNativeFullScreen(): boolean { - return true; // TODO@ben enable simple fullscreen again (https://github.com/microsoft/vscode/issues/75054) - // const windowConfig = this.configurationService.getValue('window'); - // if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') { - // return true; // default - // } + const windowConfig = this.configurationService.getValue('window'); + if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') { + return true; // default + } - // if (windowConfig.nativeTabs) { - // return true; // https://github.com/electron/electron/issues/16142 - // } + if (windowConfig.nativeTabs) { + return true; // https://github.com/electron/electron/issues/16142 + } - // return windowConfig.nativeFullScreen !== false; + return windowConfig.nativeFullScreen !== false; } isMinimized(): boolean { diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 5e50acec4dc..d4defa1962e 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -1149,9 +1149,10 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } } - // Linux/Windows: by default we open files in the new window unless triggered via DIALOG or MENU context + // Linux/Windows: by default we open files in the new window unless triggered via DIALOG / MENU context + // or from the integrated terminal where we assume the user prefers to open in the current window else { - if (openConfig.context !== OpenContext.DIALOG && openConfig.context !== OpenContext.MENU) { + if (openConfig.context !== OpenContext.DIALOG && openConfig.context !== OpenContext.MENU && !(openConfig.userEnv && openConfig.userEnv['TERM_PROGRAM'] === 'vscode')) { openFilesInNewWindow = true; } } @@ -1255,10 +1256,9 @@ export class WindowsManager extends Disposable implements IWindowsMainService { openConfig.cli['file-uri'] = fileUris; // if there are no files or folders cli args left, use the "remote" cli argument - if (!cliArgs.length && !folderUris.length && !fileUris.length) { - if (authority) { - openConfig.cli.remote = authority; - } + const noFilesOrFolders = !cliArgs.length && !folderUris.length && !fileUris.length; + if (noFilesOrFolders && authority) { + openConfig.cli.remote = authority; } // Open it @@ -1266,7 +1266,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, - forceEmpty: !cliArgs.length && !folderUris.length && !fileUris.length, + forceEmpty: noFilesOrFolders, userEnv: openConfig.userEnv, noRecentEntry: true, waitMarkerFileURI: openConfig.waitMarkerFileURI @@ -2116,4 +2116,4 @@ function resourceFromURIToOpen(u: IURIToOpen): URI { } return u.fileUri; -} \ No newline at end of file +} diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 032287a3b5d..32005dd153e 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -47,7 +47,6 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IProductService } from 'vs/platform/product/common/product'; -import { ProductService } from 'vs/platform/product/node/productService'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id); @@ -112,7 +111,13 @@ export class Main { private async listExtensions(showVersions: boolean, category?: string): Promise { let extensions = await this.extensionManagementService.getInstalled(ExtensionType.User); - if (category) { + // TODO: we should save this array in a common place so that the command and extensionQuery can use it that way changing it is easier + const categories = ['"programming languages"', 'snippets', 'linters', 'themes', 'debuggers', 'formatters', 'keymaps', '"scm providers"', 'other', '"extension packs"', '"language packs"']; + if (category && category !== '') { + if (categories.indexOf(category.toLowerCase()) < 0) { + console.log('Invalid category please enter a valid category. To list valid categories run --category without a category specified'); + return; + } extensions = extensions.filter(e => { if (e.manifest.categories) { const lowerCaseCategories: string[] = e.manifest.categories.map(c => c.toLowerCase()); @@ -120,6 +125,12 @@ export class Main { } return false; }); + } else if (category === '') { + console.log('Possible Categories: '); + categories.forEach(category => { + console.log(category); + }); + return; } extensions.forEach(e => console.log(getId(e.manifest, showVersions))); } @@ -313,7 +324,7 @@ export async function main(argv: ParsedArgs): Promise { services.set(ILogService, logService); services.set(IConfigurationService, configurationService); services.set(IStateService, new SyncDescriptor(StateService)); - services.set(IProductService, new SyncDescriptor(ProductService)); + services.set(IProductService, { _serviceBrand: undefined, ...product }); // Files const fileService = new FileService(logService); @@ -369,4 +380,4 @@ export async function main(argv: ParsedArgs): Promise { disposables.dispose(); } }); -} \ No newline at end of file +} diff --git a/src/vs/code/test/electron-main/nativeHelpers.test.ts b/src/vs/code/test/electron-main/nativeHelpers.test.ts index ff0f589d511..c8ffd0c8dbc 100644 --- a/src/vs/code/test/electron-main/nativeHelpers.test.ts +++ b/src/vs/code/test/electron-main/nativeHelpers.test.ts @@ -7,22 +7,33 @@ import * as assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; suite('Windows Native Helpers', () => { - test('windows-mutex', async () => { - if (!isWindows) { - return; - } + if (!isWindows) { + return; + } + test('windows-mutex', async () => { const mutex = await import('windows-mutex'); - assert.ok(mutex, 'Unable to load windows-mutex dependency.'); + assert.ok(mutex && typeof mutex.isActive === 'function', 'Unable to load windows-mutex dependency.'); assert.ok(typeof mutex.isActive === 'function', 'Unable to load windows-mutex dependency.'); }); test('windows-foreground-love', async () => { - if (!isWindows) { - return; - } - const foregroundLove = await import('windows-foreground-love'); - assert.ok(foregroundLove, 'Unable to load windows-foreground-love dependency.'); + assert.ok(foregroundLove && typeof foregroundLove.allowSetForegroundWindow === 'function', 'Unable to load windows-foreground-love dependency.'); }); -}); \ No newline at end of file + + test('windows-process-tree', async () => { + const processTree = await import('windows-process-tree'); + assert.ok(processTree && typeof processTree.getProcessTree === 'function', 'Unable to load windows-process-tree dependency.'); + }); + + test('vscode-windows-ca-certs', async () => { + const windowsCerts = await import('vscode-windows-ca-certs'); + assert.ok(windowsCerts, 'Unable to load vscode-windows-ca-certs dependency.'); + }); + + test('vscode-windows-registry', async () => { + const windowsRegistry = await import('vscode-windows-registry'); + assert.ok(windowsRegistry && typeof windowsRegistry.GetStringRegKey === 'function', 'Unable to load vscode-windows-registry dependency.'); + }); +}); diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 06d4b2f5307..418e8ebee98 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -49,7 +49,7 @@ export interface IPointerHandlerHelper { */ getLastViewCursorsRenderData(): IViewCursorRenderData[]; - shouldSuppressMouseDownOnViewZone(viewZoneId: number): boolean; + shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean; shouldSuppressMouseDownOnWidget(widgetId: string): boolean; /** diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 9a37d512035..f510e0f6f82 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -16,9 +16,10 @@ import { Range as EditorRange } from 'vs/editor/common/core/range'; import { HorizontalRange } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; +import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; export interface IViewZoneData { - viewZoneId: number; + viewZoneId: string; positionBefore: Position | null; positionAfter: Position | null; position: Position; @@ -410,7 +411,7 @@ class HitTestRequest extends BareHitTestRequest { let mouseColumn = this.mouseColumn; if (position && position.column < this._ctx.model.getLineMaxColumn(position.lineNumber)) { // Most likely, the line contains foreign decorations... - mouseColumn = position.column; + mouseColumn = CursorColumns.visibleColumnFromColumn(this._ctx.model.getLineContent(position.lineNumber), position.column, this._ctx.model.getOptions().tabSize) + 1; } return new MouseTarget(this.target, type, mouseColumn, position, range, detail); } diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index f0358188c86..c1a41a4981f 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -446,14 +446,8 @@ export class TextAreaHandler extends ViewPart { private _primaryCursorVisibleRange: HorizontalRange | null = null; public prepareRender(ctx: RenderingContext): void { - if (this._accessibilitySupport === AccessibilitySupport.Enabled) { - // Do not move the textarea with the cursor, as this generates accessibility events that might confuse screen readers - // See https://github.com/Microsoft/vscode/issues/26730 - this._primaryCursorVisibleRange = null; - } else { - const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn); - this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(primaryCursorPosition); - } + const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn); + this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(primaryCursorPosition); } public render(ctx: RestrictedRenderingContext): void { diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index df11729f02d..86e226d86c9 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -119,6 +119,7 @@ export class TextAreaInput extends Disposable { this._asyncTriggerCut = this._register(new RunOnceScheduler(() => this._onCut.fire(), 0)); this._textAreaState = TextAreaState.EMPTY; + this._selectionChangeListener = null; this.writeScreenReaderContent('ctor'); this._hasFocus = false; diff --git a/src/vs/editor/browser/core/editorState.ts b/src/vs/editor/browser/core/editorState.ts index 9b244cd770f..9d469299bbb 100644 --- a/src/vs/editor/browser/core/editorState.ts +++ b/src/vs/editor/browser/core/editorState.ts @@ -35,16 +35,25 @@ export class EditorState { if ((this.flags & CodeEditorStateFlag.Value) !== 0) { const model = editor.getModel(); this.modelVersionId = model ? strings.format('{0}#{1}', model.uri.toString(), model.getVersionId()) : null; + } else { + this.modelVersionId = null; } if ((this.flags & CodeEditorStateFlag.Position) !== 0) { this.position = editor.getPosition(); + } else { + this.position = null; } if ((this.flags & CodeEditorStateFlag.Selection) !== 0) { this.selection = editor.getSelection(); + } else { + this.selection = null; } if ((this.flags & CodeEditorStateFlag.Scroll) !== 0) { this.scrollLeft = editor.getScrollLeft(); this.scrollTop = editor.getScrollTop(); + } else { + this.scrollLeft = -1; + this.scrollTop = -1; } } diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 496e9cae7ab..a5e31344d3c 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -13,7 +13,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; @@ -83,17 +83,17 @@ export interface IViewZoneChangeAccessor { * @param zone Zone to create * @return A unique identifier to the view zone. */ - addZone(zone: IViewZone): number; + addZone(zone: IViewZone): string; /** * Remove a zone * @param id A unique identifier to the view zone, as returned by the `addZone` call. */ - removeZone(id: number): void; + removeZone(id: string): void; /** * Change a zone's position. * The editor will rescan the `afterLineNumber` and `afterColumn` properties of a view zone. */ - layoutZone(id: number): void; + layoutZone(id: string): void; } /** @@ -399,7 +399,7 @@ export interface ICodeEditor extends editorCommon.IEditor { */ onWillType(listener: (text: string) => void): IDisposable; /** - * An event emitted before interpreting typed characters (on the keyboard). + * An event emitted after interpreting typed characters (on the keyboard). * @event * @internal */ @@ -612,7 +612,7 @@ export interface ICodeEditor extends editorCommon.IEditor { * @param edits The edits to execute. * @param endCursorState Cursor state after the edits were applied. */ - executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: Selection[]): boolean; + executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean; /** * Execute multiple (concomitant) commands on the editor. diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index f439007c476..c7cf1da3f45 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -127,13 +127,13 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider { public refCount: number; public className: string | undefined; - public inlineClassName: string; - public inlineClassNameAffectsLetterSpacing: boolean; + public inlineClassName: string | undefined; + public inlineClassNameAffectsLetterSpacing: boolean | undefined; public beforeContentClassName: string | undefined; public afterContentClassName: string | undefined; public glyphMarginClassName: string | undefined; public isWholeLine: boolean; - public overviewRuler: IModelDecorationOverviewRulerOptions; + public overviewRuler: IModelDecorationOverviewRulerOptions | undefined; public stickiness: TrackedRangeStickiness | undefined; constructor(themeService: IThemeService, providerArgs: ProviderArguments) { @@ -229,11 +229,11 @@ const _CSS_MAP: { [prop: string]: string; } = { cursor: 'cursor:{0};', letterSpacing: 'letter-spacing:{0};', - gutterIconPath: 'background:url(\'{0}\') center center no-repeat;', + gutterIconPath: 'background:{0} center center no-repeat;', gutterIconSize: 'background-size:{0};', contentText: 'content:\'{0}\';', - contentIconPath: 'content:url(\'{0}\');', + contentIconPath: 'content:{0};', margin: 'margin:{0};', width: 'width:{0};', height: 'height:{0};' @@ -399,7 +399,7 @@ class DecorationCSSRules { if (typeof opts !== 'undefined') { this.collectBorderSettingsCSSText(opts, cssTextArr); if (typeof opts.contentIconPath !== 'undefined') { - cssTextArr.push(strings.format(_CSS_MAP.contentIconPath, dom.asDomUri(URI.revive(opts.contentIconPath)).toString(true).replace(/'/g, '%27'))); + cssTextArr.push(strings.format(_CSS_MAP.contentIconPath, dom.asCSSUrl(URI.revive(opts.contentIconPath)))); } if (typeof opts.contentText === 'string') { const truncated = opts.contentText.match(/^.*$/m)![0]; // only take first line @@ -426,7 +426,7 @@ class DecorationCSSRules { const cssTextArr: string[] = []; if (typeof opts.gutterIconPath !== 'undefined') { - cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, dom.asDomUri(URI.revive(opts.gutterIconPath)).toString(true).replace(/'/g, '%27'))); + cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, dom.asCSSUrl(URI.revive(opts.gutterIconPath)))); if (typeof opts.gutterIconSize !== 'undefined') { cssTextArr.push(strings.format(_CSS_MAP.gutterIconSize, opts.gutterIconSize)); } diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index c175034f969..7632af1b45c 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -14,16 +14,25 @@ import { IOpenerService, IOpener } from 'vs/platform/opener/common/opener'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { IDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { localize } from 'vs/nls'; +import { IProductService } from 'vs/platform/product/common/product'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import Severity from 'vs/base/common/severity'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class OpenerService implements IOpenerService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; private readonly _opener = new LinkedList(); constructor( @ICodeEditorService private readonly _editorService: ICodeEditorService, @ICommandService private readonly _commandService: ICommandService, + @IStorageService private readonly _storageService: IStorageService, + @IDialogService private readonly _dialogService: IDialogService, + @IProductService private readonly _productService: IProductService ) { // } @@ -33,7 +42,7 @@ export class OpenerService implements IOpenerService { return { dispose: remove }; } - async open(resource: URI, options?: { openToSide?: boolean }): Promise { + async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { // no scheme ?!? if (!resource.scheme) { return Promise.resolve(false); @@ -49,15 +58,58 @@ export class OpenerService implements IOpenerService { return this._doOpen(resource, options); } - private _doOpen(resource: URI, options?: { openToSide?: boolean }): Promise { + private _doOpen(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { - const { scheme, path, query, fragment } = resource; + const { scheme, authority, path, query, fragment } = resource; - if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https) || equalsIgnoreCase(scheme, Schemas.mailto)) { - // open http or default mail application - dom.windowOpenNoOpener(encodeURI(resource.toString(true))); - return Promise.resolve(true); + if (equalsIgnoreCase(scheme, Schemas.mailto) || (options && options.openExternal)) { + // open default mail application + return this._doOpenExternal(resource); + } + if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) { + let trustedDomains: string[] = ['https://code.visualstudio.com']; + try { + const trustedDomainsSrc = this._storageService.get('http.trustedDomains', StorageScope.GLOBAL); + if (trustedDomainsSrc) { + trustedDomains = JSON.parse(trustedDomainsSrc); + } + } catch (err) { } + + const domainToOpen = `${scheme}://${authority}`; + + if (isDomainTrusted(domainToOpen, trustedDomains)) { + return this._doOpenExternal(resource); + } else { + return this._dialogService.show( + Severity.Info, + localize( + 'openExternalLinkAt', + 'Do you want {0} to open the external website?\n{1}', + this._productService.nameShort, + resource.toString(true) + ), + [ + localize('openLink', 'Open Link'), + localize('cancel', 'Cancel'), + localize('configureTrustedDomains', 'Configure Trusted Domains') + ], + { + cancelId: 1 + }).then((choice) => { + if (choice === 0) { + return this._doOpenExternal(resource); + } else if (choice === 2) { + return this._commandService.executeCommand('workbench.action.configureTrustedDomains', domainToOpen).then((pickedDomains: string[]) => { + if (pickedDomains.indexOf(domainToOpen) !== -1) { + return this._doOpenExternal(resource); + } + return Promise.resolve(false); + }); + } + return Promise.resolve(false); + }); + } } else if (equalsIgnoreCase(scheme, Schemas.command)) { // run command or bail out if command isn't known if (!CommandsRegistry.getCommand(path)) { @@ -100,4 +152,28 @@ export class OpenerService implements IOpenerService { ).then(() => true); } } + + private _doOpenExternal(resource: URI): Promise { + dom.windowOpenNoOpener(encodeURI(resource.toString(true))); + + return Promise.resolve(true); + } +} + +/** + * Check whether a domain like https://www.microsoft.com matches + * the list of trusted domains. + */ +function isDomainTrusted(domain: string, trustedDomains: string[]) { + for (let i = 0; i < trustedDomains.length; i++) { + if (trustedDomains[i] === '*') { + return true; + } + + if (trustedDomains[i] === domain) { + return true; + } + } + + return false; } diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 41afb8404aa..45c711cecef 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -128,22 +128,6 @@ export class View extends ViewEventHandler { this._textAreaHandler = new TextAreaHandler(this._context, viewController, this.createTextAreaHandlerHelper()); this.viewParts.push(this._textAreaHandler); - this.createViewParts(); - this._setLayout(); - - // Pointer handler - this.pointerHandler = this._register(new PointerHandler(this._context, viewController, this.createPointerHandlerHelper())); - - this._register(model.addEventListener((events: viewEvents.ViewEvent[]) => { - this.eventDispatcher.emitMany(events); - })); - - this._register(this._cursor.addEventListener((events: viewEvents.ViewEvent[]) => { - this.eventDispatcher.emitMany(events); - })); - } - - private createViewParts(): void { // These two dom nodes must be constructed up front, since references are needed in the layout provider (scrolling & co.) this.linesContent = createFastDomNode(document.createElement('div')); this.linesContent.setClassName('lines-content' + ' monaco-editor-background'); @@ -233,6 +217,19 @@ export class View extends ViewEventHandler { this.overflowGuardContainer.appendChild(minimap.getDomNode()); this.domNode.appendChild(this.overflowGuardContainer); this.domNode.appendChild(this.contentWidgets.overflowingContentWidgetsDomNode); + + this._setLayout(); + + // Pointer handler + this.pointerHandler = this._register(new PointerHandler(this._context, viewController, this.createPointerHandlerHelper())); + + this._register(model.addEventListener((events: viewEvents.ViewEvent[]) => { + this.eventDispatcher.emitMany(events); + })); + + this._register(this._cursor.addEventListener((events: viewEvents.ViewEvent[]) => { + this.eventDispatcher.emitMany(events); + })); } private _flushAccumulatedAndRenderNow(): void { @@ -251,7 +248,7 @@ export class View extends ViewEventHandler { getLastViewCursorsRenderData: () => { return this.viewCursors.getLastRenderData() || []; }, - shouldSuppressMouseDownOnViewZone: (viewZoneId: number) => { + shouldSuppressMouseDownOnViewZone: (viewZoneId: string) => { return this.viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId); }, shouldSuppressMouseDownOnWidget: (widgetId: string) => { @@ -476,17 +473,17 @@ export class View extends ViewEventHandler { this._renderOnce(() => { const changeAccessor: editorBrowser.IViewZoneChangeAccessor = { - addZone: (zone: editorBrowser.IViewZone): number => { + addZone: (zone: editorBrowser.IViewZone): string => { zonesHaveChanged = true; return this.viewZones.addZone(zone); }, - removeZone: (id: number): void => { + removeZone: (id: string): void => { if (!id) { return; } zonesHaveChanged = this.viewZones.removeZone(id) || zonesHaveChanged; }, - layoutZone: (id: number): void => { + layoutZone: (id: string): void => { if (!id) { return; } @@ -541,7 +538,7 @@ export class View extends ViewEventHandler { public layoutContentWidget(widgetData: IContentWidgetData): void { const newPosition = widgetData.position ? widgetData.position.position : null; - const newRange = widgetData.position ? widgetData.position.range : null; + const newRange = widgetData.position ? widgetData.position.range || null : null; const newPreference = widgetData.position ? widgetData.position.preference : null; this.contentWidgets.setWidgetPosition(widgetData.widget, newPosition, newRange, newPreference); this._scheduleRender(); diff --git a/src/vs/editor/browser/view/viewLayer.ts b/src/vs/editor/browser/view/viewLayer.ts index b16f615c3b1..ecdd665be73 100644 --- a/src/vs/editor/browser/view/viewLayer.ts +++ b/src/vs/editor/browser/view/viewLayer.ts @@ -34,8 +34,8 @@ export interface ILine { export class RenderedLinesCollection { private readonly _createLine: () => T; - private _lines: T[]; - private _rendLineNumberStart: number; + private _lines!: T[]; + private _rendLineNumberStart!: number; constructor(createLine: () => T) { this._createLine = createLine; diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index c275168cca7..cae3be1ed16 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -14,7 +14,6 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; -import { withUndefinedAsNull } from 'vs/base/common/types'; class Coordinate { _coordinateBrand: void; @@ -111,7 +110,7 @@ export class ViewContentWidgets extends ViewPart { this.setShouldRender(); } - public setWidgetPosition(widget: IContentWidget, position: IPosition | null | undefined, range: IRange | null | undefined, preference: ContentWidgetPositionPreference[] | null | undefined): void { + public setWidgetPosition(widget: IContentWidget, position: IPosition | null, range: IRange | null, preference: ContentWidgetPositionPreference[] | null): void { const myWidget = this._widgets[widget.getId()]; myWidget.setPosition(position, range, preference); @@ -202,8 +201,8 @@ class Widget { this._context = context; this._viewDomNode = viewDomNode; this._actual = actual; - this.domNode = createFastDomNode(this._actual.getDomNode()); + this.domNode = createFastDomNode(this._actual.getDomNode()); this.id = this._actual.getId(); this.allowEditorOverflow = this._actual.allowEditorOverflow || false; this.suppressMouseDown = this._actual.suppressMouseDown || false; @@ -213,7 +212,10 @@ class Widget { this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft; this._lineHeight = this._context.configuration.editor.lineHeight; - this._setPosition(null, null); + this._position = null; + this._range = null; + this._viewPosition = null; + this._viewRange = null; this._preference = []; this._cachedDomNodeClientWidth = -1; this._cachedDomNodeClientHeight = -1; @@ -242,9 +244,9 @@ class Widget { this._setPosition(this._position, this._range); } - private _setPosition(position: IPosition | null | undefined, range: IRange | null | undefined): void { - this._position = withUndefinedAsNull(position); - this._range = withUndefinedAsNull(range); + private _setPosition(position: IPosition | null, range: IRange | null): void { + this._position = position; + this._range = range; this._viewPosition = null; this._viewRange = null; @@ -270,9 +272,9 @@ class Widget { ); } - public setPosition(position: IPosition | null | undefined, range: IRange | null | undefined, preference: ContentWidgetPositionPreference[] | null | undefined): void { + public setPosition(position: IPosition | null, range: IRange | null, preference: ContentWidgetPositionPreference[] | null): void { this._setPosition(position, range); - this._preference = withUndefinedAsNull(preference); + this._preference = preference; this._cachedDomNodeClientWidth = -1; this._cachedDomNodeClientHeight = -1; } diff --git a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts index 323a8097cec..ea713746e0d 100644 --- a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts +++ b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts @@ -21,6 +21,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { private _renderResult: string[] | null; private _enabled: boolean; private _activeIndentEnabled: boolean; + private _maxIndentLeft: number; constructor(context: ViewContext) { super(); @@ -30,6 +31,9 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { this._spaceWidth = this._context.configuration.editor.fontInfo.spaceWidth; this._enabled = this._context.configuration.editor.viewInfo.renderIndentGuides; this._activeIndentEnabled = this._context.configuration.editor.viewInfo.highlightActiveIndentGuide; + const wrappingColumn = this._context.configuration.editor.wrappingInfo.wrappingColumn; + this._maxIndentLeft = wrappingColumn === -1 ? -1 : (wrappingColumn * this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth); + this._renderResult = null; this._context.addEventHandler(this); @@ -54,6 +58,10 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { this._enabled = this._context.configuration.editor.viewInfo.renderIndentGuides; this._activeIndentEnabled = this._context.configuration.editor.viewInfo.highlightActiveIndentGuide; } + if (e.wrappingInfo || e.fontInfo) { + const wrappingColumn = this._context.configuration.editor.wrappingInfo.wrappingColumn; + this._maxIndentLeft = wrappingColumn === -1 ? -1 : (wrappingColumn * this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth); + } return true; } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { @@ -133,7 +141,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr'); result += `
`; left += indentWidth; - if (left > scrollWidth) { + if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) { break; } } diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts index 8ccd25f7453..3e9b2d6c68b 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts @@ -20,12 +20,12 @@ export class LineNumbersOverlay extends DynamicViewOverlay { private readonly _context: ViewContext; - private _lineHeight: number; - private _renderLineNumbers: RenderLineNumbersType; - private _renderCustomLineNumbers: ((lineNumber: number) => string) | null; - private _renderFinalNewline: boolean; - private _lineNumbersLeft: number; - private _lineNumbersWidth: number; + private _lineHeight!: number; + private _renderLineNumbers!: RenderLineNumbersType; + private _renderCustomLineNumbers!: ((lineNumber: number) => string) | null; + private _renderFinalNewline!: boolean; + private _lineNumbersLeft!: number; + private _lineNumbersWidth!: number; private _lastCursorModelPosition: Position; private _renderResult: string[] | null; diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index ba4d49c639a..580311d7691 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -71,6 +71,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, private _typicalHalfwidthCharacterWidth: number; private _isViewportWrapping: boolean; private _revealHorizontalRightPadding: number; + private _scrollOff: number; private _canUseLayerHinting: boolean; private _viewLineOptions: ViewLineOptions; @@ -94,6 +95,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._typicalHalfwidthCharacterWidth = conf.editor.fontInfo.typicalHalfwidthCharacterWidth; this._isViewportWrapping = conf.editor.wrappingInfo.isViewportWrapping; this._revealHorizontalRightPadding = conf.editor.viewInfo.revealHorizontalRightPadding; + this._scrollOff = conf.editor.viewInfo.cursorSurroundingLines; this._canUseLayerHinting = conf.editor.canUseLayerHinting; this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type); @@ -150,6 +152,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } if (e.viewInfo) { this._revealHorizontalRightPadding = conf.editor.viewInfo.revealHorizontalRightPadding; + this._scrollOff = conf.editor.viewInfo.cursorSurroundingLines; } if (e.canUseLayerHinting) { this._canUseLayerHinting = conf.editor.canUseLayerHinting; @@ -596,6 +599,11 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // Have a box that includes one extra line height (for the horizontal scrollbar) boxStartY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.startLineNumber); boxEndY = this._context.viewLayout.getVerticalOffsetForLineNumber(range.endLineNumber) + this._lineHeight; + + const context = Math.min((viewportHeight / this._lineHeight) / 2, this._scrollOff); + boxStartY -= context * this._lineHeight; + boxEndY += Math.max(0, (context - 1)) * this._lineHeight; + if (verticalType === viewEvents.VerticalRevealType.Simple || verticalType === viewEvents.VerticalRevealType.Bottom) { // Reveal one line more when the last line would be covered by the scrollbar - arrow down case or revealing a line explicitly at bottom boxEndY += this._lineHeight; diff --git a/src/vs/editor/browser/viewParts/margin/margin.ts b/src/vs/editor/browser/viewParts/margin/margin.ts index eda44ab11d6..ab4f24c7bf3 100644 --- a/src/vs/editor/browser/viewParts/margin/margin.ts +++ b/src/vs/editor/browser/viewParts/margin/margin.ts @@ -28,7 +28,16 @@ export class Margin extends ViewPart { this._glyphMarginLeft = this._context.configuration.editor.layoutInfo.glyphMarginLeft; this._glyphMarginWidth = this._context.configuration.editor.layoutInfo.glyphMarginWidth; - this._domNode = this._createDomNode(); + this._domNode = createFastDomNode(document.createElement('div')); + this._domNode.setClassName(Margin.OUTER_CLASS_NAME); + this._domNode.setPosition('absolute'); + this._domNode.setAttribute('role', 'presentation'); + this._domNode.setAttribute('aria-hidden', 'true'); + + this._glyphMarginBackgroundDomNode = createFastDomNode(document.createElement('div')); + this._glyphMarginBackgroundDomNode.setClassName(Margin.CLASS_NAME); + + this._domNode.appendChild(this._glyphMarginBackgroundDomNode); } public dispose(): void { @@ -39,20 +48,6 @@ export class Margin extends ViewPart { return this._domNode; } - private _createDomNode(): FastDomNode { - const domNode = createFastDomNode(document.createElement('div')); - domNode.setClassName(Margin.OUTER_CLASS_NAME); - domNode.setPosition('absolute'); - domNode.setAttribute('role', 'presentation'); - domNode.setAttribute('aria-hidden', 'true'); - - this._glyphMarginBackgroundDomNode = createFastDomNode(document.createElement('div')); - this._glyphMarginBackgroundDomNode.setClassName(Margin.CLASS_NAME); - - domNode.appendChild(this._glyphMarginBackgroundDomNode); - return domNode; - } - // --- begin event handlers public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index c883223ae31..1182f2ee8ab 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -742,10 +742,6 @@ export class Minimap extends ViewPart { canvasContext.clearRect(0, 0, canvasInnerWidth, canvasInnerHeight); - // If the minimap is rendered using blocks, text takes up half the line height - const lineHeightRatio = renderMinimap === RenderMinimap.LargeBlocks || renderMinimap === RenderMinimap.SmallBlocks ? 0.5 : 1; - const height = lineHeight * lineHeightRatio; - // Loop over decorations, ignoring those that don't have the minimap property set and rendering rectangles for each line the decoration spans const lineOffsetMap = new Map(); for (let i = 0; i < decorations.length; i++) { @@ -756,7 +752,7 @@ export class Minimap extends ViewPart { } for (let line = decoration.range.startLineNumber; line <= decoration.range.endLineNumber; line++) { - this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration, layout, line, height, lineHeight, tabSize, characterWidth); + this.renderDecorationOnLine(canvasContext, lineOffsetMap, decoration, layout, line, lineHeight, lineHeight, tabSize, characterWidth); } } diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index 2aaaac909bf..c186568ad8f 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -201,7 +201,7 @@ export class DecorationsOverviewRuler extends ViewPart { private readonly _tokensColorTrackerListener: IDisposable; private readonly _domNode: FastDomNode; - private _settings: Settings; + private _settings!: Settings; private _cursorPositions: Position[]; constructor(context: ViewContext) { diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index 3cf99a75291..37105276557 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -68,7 +68,7 @@ export class ViewCursor { Configuration.applyFontInfo(this._domNode, this._context.configuration.editor.fontInfo); this._domNode.setDisplay('none'); - this.updatePosition(new Position(1, 1)); + this._position = new Position(1, 1); this._lastRenderedContent = ''; this._renderData = null; @@ -113,7 +113,7 @@ export class ViewCursor { } public onCursorPositionChanged(position: Position): boolean { - this.updatePosition(position); + this._position = position; return true; } @@ -210,8 +210,4 @@ export class ViewCursor { width: 2 }; } - - private updatePosition(newPosition: Position): void { - this._position = newPosition; - } } diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index 982b021bd1a..023e031dd02 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -49,6 +49,8 @@ export class ViewCursors extends ViewPart { this._cursorSmoothCaretAnimation = this._context.configuration.editor.viewInfo.cursorSmoothCaretAnimation; this._selectionIsEmpty = true; + this._isVisible = false; + this._primaryCursor = new ViewCursor(this._context); this._secondaryCursors = []; this._renderData = []; diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index a51fbe5bf02..6c01df4e0b6 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -14,7 +14,7 @@ import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel'; export interface IMyViewZone { - whitespaceId: number; + whitespaceId: string; delegate: IViewZone; isVisible: boolean; domNode: FastDomNode; @@ -74,7 +74,7 @@ export class ViewZones extends ViewPart { const id = keys[i]; const zone = this._zones[id]; const props = this._computeWhitespaceProps(zone.delegate); - if (this._context.viewLayout.changeWhitespace(parseInt(id, 10), props.afterViewLineNumber, props.heightInPx)) { + if (this._context.viewLayout.changeWhitespace(id, props.afterViewLineNumber, props.heightInPx)) { this._safeCallOnComputedHeight(zone.delegate, props.heightInPx); hadAChange = true; } @@ -183,7 +183,7 @@ export class ViewZones extends ViewPart { }; } - public addZone(zone: IViewZone): number { + public addZone(zone: IViewZone): string { const props = this._computeWhitespaceProps(zone); const whitespaceId = this._context.viewLayout.addWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx); @@ -200,18 +200,18 @@ export class ViewZones extends ViewPart { myZone.domNode.setPosition('absolute'); myZone.domNode.domNode.style.width = '100%'; myZone.domNode.setDisplay('none'); - myZone.domNode.setAttribute('monaco-view-zone', myZone.whitespaceId.toString()); + myZone.domNode.setAttribute('monaco-view-zone', myZone.whitespaceId); this.domNode.appendChild(myZone.domNode); if (myZone.marginDomNode) { myZone.marginDomNode.setPosition('absolute'); myZone.marginDomNode.domNode.style.width = '100%'; myZone.marginDomNode.setDisplay('none'); - myZone.marginDomNode.setAttribute('monaco-view-zone', myZone.whitespaceId.toString()); + myZone.marginDomNode.setAttribute('monaco-view-zone', myZone.whitespaceId); this.marginDomNode.appendChild(myZone.marginDomNode); } - this._zones[myZone.whitespaceId.toString()] = myZone; + this._zones[myZone.whitespaceId] = myZone; this.setShouldRender(); @@ -219,10 +219,10 @@ export class ViewZones extends ViewPart { return myZone.whitespaceId; } - public removeZone(id: number): boolean { - if (this._zones.hasOwnProperty(id.toString())) { - const zone = this._zones[id.toString()]; - delete this._zones[id.toString()]; + public removeZone(id: string): boolean { + if (this._zones.hasOwnProperty(id)) { + const zone = this._zones[id]; + delete this._zones[id]; this._context.viewLayout.removeWhitespace(zone.whitespaceId); zone.domNode.removeAttribute('monaco-visible-view-zone'); @@ -242,10 +242,10 @@ export class ViewZones extends ViewPart { return false; } - public layoutZone(id: number): boolean { + public layoutZone(id: string): boolean { let changed = false; - if (this._zones.hasOwnProperty(id.toString())) { - const zone = this._zones[id.toString()]; + if (this._zones.hasOwnProperty(id)) { + const zone = this._zones[id]; const props = this._computeWhitespaceProps(zone.delegate); // const newOrdinal = this._getZoneOrdinal(zone.delegate); changed = this._context.viewLayout.changeWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx) || changed; @@ -259,9 +259,9 @@ export class ViewZones extends ViewPart { return changed; } - public shouldSuppressMouseDownOnViewZone(id: number): boolean { - if (this._zones.hasOwnProperty(id.toString())) { - const zone = this._zones[id.toString()]; + public shouldSuppressMouseDownOnViewZone(id: string): boolean { + if (this._zones.hasOwnProperty(id)) { + const zone = this._zones[id]; return Boolean(zone.delegate.suppressMouseDown); } return false; @@ -314,7 +314,7 @@ export class ViewZones extends ViewPart { let hasVisibleZone = false; for (let i = 0, len = visibleWhitespaces.length; i < len; i++) { - visibleZones[visibleWhitespaces[i].id.toString()] = visibleWhitespaces[i]; + visibleZones[visibleWhitespaces[i].id] = visibleWhitespaces[i]; hasVisibleZone = true; } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index c3be18fe35d..0ac879ee78a 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -33,13 +33,13 @@ import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { InternalEditorAction } from 'vs/editor/common/editorAction'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; +import { EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecoration, IModelDecorationOptions, IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel, ICursorStateComputer } from 'vs/editor/common/model'; import { ClassName } from 'vs/editor/common/model/intervalTree'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import * as modes from 'vs/editor/common/modes'; import { editorUnnecessaryCodeBorder, editorUnnecessaryCodeOpacity } from 'vs/editor/common/view/editorColorRegistry'; -import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorWarningBorder, editorWarningForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorWarningBorder, editorWarningForeground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { VerticalRevealType } from 'vs/editor/common/view/viewEvents'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; @@ -279,7 +279,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService])); - this._attachModel(null); + this._modelData = null; this._contributions = {}; this._actions = {}; @@ -980,7 +980,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return true; } - public executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: Selection[]): boolean { + public executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean { if (!this._modelData) { return false; } @@ -989,14 +989,16 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return false; } - this._modelData.model.pushEditOperations(this._modelData.cursor.getSelections(), edits, () => { - return endCursorState ? endCursorState : null; - }); - - if (endCursorState) { - this._modelData.cursor.setSelections(source, endCursorState); + let cursorStateComputer: ICursorStateComputer; + if (!endCursorState) { + cursorStateComputer = () => null; + } else if (Array.isArray(endCursorState)) { + cursorStateComputer = () => endCursorState; + } else { + cursorStateComputer = endCursorState; } + this._modelData.cursor.executeEdits(source, edits, cursorStateComputer); return true; } @@ -1849,5 +1851,6 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.${SHOW_UNUSED_ENABLED_CLASS} .monaco-editor .${ClassName.EditorUnnecessaryDecoration} { border-bottom: 2px dashed ${unnecessaryBorder}; }`); } - collector.addRule(`.monaco-editor .${ClassName.EditorDeprecatedInlineDecoration} { text-decoration: line-through; }`); + const deprecatedForeground = theme.getColor(editorForeground) || 'inherit'; + collector.addRule(`.monaco-editor .${ClassName.EditorDeprecatedInlineDecoration} { text-decoration: line-through; text-decoration-color: ${deprecatedForeground}}`); }); diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 9c3c0df63e8..ee32431b77a 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { ISashEvent, IVerticalSashLayoutProvider, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { RunOnceScheduler, IntervalTimer } from 'vs/base/common/async'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -69,7 +69,7 @@ interface IDiffEditorWidgetStyle { } class VisualEditorState { - private _zones: number[]; + private _zones: string[]; private _zonesMap: { [zoneId: string]: boolean; }; private _decorations: string[]; @@ -156,17 +156,17 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _width: number; private _height: number; private _reviewHeight: number; - private readonly _measureDomElementToken: number; + private readonly _measureDomElementToken: IntervalTimer | null; - private originalEditor: CodeEditorWidget; - private _originalDomNode: HTMLElement; + private readonly originalEditor: CodeEditorWidget; + private readonly _originalDomNode: HTMLElement; private readonly _originalEditorState: VisualEditorState; - private _originalOverviewRuler: editorBrowser.IOverviewRuler; + private _originalOverviewRuler: editorBrowser.IOverviewRuler | null; - private modifiedEditor: CodeEditorWidget; - private _modifiedDomNode: HTMLElement; + private readonly modifiedEditor: CodeEditorWidget; + private readonly _modifiedDomNode: HTMLElement; private readonly _modifiedEditorState: VisualEditorState; - private _modifiedOverviewRuler: editorBrowser.IOverviewRuler; + private _modifiedOverviewRuler: editorBrowser.IOverviewRuler | null; private _currentlyChangingViewZones: boolean; private _beginUpdateDecorationsTimeout: number; @@ -182,7 +182,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _renderSideBySide: boolean; private _renderIndicators: boolean; private _enableSplitViewResizing: boolean; - private _strategy: IDiffEditorWidgetStyle; + private _strategy!: IDiffEditorWidgetStyle; private readonly _updateDecorationsRunner: RunOnceScheduler; @@ -264,8 +264,19 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE })); this._containerDomElement.appendChild(this._overviewDomElement); - this._createLeftHandSide(); - this._createRightHandSide(); + // Create left side + this._originalDomNode = document.createElement('div'); + this._originalDomNode.className = 'editor original'; + this._originalDomNode.style.position = 'absolute'; + this._originalDomNode.style.height = '100%'; + this._containerDomElement.appendChild(this._originalDomNode); + + // Create right side + this._modifiedDomNode = document.createElement('div'); + this._modifiedDomNode.className = 'editor modified'; + this._modifiedDomNode.style.position = 'absolute'; + this._modifiedDomNode.style.height = '100%'; + this._containerDomElement.appendChild(this._modifiedDomNode); this._beginUpdateDecorationsTimeout = -1; this._currentlyChangingViewZones = false; @@ -297,8 +308,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE rightServices.set(IContextKeyService, rightContextKeyService); const rightScopedInstantiationService = instantiationService.createChild(rightServices); - this._createLeftHandSideEditor(options, leftScopedInstantiationService); - this._createRightHandSideEditor(options, rightScopedInstantiationService); + this.originalEditor = this._createLeftHandSideEditor(options, leftScopedInstantiationService); + this.modifiedEditor = this._createRightHandSideEditor(options, rightScopedInstantiationService); + + this._originalOverviewRuler = null; + this._modifiedOverviewRuler = null; this._reviewPane = new DiffReview(this); this._containerDomElement.appendChild(this._reviewPane.domNode.domNode); @@ -306,7 +320,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._containerDomElement.appendChild(this._reviewPane.actionBarContainer.domNode); if (options.automaticLayout) { - this._measureDomElementToken = window.setInterval(() => this._measureDomElement(false), 100); + this._measureDomElementToken = new IntervalTimer(); + this._measureDomElementToken.cancelAndSet(() => this._measureDomElement(false), 100); + } else { + this._measureDomElementToken = null; } // enableSplitViewResizing @@ -386,26 +403,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._layoutOverviewRulers(); } - private _createLeftHandSide(): void { - this._originalDomNode = document.createElement('div'); - this._originalDomNode.className = 'editor original'; - this._originalDomNode.style.position = 'absolute'; - this._originalDomNode.style.height = '100%'; - this._containerDomElement.appendChild(this._originalDomNode); - } + private _createLeftHandSideEditor(options: editorOptions.IDiffEditorOptions, instantiationService: IInstantiationService): CodeEditorWidget { + const editor = this._createInnerEditor(instantiationService, this._originalDomNode, this._adjustOptionsForLeftHandSide(options, this._originalIsEditable)); - private _createRightHandSide(): void { - this._modifiedDomNode = document.createElement('div'); - this._modifiedDomNode.className = 'editor modified'; - this._modifiedDomNode.style.position = 'absolute'; - this._modifiedDomNode.style.height = '100%'; - this._containerDomElement.appendChild(this._modifiedDomNode); - } - - private _createLeftHandSideEditor(options: editorOptions.IDiffEditorOptions, instantiationService: IInstantiationService): void { - this.originalEditor = this._createInnerEditor(instantiationService, this._originalDomNode, this._adjustOptionsForLeftHandSide(options, this._originalIsEditable)); - - this._register(this.originalEditor.onDidScrollChange((e) => { + this._register(editor.onDidScrollChange((e) => { if (this._isHandlingScrollEvent) { return; } @@ -422,21 +423,23 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._layoutOverviewViewport(); })); - this._register(this.originalEditor.onDidChangeViewZones(() => { + this._register(editor.onDidChangeViewZones(() => { this._onViewZonesChanged(); })); - this._register(this.originalEditor.onDidChangeModelContent(() => { + this._register(editor.onDidChangeModelContent(() => { if (this._isVisible) { this._beginUpdateDecorationsSoon(); } })); + + return editor; } - private _createRightHandSideEditor(options: editorOptions.IDiffEditorOptions, instantiationService: IInstantiationService): void { - this.modifiedEditor = this._createInnerEditor(instantiationService, this._modifiedDomNode, this._adjustOptionsForRightHandSide(options)); + private _createRightHandSideEditor(options: editorOptions.IDiffEditorOptions, instantiationService: IInstantiationService): CodeEditorWidget { + const editor = this._createInnerEditor(instantiationService, this._modifiedDomNode, this._adjustOptionsForRightHandSide(options)); - this._register(this.modifiedEditor.onDidScrollChange((e) => { + this._register(editor.onDidScrollChange((e) => { if (this._isHandlingScrollEvent) { return; } @@ -453,21 +456,23 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._layoutOverviewViewport(); })); - this._register(this.modifiedEditor.onDidChangeViewZones(() => { + this._register(editor.onDidChangeViewZones(() => { this._onViewZonesChanged(); })); - this._register(this.modifiedEditor.onDidChangeConfiguration((e) => { - if (e.fontInfo && this.modifiedEditor.getModel()) { + this._register(editor.onDidChangeConfiguration((e) => { + if (e.fontInfo && editor.getModel()) { this._onViewZonesChanged(); } })); - this._register(this.modifiedEditor.onDidChangeModelContent(() => { + this._register(editor.onDidChangeModelContent(() => { if (this._isVisible) { this._beginUpdateDecorationsSoon(); } })); + + return editor; } protected _createInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: editorOptions.IEditorOptions): CodeEditorWidget { @@ -482,7 +487,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._beginUpdateDecorationsTimeout = -1; } - window.clearInterval(this._measureDomElementToken); + if (this._measureDomElementToken) { + this._measureDomElementToken.dispose(); + } this._cleanViewZonesAndDecorations(); @@ -818,6 +825,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _layoutOverviewRulers(): void { + if (!this._originalOverviewRuler || !this._modifiedOverviewRuler) { + return; + } let freeSpace = DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH - 2 * DiffEditorWidget.ONE_OVERVIEW_WIDTH; let layoutInfo = this.modifiedEditor.getLayoutInfo(); if (layoutInfo) { @@ -919,7 +929,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _updateDecorations(): void { - if (!this.originalEditor.getModel() || !this.modifiedEditor.getModel()) { + if (!this.originalEditor.getModel() || !this.modifiedEditor.getModel() || !this._originalOverviewRuler || !this._modifiedOverviewRuler) { return; } const lineChanges = (this._diffComputationResult ? this._diffComputationResult.changes : []); @@ -1186,12 +1196,14 @@ interface IDataSource { abstract class DiffEditorWidgetStyle extends Disposable implements IDiffEditorWidgetStyle { _dataSource: IDataSource; - _insertColor: Color; - _removeColor: Color; + _insertColor: Color | null; + _removeColor: Color | null; constructor(dataSource: IDataSource) { super(); this._dataSource = dataSource; + this._insertColor = null; + this._removeColor = null; } public applyColors(theme: ITheme): boolean { @@ -1257,6 +1269,7 @@ class ForeignViewZonesIterator { constructor(source: IEditorWhitespace[]) { this._source = source; this._index = -1; + this.current = null; this.advance(); } @@ -1535,7 +1548,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE private readonly _sash: Sash; private _sashRatio: number | null; private _sashPosition: number | null; - private _startSashPosition: number; + private _startSashPosition: number | null; constructor(dataSource: IDataSource, enableSplitViewResizing: boolean) { super(dataSource); @@ -1543,6 +1556,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE this._disableSash = (enableSplitViewResizing === false); this._sashRatio = null; this._sashPosition = null; + this._startSashPosition = null; this._sash = this._register(new Sash(this._dataSource.getContainerDomNode(), this)); if (this._disableSash) { @@ -1599,7 +1613,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE private onSashDrag(e: ISashEvent): void { let w = this._dataSource.getWidth(); let contentWidth = w - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH; - let sashPosition = this.layout((this._startSashPosition + (e.currentX - e.startX)) / contentWidth); + let sashPosition = this.layout((this._startSashPosition! + (e.currentX - e.startX)) / contentWidth); this._sashRatio = sashPosition / contentWidth; @@ -1634,7 +1648,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE } protected _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { - const overviewZoneColor = this._removeColor.toString(); + const overviewZoneColor = String(this._removeColor); let result: IEditorDiffDecorations = { decorations: [], @@ -1694,7 +1708,7 @@ class DiffEditorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffE } protected _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { - const overviewZoneColor = this._insertColor.toString(); + const overviewZoneColor = String(this._insertColor); let result: IEditorDiffDecorations = { decorations: [], @@ -1814,7 +1828,7 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito } protected _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { - const overviewZoneColor = this._removeColor.toString(); + const overviewZoneColor = String(this._removeColor); let result: IEditorDiffDecorations = { decorations: [], @@ -1843,7 +1857,7 @@ class DiffEditorWidgetInline extends DiffEditorWidgetStyle implements IDiffEdito } protected _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { - const overviewZoneColor = this._insertColor.toString(); + const overviewZoneColor = String(this._insertColor); let result: IEditorDiffDecorations = { decorations: [], diff --git a/src/vs/editor/common/commands/replaceCommand.ts b/src/vs/editor/common/commands/replaceCommand.ts index c6cd6494050..6cd736b2adf 100644 --- a/src/vs/editor/common/commands/replaceCommand.ts +++ b/src/vs/editor/common/commands/replaceCommand.ts @@ -101,12 +101,13 @@ export class ReplaceCommandThatPreservesSelection implements ICommand { private readonly _range: Range; private readonly _text: string; private readonly _initialSelection: Selection; - private _selectionId: string; + private _selectionId: string | null; constructor(editRange: Range, text: string, initialSelection: Selection) { this._range = editRange; this._text = text; this._initialSelection = initialSelection; + this._selectionId = null; } public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { @@ -115,6 +116,6 @@ export class ReplaceCommandThatPreservesSelection implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this._selectionId); + return helper.getTrackedSelection(this._selectionId!); } } diff --git a/src/vs/editor/common/commands/shiftCommand.ts b/src/vs/editor/common/commands/shiftCommand.ts index 16ff871c456..b4cbf839717 100644 --- a/src/vs/editor/common/commands/shiftCommand.ts +++ b/src/vs/editor/common/commands/shiftCommand.ts @@ -70,13 +70,14 @@ export class ShiftCommand implements ICommand { private readonly _opts: IShiftCommandOpts; private readonly _selection: Selection; - private _selectionId: string; + private _selectionId: string | null; private _useLastEditRangeForCursorEndPosition: boolean; private _selectionStartColumnStaysPut: boolean; constructor(range: Selection, opts: IShiftCommandOpts) { this._opts = opts; this._selection = range; + this._selectionId = null; this._useLastEditRangeForCursorEndPosition = false; this._selectionStartColumnStaysPut = false; } @@ -241,7 +242,7 @@ export class ShiftCommand implements ICommand { let lastOp = helper.getInverseEditOperations()[0]; return new Selection(lastOp.range.endLineNumber, lastOp.range.endColumn, lastOp.range.endLineNumber, lastOp.range.endColumn); } - const result = helper.getTrackedSelection(this._selectionId); + const result = helper.getTrackedSelection(this._selectionId!); if (this._selectionStartColumnStaysPut) { // The selection start should not move diff --git a/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts b/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts index 2d442842b97..1197109912d 100644 --- a/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts +++ b/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts @@ -13,28 +13,29 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod export class TrimTrailingWhitespaceCommand implements ICommand { - private readonly selection: Selection; - private selectionId: string; - private readonly cursors: Position[]; + private readonly _selection: Selection; + private _selectionId: string | null; + private readonly _cursors: Position[]; constructor(selection: Selection, cursors: Position[]) { - this.selection = selection; - this.cursors = cursors; + this._selection = selection; + this._cursors = cursors; + this._selectionId = null; } public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { - let ops = trimTrailingWhitespace(model, this.cursors); + let ops = trimTrailingWhitespace(model, this._cursors); for (let i = 0, len = ops.length; i < len; i++) { let op = ops[i]; builder.addEditOperation(op.range, op.text); } - this.selectionId = builder.trackSelection(this.selection); + this._selectionId = builder.trackSelection(this._selection); } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this.selectionId); + return helper.getTrackedSelection(this._selectionId!); } } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 5d1818712b0..65f579a296e 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -68,7 +68,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed public readonly isSimpleWidget: boolean; protected _rawOptions: editorOptions.IEditorOptions; protected _validatedOptions: editorOptions.IValidatedEditorOptions; - public editor: editorOptions.InternalEditorOptions; + public editor!: editorOptions.InternalEditorOptions; private _isDominatedByLongLines: boolean; private _lineNumbersDigitCount: number; @@ -268,6 +268,11 @@ const editorConfiguration: IConfigurationNode = { 'default': 'on', 'description': nls.localize('lineNumbers', "Controls the display of line numbers.") }, + 'editor.cursorSurroundingLines': { + 'type': 'number', + 'default': EDITOR_DEFAULTS.viewInfo.cursorSurroundingLines, + 'description': nls.localize('cursorSurroundingLines', "Controls the minimal number of visible leading and trailing lines surrounding the cursor. Known as 'scrollOff' or `scrollOffset` in some other editors.") + }, 'editor.renderFinalNewline': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.viewInfo.renderFinalNewline, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 79ba4dc836d..71b0cbb8843 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -268,6 +268,11 @@ export interface IEditorOptions { * Defaults to true. */ lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); + /** + * Controls the minimal number of visible leading and trailing lines surrounding the cursor. + * Defaults to 0. + */ + cursorSurroundingLines?: number; /** * Render last line number when the file ends with a newline. * Defaults to true. @@ -982,6 +987,7 @@ export interface InternalEditorViewOptions { readonly ariaLabel: string; readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; + readonly cursorSurroundingLines: number; readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; @@ -1290,6 +1296,7 @@ export class InternalEditorOptions { && a.ariaLabel === b.ariaLabel && a.renderLineNumbers === b.renderLineNumbers && a.renderCustomLineNumbers === b.renderCustomLineNumbers + && a.cursorSurroundingLines === b.cursorSurroundingLines && a.renderFinalNewline === b.renderFinalNewline && a.selectOnLineNumbers === b.selectOnLineNumbers && a.glyphMargin === b.glyphMargin @@ -2049,6 +2056,7 @@ export class EditorOptionsValidator { disableMonospaceOptimizations: disableMonospaceOptimizations, rulers: rulers, ariaLabel: _string(opts.ariaLabel, defaults.ariaLabel), + cursorSurroundingLines: _clampedInt(opts.cursorSurroundingLines, defaults.cursorWidth, 0, Number.MAX_VALUE), renderLineNumbers: renderLineNumbers, renderCustomLineNumbers: renderCustomLineNumbers, renderFinalNewline: _boolean(opts.renderFinalNewline, defaults.renderFinalNewline), @@ -2172,6 +2180,7 @@ export class InternalEditorOptionsFactory { ariaLabel: (accessibilityIsOff ? nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press Alt+F1 for options.") : opts.viewInfo.ariaLabel), renderLineNumbers: opts.viewInfo.renderLineNumbers, renderCustomLineNumbers: opts.viewInfo.renderCustomLineNumbers, + cursorSurroundingLines: opts.viewInfo.cursorSurroundingLines, renderFinalNewline: opts.viewInfo.renderFinalNewline, selectOnLineNumbers: opts.viewInfo.selectOnLineNumbers, glyphMargin: opts.viewInfo.glyphMargin, @@ -2191,7 +2200,7 @@ export class InternalEditorOptionsFactory { stopRenderingLineAfter: opts.viewInfo.stopRenderingLineAfter, renderWhitespace: (accessibilityIsOn ? 'none' : opts.viewInfo.renderWhitespace), // DISABLED WHEN SCREEN READER IS ATTACHED renderControlCharacters: (accessibilityIsOn ? false : opts.viewInfo.renderControlCharacters), // DISABLED WHEN SCREEN READER IS ATTACHED - fontLigatures: (accessibilityIsOn ? false : opts.viewInfo.fontLigatures), // DISABLED WHEN SCREEN READER IS ATTACHED + fontLigatures: opts.viewInfo.fontLigatures, renderIndentGuides: (accessibilityIsOn ? false : opts.viewInfo.renderIndentGuides), // DISABLED WHEN SCREEN READER IS ATTACHED highlightActiveIndentGuide: opts.viewInfo.highlightActiveIndentGuide, renderLineHighlight: opts.viewInfo.renderLineHighlight, @@ -2636,6 +2645,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { ariaLabel: nls.localize('editorViewAccessibleLabel', "Editor content"), renderLineNumbers: RenderLineNumbersType.On, renderCustomLineNumbers: null, + cursorSurroundingLines: 0, renderFinalNewline: true, selectOnLineNumbers: true, glyphMargin: true, diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 789d3a97b02..1b448239065 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -15,7 +15,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer } from 'vs/editor/common/model'; import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; @@ -86,6 +86,14 @@ export class CursorModelState { class AutoClosedAction { + public static getAllAutoClosedCharacters(autoClosedActions: AutoClosedAction[]): Range[] { + let autoClosedCharacters: Range[] = []; + for (const autoClosedAction of autoClosedActions) { + autoClosedCharacters = autoClosedCharacters.concat(autoClosedAction.getAutoClosedCharactersRanges()); + } + return autoClosedCharacters; + } + private readonly _model: ITextModel; private _autoClosedCharactersDecorations: string[]; @@ -429,6 +437,31 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { // ------ auxiliary handling logic + private _pushAutoClosedAction(autoClosedCharactersRanges: Range[], autoClosedEnclosingRanges: Range[]): void { + let autoClosedCharactersDeltaDecorations: IModelDeltaDecoration[] = []; + let autoClosedEnclosingDeltaDecorations: IModelDeltaDecoration[] = []; + + for (let i = 0, len = autoClosedCharactersRanges.length; i < len; i++) { + autoClosedCharactersDeltaDecorations.push({ + range: autoClosedCharactersRanges[i], + options: { + inlineClassName: 'auto-closed-character', + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges + } + }); + autoClosedEnclosingDeltaDecorations.push({ + range: autoClosedEnclosingRanges[i], + options: { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges + } + }); + } + + const autoClosedCharactersDecorations = this._model.deltaDecorations([], autoClosedCharactersDeltaDecorations); + const autoClosedEnclosingDecorations = this._model.deltaDecorations([], autoClosedEnclosingDeltaDecorations); + this._autoClosedActions.push(new AutoClosedAction(this._model, autoClosedCharactersDecorations, autoClosedEnclosingDecorations)); + } + private _executeEditOperation(opResult: EditOperationResult | null): void { if (!opResult) { @@ -446,32 +479,19 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._interpretCommandResult(result); // Check for auto-closing closed characters - let autoClosedCharactersRanges: IModelDeltaDecoration[] = []; - let autoClosedEnclosingRanges: IModelDeltaDecoration[] = []; + let autoClosedCharactersRanges: Range[] = []; + let autoClosedEnclosingRanges: Range[] = []; for (let i = 0; i < opResult.commands.length; i++) { const command = opResult.commands[i]; if (command instanceof TypeWithAutoClosingCommand && command.enclosingRange && command.closeCharacterRange) { - autoClosedCharactersRanges.push({ - range: command.closeCharacterRange, - options: { - inlineClassName: 'auto-closed-character', - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges - } - }); - autoClosedEnclosingRanges.push({ - range: command.enclosingRange, - options: { - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges - } - }); + autoClosedCharactersRanges.push(command.closeCharacterRange); + autoClosedEnclosingRanges.push(command.enclosingRange); } } if (autoClosedCharactersRanges.length > 0) { - const autoClosedCharactersDecorations = this._model.deltaDecorations([], autoClosedCharactersRanges); - const autoClosedEnclosingDecorations = this._model.deltaDecorations([], autoClosedEnclosingRanges); - this._autoClosedActions.push(new AutoClosedAction(this._model, autoClosedCharactersDecorations, autoClosedEnclosingDecorations)); + this._pushAutoClosedAction(autoClosedCharactersRanges, autoClosedEnclosingRanges); } this._prevEditOperationType = opResult.type; @@ -563,6 +583,76 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { // ----------------------------------------------------------------------------------------------------------- // ----- handlers beyond this point + private _findAutoClosingPairs(edits: IIdentifiedSingleEditOperation[]): [number, number][] | null { + if (!edits.length) { + return null; + } + + let indices: [number, number][] = []; + for (let i = 0, len = edits.length; i < len; i++) { + const edit = edits[i]; + if (!edit.text || edit.text.indexOf('\n') >= 0) { + return null; + } + + const m = edit.text.match(/([)\]}>'"`])([^)\]}>'"`]*)$/); + if (!m) { + return null; + } + const closeChar = m[1]; + + const autoClosingPairsCandidates = this.context.config.autoClosingPairsClose2.get(closeChar); + if (!autoClosingPairsCandidates || autoClosingPairsCandidates.length !== 1) { + return null; + } + + const openChar = autoClosingPairsCandidates[0].open; + const closeCharIndex = edit.text.length - m[2].length - 1; + const openCharIndex = edit.text.lastIndexOf(openChar, closeCharIndex - 1); + if (openCharIndex === -1) { + return null; + } + + indices.push([openCharIndex, closeCharIndex]); + } + + return indices; + } + + public executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): void { + let autoClosingIndices: [number, number][] | null = null; + if (source === 'snippet') { + autoClosingIndices = this._findAutoClosingPairs(edits); + } + + if (autoClosingIndices) { + edits[0]._isTracked = true; + } + let autoClosedCharactersRanges: Range[] = []; + let autoClosedEnclosingRanges: Range[] = []; + const selections = this._model.pushEditOperations(this.getSelections(), edits, (undoEdits) => { + if (autoClosingIndices) { + for (let i = 0, len = autoClosingIndices.length; i < len; i++) { + const [openCharInnerIndex, closeCharInnerIndex] = autoClosingIndices[i]; + const undoEdit = undoEdits[i]; + const lineNumber = undoEdit.range.startLineNumber; + const openCharIndex = undoEdit.range.startColumn - 1 + openCharInnerIndex; + const closeCharIndex = undoEdit.range.startColumn - 1 + closeCharInnerIndex; + + autoClosedCharactersRanges.push(new Range(lineNumber, closeCharIndex + 1, lineNumber, closeCharIndex + 2)); + autoClosedEnclosingRanges.push(new Range(lineNumber, openCharIndex + 1, lineNumber, closeCharIndex + 2)); + } + } + return cursorStateComputer(undoEdits); + }); + if (selections) { + this.setSelections(source, selections); + } + if (autoClosedCharactersRanges.length > 0) { + this._pushAutoClosedAction(autoClosedCharactersRanges, autoClosedEnclosingRanges); + } + } + public trigger(source: string, handlerId: string, payload: any): void { const H = editorCommon.Handler; @@ -657,7 +747,8 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { private _interpretCompositionEnd(source: string) { if (!this._isDoingComposition && source === 'keyboard') { // composition finishes, let's check if we need to auto complete if necessary. - this._executeEditOperation(TypeOperations.compositionEndWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections())); + const autoClosedCharacters = AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions); + this._executeEditOperation(TypeOperations.compositionEndWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters)); } } @@ -675,14 +766,8 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { chr = text.charAt(i); } - let autoClosedCharacters: Range[] = []; - if (this._autoClosedActions.length > 0) { - for (let i = 0, len = this._autoClosedActions.length; i < len; i++) { - autoClosedCharacters = autoClosedCharacters.concat(this._autoClosedActions[i].getAutoClosedCharactersRanges()); - } - } - - // Here we must interpret each typed character individually, that's why we create a new context + // Here we must interpret each typed character individually + const autoClosedCharacters = AutoClosedAction.getAllAutoClosedCharacters(this._autoClosedActions); this._executeEditOperation(TypeOperations.typeWithInterceptors(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), autoClosedCharacters, chr)); } diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index 844387516d6..f459e61735f 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -15,7 +15,7 @@ import { ICommand, IConfiguration, ScrollType } from 'vs/editor/common/editorCom import { ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { LanguageIdentifier } from 'vs/editor/common/modes'; -import { IAutoClosingPair } from 'vs/editor/common/modes/languageConfiguration'; +import { IAutoClosingPair, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { VerticalRevealType } from 'vs/editor/common/view/viewEvents'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; @@ -67,11 +67,22 @@ export interface ICursors { export interface CharacterMap { [char: string]: string; } +export interface MultipleCharacterMap { + [char: string]: string[]; +} const autoCloseAlways = () => true; const autoCloseNever = () => false; const autoCloseBeforeWhitespace = (chr: string) => (chr === ' ' || chr === '\t'); +function appendEntry(target: Map, key: K, value: V): void { + if (target.has(key)) { + target.get(key)!.push(value); + } else { + target.set(key, [value]); + } +} + export class CursorConfiguration { _cursorMoveConfigurationBrand: void; @@ -90,8 +101,8 @@ export class CursorConfiguration { public readonly autoClosingQuotes: EditorAutoClosingStrategy; public readonly autoSurround: EditorAutoSurroundStrategy; public readonly autoIndent: boolean; - public readonly autoClosingPairsOpen: CharacterMap; - public readonly autoClosingPairsClose: CharacterMap; + public readonly autoClosingPairsOpen2: Map; + public readonly autoClosingPairsClose2: Map; public readonly surroundingPairs: CharacterMap; public readonly shouldAutoCloseBefore: { quote: (ch: string) => boolean, bracket: (ch: string) => boolean }; @@ -138,8 +149,8 @@ export class CursorConfiguration { this.autoSurround = c.autoSurround; this.autoIndent = c.autoIndent; - this.autoClosingPairsOpen = {}; - this.autoClosingPairsClose = {}; + this.autoClosingPairsOpen2 = new Map(); + this.autoClosingPairsClose2 = new Map(); this.surroundingPairs = {}; this._electricChars = null; @@ -151,8 +162,10 @@ export class CursorConfiguration { let autoClosingPairs = CursorConfiguration._getAutoClosingPairs(languageIdentifier); if (autoClosingPairs) { for (const pair of autoClosingPairs) { - this.autoClosingPairsOpen[pair.open] = pair.close; - this.autoClosingPairsClose[pair.close] = pair.open; + appendEntry(this.autoClosingPairsOpen2, pair.open.charAt(pair.open.length - 1), pair); + if (pair.close.length === 1) { + appendEntry(this.autoClosingPairsClose2, pair.close, pair); + } } } @@ -190,7 +203,7 @@ export class CursorConfiguration { } } - private static _getAutoClosingPairs(languageIdentifier: LanguageIdentifier): IAutoClosingPair[] | null { + private static _getAutoClosingPairs(languageIdentifier: LanguageIdentifier): StandardAutoClosingPairConditional[] | null { try { return LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id); } catch (e) { diff --git a/src/vs/editor/common/controller/cursorDeleteOperations.ts b/src/vs/editor/common/controller/cursorDeleteOperations.ts index 5a7830e26b3..3f5e80a3ee9 100644 --- a/src/vs/editor/common/controller/cursorDeleteOperations.ts +++ b/src/vs/editor/common/controller/cursorDeleteOperations.ts @@ -63,7 +63,8 @@ export class DeleteOperations { const lineText = model.getLineContent(position.lineNumber); const character = lineText[position.column - 2]; - if (!config.autoClosingPairsOpen.hasOwnProperty(character)) { + const autoClosingPairCandidates = config.autoClosingPairsOpen2.get(character); + if (!autoClosingPairCandidates) { return false; } @@ -78,9 +79,14 @@ export class DeleteOperations { } const afterCharacter = lineText[position.column - 1]; - const closeCharacter = config.autoClosingPairsOpen[character]; - if (afterCharacter !== closeCharacter) { + let foundAutoClosingPair = false; + for (const autoClosingPairCandidate of autoClosingPairCandidates) { + if (autoClosingPairCandidate.open === character && autoClosingPairCandidate.close === afterCharacter) { + foundAutoClosingPair = true; + } + } + if (!foundAutoClosingPair) { return false; } } diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 73d984cfb62..aa6b5747420 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -13,9 +13,10 @@ import { CursorColumns, CursorConfiguration, EditOperationResult, EditOperationT import { WordCharacterClass, getMapForWordSeparators } from 'vs/editor/common/controller/wordCharacterClassifier'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; +import { Position } from 'vs/editor/common/core/position'; import { ICommand, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { EnterAction, IndentAction } from 'vs/editor/common/modes/languageConfiguration'; +import { EnterAction, IndentAction, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter'; @@ -433,11 +434,13 @@ export class TypeOperations { private static _isAutoClosingCloseCharType(config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): boolean { const autoCloseConfig = isQuote(ch) ? config.autoClosingQuotes : config.autoClosingBrackets; - if (autoCloseConfig === 'never' || !config.autoClosingPairsClose.hasOwnProperty(ch)) { + if (autoCloseConfig === 'never') { return false; } - const isEqualPair = (ch === config.autoClosingPairsClose[ch]); + if (!config.autoClosingPairsClose2.has(ch)) { + return false; + } for (let i = 0, len = selections.length; i < len; i++) { const selection = selections[i]; @@ -454,14 +457,6 @@ export class TypeOperations { return false; } - if (isEqualPair) { - const lineTextBeforeCursor = lineText.substr(0, position.column - 1); - const chCntBefore = this._countNeedlesInHaystack(lineTextBeforeCursor, ch); - if (chCntBefore % 2 === 0) { - return false; - } - } - // Must over-type a closing character typed by the editor let found = false; for (let j = 0, lenJ = autoClosedCharacters.length; j < lenJ; j++) { @@ -479,15 +474,6 @@ export class TypeOperations { return true; } - private static _countNeedlesInHaystack(haystack: string, needle: string): number { - let cnt = 0; - let lastIndex = -1; - while ((lastIndex = haystack.indexOf(needle, lastIndex + 1)) !== -1) { - cnt++; - } - return cnt; - } - private static _runAutoClosingCloseCharType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): EditOperationResult { let commands: ICommand[] = []; for (let i = 0, len = selections.length; i < len; i++) { @@ -502,65 +488,98 @@ export class TypeOperations { }); } - private static _isBeforeClosingBrace(config: CursorConfiguration, ch: string, characterAfter: string) { - const thisBraceIsSymmetric = (config.autoClosingPairsOpen[ch] === ch); - let isBeforeCloseBrace = false; - for (let otherCloseBrace in config.autoClosingPairsClose) { - const otherBraceIsSymmetric = (config.autoClosingPairsOpen[otherCloseBrace] === otherCloseBrace); - if (!thisBraceIsSymmetric && otherBraceIsSymmetric) { - continue; - } - if (characterAfter === otherCloseBrace) { - isBeforeCloseBrace = true; - break; - } - } - - return isBeforeCloseBrace; - } - - private static _isAutoClosingOpenCharType(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): boolean { - const chIsQuote = isQuote(ch); - const autoCloseConfig = chIsQuote ? config.autoClosingQuotes : config.autoClosingBrackets; - - if (autoCloseConfig === 'never' || !config.autoClosingPairsOpen.hasOwnProperty(ch)) { + private static _isBeforeClosingBrace(config: CursorConfiguration, autoClosingPair: StandardAutoClosingPairConditional, characterAfter: string) { + const otherAutoClosingPairs = config.autoClosingPairsClose2.get(characterAfter); + if (!otherAutoClosingPairs) { return false; } - let shouldAutoCloseBefore = chIsQuote ? config.shouldAutoCloseBefore.quote : config.shouldAutoCloseBefore.bracket; + const thisBraceIsSymmetric = (autoClosingPair.open === autoClosingPair.close); + for (const otherAutoClosingPair of otherAutoClosingPairs) { + const otherBraceIsSymmetric = (otherAutoClosingPair.open === otherAutoClosingPair.close); + if (!thisBraceIsSymmetric && otherBraceIsSymmetric) { + continue; + } + return true; + } + + return false; + } + + private static _findAutoClosingPairOpen(config: CursorConfiguration, model: ITextModel, positions: Position[], ch: string): StandardAutoClosingPairConditional | null { + const autoClosingPairCandidates = config.autoClosingPairsOpen2.get(ch); + if (!autoClosingPairCandidates) { + return null; + } + + // Determine which auto-closing pair it is + let autoClosingPair: StandardAutoClosingPairConditional | null = null; + for (const autoClosingPairCandidate of autoClosingPairCandidates) { + if (autoClosingPair === null || autoClosingPairCandidate.open.length > autoClosingPair.open.length) { + let candidateIsMatch = true; + for (const position of positions) { + const relevantText = model.getValueInRange(new Range(position.lineNumber, position.column - autoClosingPairCandidate.open.length + 1, position.lineNumber, position.column)); + if (relevantText + ch !== autoClosingPairCandidate.open) { + candidateIsMatch = false; + break; + } + } + + if (candidateIsMatch) { + autoClosingPair = autoClosingPairCandidate; + } + } + } + return autoClosingPair; + } + + private static _isAutoClosingOpenCharType(config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean): StandardAutoClosingPairConditional | null { + const chIsQuote = isQuote(ch); + const autoCloseConfig = chIsQuote ? config.autoClosingQuotes : config.autoClosingBrackets; + if (autoCloseConfig === 'never') { + return null; + } + + const autoClosingPair = this._findAutoClosingPairOpen(config, model, selections.map(s => s.getPosition()), ch); + if (!autoClosingPair) { + return null; + } + + const shouldAutoCloseBefore = chIsQuote ? config.shouldAutoCloseBefore.quote : config.shouldAutoCloseBefore.bracket; for (let i = 0, len = selections.length; i < len; i++) { const selection = selections[i]; if (!selection.isEmpty()) { - return false; + return null; } const position = selection.getPosition(); const lineText = model.getLineContent(position.lineNumber); - // Do not auto-close ' or " after a word character - if ((chIsQuote && position.column > 1) && autoCloseConfig !== 'always') { - const wordSeparators = getMapForWordSeparators(config.wordSeparators); - const characterBeforeCode = lineText.charCodeAt(position.column - 2); - const characterBeforeType = wordSeparators.get(characterBeforeCode); - if (characterBeforeType === WordCharacterClass.Regular) { - return false; - } - } - // Only consider auto closing the pair if a space follows or if another autoclosed pair follows - const characterAfter = lineText.charAt(position.column - 1); - if (characterAfter) { - let isBeforeCloseBrace = TypeOperations._isBeforeClosingBrace(config, ch, characterAfter); + if (lineText.length > position.column - 1) { + const characterAfter = lineText.charAt(position.column - 1); + const isBeforeCloseBrace = TypeOperations._isBeforeClosingBrace(config, autoClosingPair, characterAfter); if (!isBeforeCloseBrace && !shouldAutoCloseBefore(characterAfter)) { - return false; + return null; } } if (!model.isCheapToTokenize(position.lineNumber)) { // Do not force tokenization - return false; + return null; + } + + // Do not auto-close ' or " after a word character + if (autoClosingPair.open.length === 1 && chIsQuote && autoCloseConfig !== 'always') { + const wordSeparators = getMapForWordSeparators(config.wordSeparators); + if (insertOpenCharacter && position.column > 1 && wordSeparators.get(lineText.charCodeAt(position.column - 2)) === WordCharacterClass.Regular) { + return null; + } + if (!insertOpenCharacter && position.column > 2 && wordSeparators.get(lineText.charCodeAt(position.column - 3)) === WordCharacterClass.Regular) { + return null; + } } model.forceTokenization(position.lineNumber); @@ -568,25 +587,24 @@ export class TypeOperations { let shouldAutoClosePair = false; try { - shouldAutoClosePair = LanguageConfigurationRegistry.shouldAutoClosePair(ch, lineTokens, position.column); + shouldAutoClosePair = LanguageConfigurationRegistry.shouldAutoClosePair(autoClosingPair, lineTokens, insertOpenCharacter ? position.column : position.column - 1); } catch (e) { onUnexpectedError(e); } if (!shouldAutoClosePair) { - return false; + return null; } } - return true; + return autoClosingPair; } - private static _runAutoClosingOpenCharType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string): EditOperationResult { + private static _runAutoClosingOpenCharType(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], ch: string, insertOpenCharacter: boolean, autoClosingPair: StandardAutoClosingPairConditional): EditOperationResult { let commands: ICommand[] = []; for (let i = 0, len = selections.length; i < len; i++) { const selection = selections[i]; - const closeCharacter = config.autoClosingPairsOpen[ch]; - commands[i] = new TypeWithAutoClosingCommand(selection, ch, closeCharacter); + commands[i] = new TypeWithAutoClosingCommand(selection, ch, insertOpenCharacter, autoClosingPair.close); } return new EditOperationResult(EditOperationType.Typing, commands, { shouldPushStackElementBefore: true, @@ -689,14 +707,6 @@ export class TypeOperations { return null; } - if (electricAction.appendText) { - const command = new ReplaceCommandWithOffsetCursorState(selection, ch + electricAction.appendText, 0, -electricAction.appendText.length); - return new EditOperationResult(EditOperationType.Typing, [command], { - shouldPushStackElementBefore: false, - shouldPushStackElementAfter: true - }); - } - if (electricAction.matchOpenBracket) { let endColumn = (lineTokens.getLineContent() + ch).lastIndexOf(electricAction.matchOpenBracket) + 1; let match = model.findMatchingBracketUp(electricAction.matchOpenBracket, { @@ -732,87 +742,44 @@ export class TypeOperations { return null; } - public static compositionEndWithInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[]): EditOperationResult | null { - if (config.autoClosingQuotes === 'never') { + /** + * This is very similar with typing, but the character is already in the text buffer! + */ + public static compositionEndWithInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[]): EditOperationResult | null { + let ch: string | null = null; + // extract last typed character + for (const selection of selections) { + if (!selection.isEmpty()) { + return null; + } + const position = selection.getPosition(); + const currentChar = model.getValueInRange(new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column)); + if (ch === null) { + ch = currentChar; + } else if (ch !== currentChar) { + return null; + } + } + + if (!ch) { return null; } - let commands: ICommand[] = []; - - for (let i = 0; i < selections.length; i++) { - if (!selections[i].isEmpty()) { - continue; - } - const position = selections[i].getPosition(); - const lineText = model.getLineContent(position.lineNumber); - const ch = lineText.charAt(position.column - 2); - - if (config.autoClosingPairsClose.hasOwnProperty(ch)) { // first of all, it's a closing tag - if (ch === config.autoClosingPairsClose[ch] /** isEqualPair */) { - const lineTextBeforeCursor = lineText.substr(0, position.column - 2); - const chCntBefore = this._countNeedlesInHaystack(lineTextBeforeCursor, ch); - - if (chCntBefore % 2 === 1) { - continue; // it pairs with the opening tag. - } - } - } - - // As we are not typing in a new character, so we don't need to run `_runAutoClosingCloseCharType` - // Next step, let's try to check if it's an open char. - if (config.autoClosingPairsOpen.hasOwnProperty(ch)) { - if (isQuote(ch) && position.column > 2) { - const wordSeparators = getMapForWordSeparators(config.wordSeparators); - const characterBeforeCode = lineText.charCodeAt(position.column - 3); - const characterBeforeType = wordSeparators.get(characterBeforeCode); - if (characterBeforeType === WordCharacterClass.Regular) { - continue; - } - } - - const characterAfter = lineText.charAt(position.column - 1); - - if (characterAfter) { - let isBeforeCloseBrace = TypeOperations._isBeforeClosingBrace(config, ch, characterAfter); - let shouldAutoCloseBefore = isQuote(ch) ? config.shouldAutoCloseBefore.quote : config.shouldAutoCloseBefore.bracket; - if (isBeforeCloseBrace) { - // In normal auto closing logic, we will auto close if the cursor is even before a closing brace intentionally. - // However for composition mode, we do nothing here as users might clear all the characters for composition and we don't want to do a unnecessary auto close. - // Related: microsoft/vscode#57250. - continue; - } - if (!shouldAutoCloseBefore(characterAfter)) { - continue; - } - } - - if (!model.isCheapToTokenize(position.lineNumber)) { - // Do not force tokenization - continue; - } - - model.forceTokenization(position.lineNumber); - const lineTokens = model.getLineTokens(position.lineNumber); - - let shouldAutoClosePair = false; - - try { - shouldAutoClosePair = LanguageConfigurationRegistry.shouldAutoClosePair(ch, lineTokens, position.column - 1); - } catch (e) { - onUnexpectedError(e); - } - - if (shouldAutoClosePair) { - const closeCharacter = config.autoClosingPairsOpen[ch]; - commands[i] = new ReplaceCommandWithOffsetCursorState(selections[i], closeCharacter, 0, -closeCharacter.length); - } - } + if (this._isAutoClosingCloseCharType(config, model, selections, autoClosedCharacters, ch)) { + // Unfortunately, the close character is at this point "doubled", so we need to delete it... + const commands = selections.map(s => new ReplaceCommand(new Range(s.positionLineNumber, s.positionColumn, s.positionLineNumber, s.positionColumn + 1), '', false)); + return new EditOperationResult(EditOperationType.Typing, commands, { + shouldPushStackElementBefore: true, + shouldPushStackElementAfter: false + }); } - return new EditOperationResult(EditOperationType.Typing, commands, { - shouldPushStackElementBefore: true, - shouldPushStackElementAfter: false - }); + const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, false); + if (autoClosingPairOpenCharType) { + return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, false, autoClosingPairOpenCharType); + } + + return null; } public static typeWithInterceptors(prevEditOperationType: EditOperationType, config: CursorConfiguration, model: ITextModel, selections: Selection[], autoClosedCharacters: Range[], ch: string): EditOperationResult { @@ -850,8 +817,9 @@ export class TypeOperations { return this._runAutoClosingCloseCharType(prevEditOperationType, config, model, selections, ch); } - if (this._isAutoClosingOpenCharType(config, model, selections, ch)) { - return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch); + const autoClosingPairOpenCharType = this._isAutoClosingOpenCharType(config, model, selections, ch, true); + if (autoClosingPairOpenCharType) { + return this._runAutoClosingOpenCharType(prevEditOperationType, config, model, selections, ch, true, autoClosingPairOpenCharType); } if (this._isSurroundSelectionType(config, model, selections, ch)) { @@ -939,21 +907,24 @@ export class TypeOperations { export class TypeWithAutoClosingCommand extends ReplaceCommandWithOffsetCursorState { - private _closeCharacter: string; + private readonly _openCharacter: string; + private readonly _closeCharacter: string; public closeCharacterRange: Range | null; public enclosingRange: Range | null; - constructor(selection: Selection, openCharacter: string, closeCharacter: string) { - super(selection, openCharacter + closeCharacter, 0, -closeCharacter.length); + constructor(selection: Selection, openCharacter: string, insertOpenCharacter: boolean, closeCharacter: string) { + super(selection, (insertOpenCharacter ? openCharacter : '') + closeCharacter, 0, -closeCharacter.length); + this._openCharacter = openCharacter; this._closeCharacter = closeCharacter; this.closeCharacterRange = null; + this.enclosingRange = null; } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { let inverseEditOperations = helper.getInverseEditOperations(); let range = inverseEditOperations[0].range; this.closeCharacterRange = new Range(range.startLineNumber, range.endColumn - this._closeCharacter.length, range.endLineNumber, range.endColumn); - this.enclosingRange = range; + this.enclosingRange = new Range(range.startLineNumber, range.endColumn - this._openCharacter.length - this._closeCharacter.length, range.endLineNumber, range.endColumn); return super.computeCursorState(model, helper); } } diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts index e19068883ef..2bb09583133 100644 --- a/src/vs/editor/common/controller/oneCursor.ts +++ b/src/vs/editor/common/controller/oneCursor.ts @@ -11,8 +11,8 @@ import { TrackedRangeStickiness } from 'vs/editor/common/model'; export class OneCursor { - public modelState: SingleCursorState; - public viewState: SingleCursorState; + public modelState!: SingleCursorState; + public viewState!: SingleCursorState; private _selTrackedRange: string | null; private _trackSelection: boolean; diff --git a/src/vs/editor/common/diff/diffComputer.ts b/src/vs/editor/common/diff/diffComputer.ts index be2d727328a..67ccd7e5c4e 100644 --- a/src/vs/editor/common/diff/diffComputer.ts +++ b/src/vs/editor/common/diff/diffComputer.ts @@ -328,6 +328,8 @@ export class DiffComputer { this.modifiedLines = modifiedLines; this.original = new LineMarkerSequence(originalLines); this.modified = new LineMarkerSequence(modifiedLines); + + this.computationStartTime = (new Date()).getTime(); } public computeDiff(): ILineChange[] { diff --git a/src/vs/editor/common/model/indentationGuesser.ts b/src/vs/editor/common/model/indentationGuesser.ts index 24c449c60c4..b1790f97bdb 100644 --- a/src/vs/editor/common/model/indentationGuesser.ts +++ b/src/vs/editor/common/model/indentationGuesser.ts @@ -7,8 +7,8 @@ import { CharCode } from 'vs/base/common/charCode'; import { ITextBuffer } from 'vs/editor/common/model'; class SpacesDiffResult { - public spacesDiff: number; - public looksLikeAlignment: boolean; + public spacesDiff: number = 0; + public looksLikeAlignment: boolean = false; } /** @@ -158,8 +158,19 @@ export function guessIndentation(source: ITextBuffer, defaultTabSize: number, de spacesDiff(previousLineText, previousLineIndentation, currentLineText, currentLineIndentation, tmp); if (tmp.looksLikeAlignment) { - // skip this line entirely - continue; + // if defaultInsertSpaces === true && the spaces count == tabSize, we may want to count it as valid indentation + // + // - item1 + // - item2 + // + // otherwise skip this line entirely + // + // const a = 1, + // b = 2; + + if (!(defaultInsertSpaces && defaultTabSize === tmp.spacesDiff)) { + continue; + } } let currentSpacesDiff = tmp.spacesDiff; diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index 445ddde972f..9e38117e9e7 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -265,16 +265,16 @@ class PieceTreeSearchCache { } export class PieceTreeBase { - root: TreeNode; - protected _buffers: StringBuffer[]; // 0 is change buffer, others are readonly original buffer. - protected _lineCnt: number; - protected _length: number; - protected _EOL: string; - protected _EOLLength: number; - protected _EOLNormalized: boolean; - private _lastChangeBufferPos: BufferCursor; - private _searchCache: PieceTreeSearchCache; - private _lastVisitedLine: { lineNumber: number; value: string; }; + root!: TreeNode; + protected _buffers!: StringBuffer[]; // 0 is change buffer, others are readonly original buffer. + protected _lineCnt!: number; + protected _length!: number; + protected _EOL!: string; + protected _EOLLength!: number; + protected _EOLNormalized!: boolean; + private _lastChangeBufferPos!: BufferCursor; + private _searchCache!: PieceTreeSearchCache; + private _lastVisitedLine!: { lineNumber: number; value: string; }; constructor(chunks: StringBuffer[], eol: '\r\n' | '\n', eolNormalized: boolean) { this.create(chunks, eol, eolNormalized); diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index e73183b0bcc..0469471f8c4 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -110,21 +110,6 @@ export function createTextBuffer(value: string | model.ITextBufferFactory, defau let MODEL_ID = 0; -/** - * Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc. - */ -function singleLetter(result: number): string { - const LETTERS_CNT = (CharCode.Z - CharCode.A + 1); - - result = result % (2 * LETTERS_CNT); - - if (result < LETTERS_CNT) { - return String.fromCharCode(CharCode.a + result); - } - - return String.fromCharCode(CharCode.A + result - LETTERS_CNT); -} - const LIMIT_FIND_COUNT = 999; export const LONG_LINE_BOUNDARY = 10000; @@ -329,7 +314,9 @@ export class TextModel extends Disposable implements model.ITextModel { this._isTooLargeForSyncing = (bufferTextLength > TextModel.MODEL_SYNC_LIMIT); - this._setVersionId(1); + this._versionId = 1; + this._alternativeVersionId = 1; + this._isDisposed = false; this._isDisposing = false; @@ -341,7 +328,7 @@ export class TextModel extends Disposable implements model.ITextModel { } }); - this._instanceId = singleLetter(MODEL_ID); + this._instanceId = strings.singleLetterHash(MODEL_ID); this._lastDecorationId = 0; this._decorations = Object.create(null); this._decorationsTree = new DecorationsTrees(); @@ -693,11 +680,7 @@ export class TextModel extends Disposable implements model.ITextModel { } private _increaseVersionId(): void { - this._setVersionId(this._versionId + 1); - } - - private _setVersionId(newVersionId: number): void { - this._versionId = newVersionId; + this._versionId = this._versionId + 1; this._alternativeVersionId = this._versionId; } diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index 076b142db79..03e1c4bd6b7 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -28,7 +28,10 @@ export class TokenizationStateStore { private _invalidLineStartIndex: number; constructor() { - this._reset(null); + this._beginState = []; + this._valid = []; + this._len = 0; + this._invalidLineStartIndex = 0; } private _reset(initialState: IState | null): void { diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index dbeb3fb1956..f0b9934fcff 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1292,7 +1292,7 @@ export interface CommentThread { threadId: string; resource: string | null; range: IRange; - label: string; + label: string | undefined; contextValue: string | undefined; comments: Comment[] | undefined; onDidChangeComments: Event; @@ -1300,7 +1300,7 @@ export interface CommentThread { input?: CommentInput; onDidChangeInput: Event; onDidChangeRange: Event; - onDidChangeLabel: Event; + onDidChangeLabel: Event; onDidChangeCollasibleState: Event; isDisposed: boolean; } diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index 4bca048eec1..ed8d2fdb4ae 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -78,7 +78,9 @@ export interface LanguageConfiguration { * * @deprecated Will be replaced by a better API soon. */ - __electricCharacterSupport?: IBracketElectricCharacterContribution; + __electricCharacterSupport?: { + docComment?: IDocComment; + }; } /** @@ -155,10 +157,6 @@ export interface OnEnterRule { action: EnterAction; } -export interface IBracketElectricCharacterContribution { - docComment?: IDocComment; -} - /** * Definition of documentation comments (e.g. Javadoc/JSdoc) */ diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index 713df07ca0d..cee0469e30e 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -12,7 +12,7 @@ import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper'; import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; -import { EnterAction, FoldingRules, IAutoClosingPair, IAutoClosingPairConditional, IndentAction, IndentationRule, LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; +import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; import { createScopedLineTokens } from 'vs/editor/common/modes/supports'; import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair'; import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter'; @@ -53,7 +53,7 @@ export class RichEditSupport { public readonly characterPair: CharacterPairSupport; public readonly wordDefinition: RegExp; public readonly onEnter: OnEnterSupport | null; - public readonly indentRulesSupport: IndentRulesSupport; + public readonly indentRulesSupport: IndentRulesSupport | null; public readonly indentationRules: IndentationRule | undefined; public readonly foldingRules: FoldingRules; @@ -81,6 +81,8 @@ export class RichEditSupport { this.indentationRules = this._conf.indentationRules; if (this._conf.indentationRules) { this.indentRulesSupport = new IndentRulesSupport(this._conf.indentationRules); + } else { + this.indentRulesSupport = null; } this.foldingRules = this._conf.folding || {}; @@ -95,16 +97,7 @@ export class RichEditSupport { public get electricCharacter(): BracketElectricCharacterSupport | null { if (!this._electricCharacter) { - let autoClosingPairs: IAutoClosingPairConditional[] = []; - if (this._conf.autoClosingPairs) { - autoClosingPairs = this._conf.autoClosingPairs; - } else if (this._conf.brackets) { - autoClosingPairs = this._conf.brackets.map(b => { - return { open: b[0], close: b[1] }; - }); - } - - this._electricCharacter = new BracketElectricCharacterSupport(this.brackets, autoClosingPairs, this._conf.__electricCharacterSupport); + this._electricCharacter = new BracketElectricCharacterSupport(this.brackets); } return this._electricCharacter; } @@ -170,7 +163,9 @@ export class RichEditSupport { } export class LanguageConfigurationChangeEvent { - languageIdentifier: LanguageIdentifier; + constructor( + public readonly languageIdentifier: LanguageIdentifier + ) { } } export class LanguageConfigurationRegistryImpl { @@ -184,11 +179,11 @@ export class LanguageConfigurationRegistryImpl { let previous = this._getRichEditSupport(languageIdentifier.id); let current = new RichEditSupport(languageIdentifier, previous, configuration); this._entries.set(languageIdentifier.id, current); - this._onDidChange.fire({ languageIdentifier }); + this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageIdentifier)); return toDisposable(() => { if (this._entries.get(languageIdentifier.id) === current) { this._entries.set(languageIdentifier.id, previous); - this._onDidChange.fire({ languageIdentifier }); + this._onDidChange.fire(new LanguageConfigurationChangeEvent(languageIdentifier)); } }); } @@ -257,7 +252,7 @@ export class LanguageConfigurationRegistryImpl { return value.characterPair || null; } - public getAutoClosingPairs(languageId: LanguageId): IAutoClosingPair[] { + public getAutoClosingPairs(languageId: LanguageId): StandardAutoClosingPairConditional[] { let characterPairSupport = this._getCharacterPairSupport(languageId); if (!characterPairSupport) { return []; @@ -281,13 +276,9 @@ export class LanguageConfigurationRegistryImpl { return characterPairSupport.getSurroundingPairs(); } - public shouldAutoClosePair(character: string, context: LineTokens, column: number): boolean { - let scopedLineTokens = createScopedLineTokens(context, column - 1); - let characterPairSupport = this._getCharacterPairSupport(scopedLineTokens.languageId); - if (!characterPairSupport) { - return false; - } - return characterPairSupport.shouldAutoClosePair(character, scopedLineTokens, column - scopedLineTokens.firstCharOffset); + public shouldAutoClosePair(autoClosingPair: StandardAutoClosingPairConditional, context: LineTokens, column: number): boolean { + const scopedLineTokens = createScopedLineTokens(context, column - 1); + return CharacterPairSupport.shouldAutoClosePair(autoClosingPair, scopedLineTokens, column - scopedLineTokens.firstCharOffset); } // end characterPair diff --git a/src/vs/editor/common/modes/supports/characterPair.ts b/src/vs/editor/common/modes/supports/characterPair.ts index bb8ecd4980e..b7cfa2ecdd7 100644 --- a/src/vs/editor/common/modes/supports/characterPair.ts +++ b/src/vs/editor/common/modes/supports/characterPair.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CharacterPair, IAutoClosingPair, IAutoClosingPairConditional, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; +import { IAutoClosingPair, StandardAutoClosingPairConditional, LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; import { ScopedLineTokens } from 'vs/editor/common/modes/supports'; export class CharacterPairSupport { @@ -15,7 +15,7 @@ export class CharacterPairSupport { private readonly _surroundingPairs: IAutoClosingPair[]; private readonly _autoCloseBefore: string; - constructor(config: { brackets?: CharacterPair[]; autoClosingPairs?: IAutoClosingPairConditional[], surroundingPairs?: IAutoClosingPair[], autoCloseBefore?: string }) { + constructor(config: LanguageConfiguration) { if (config.autoClosingPairs) { this._autoClosingPairs = config.autoClosingPairs.map(el => new StandardAutoClosingPairConditional(el)); } else if (config.brackets) { @@ -24,12 +24,18 @@ export class CharacterPairSupport { this._autoClosingPairs = []; } + if (config.__electricCharacterSupport && config.__electricCharacterSupport.docComment) { + const docComment = config.__electricCharacterSupport.docComment; + // IDocComment is legacy, only partially supported + this._autoClosingPairs.push(new StandardAutoClosingPairConditional({ open: docComment.open, close: docComment.close || '' })); + } + this._autoCloseBefore = typeof config.autoCloseBefore === 'string' ? config.autoCloseBefore : CharacterPairSupport.DEFAULT_AUTOCLOSE_BEFORE_LANGUAGE_DEFINED; this._surroundingPairs = config.surroundingPairs || this._autoClosingPairs; } - public getAutoClosingPairs(): IAutoClosingPair[] { + public getAutoClosingPairs(): StandardAutoClosingPairConditional[] { return this._autoClosingPairs; } @@ -37,22 +43,15 @@ export class CharacterPairSupport { return this._autoCloseBefore; } - public shouldAutoClosePair(character: string, context: ScopedLineTokens, column: number): boolean { + public static shouldAutoClosePair(autoClosingPair: StandardAutoClosingPairConditional, context: ScopedLineTokens, column: number): boolean { // Always complete on empty line if (context.getTokenCount() === 0) { return true; } - let tokenIndex = context.findTokenIndexAtOffset(column - 2); - let standardTokenType = context.getStandardTokenType(tokenIndex); - - for (const autoClosingPair of this._autoClosingPairs) { - if (autoClosingPair.open === character) { - return autoClosingPair.isOK(standardTokenType); - } - } - - return false; + const tokenIndex = context.findTokenIndexAtOffset(column - 2); + const standardTokenType = context.getStandardTokenType(tokenIndex); + return autoClosingPair.isOK(standardTokenType); } public getSurroundingPairs(): IAutoClosingPair[] { diff --git a/src/vs/editor/common/modes/supports/electricCharacter.ts b/src/vs/editor/common/modes/supports/electricCharacter.ts index 878ef492e12..387994c28a4 100644 --- a/src/vs/editor/common/modes/supports/electricCharacter.ts +++ b/src/vs/editor/common/modes/supports/electricCharacter.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAutoClosingPairConditional, IBracketElectricCharacterContribution, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; import { ScopedLineTokens, ignoreBracketsInToken } from 'vs/editor/common/modes/supports'; import { BracketsUtils, RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets'; @@ -12,29 +11,17 @@ import { BracketsUtils, RichEditBrackets } from 'vs/editor/common/modes/supports * @internal */ export interface IElectricAction { - // Only one of the following properties should be defined: - // The line will be indented at the same level of the line // which contains the matching given bracket type. - matchOpenBracket?: string; - - // The text will be appended after the electric character. - appendText?: string; + matchOpenBracket: string; } export class BracketElectricCharacterSupport { private readonly _richEditBrackets: RichEditBrackets | null; - private readonly _complexAutoClosePairs: StandardAutoClosingPairConditional[]; - constructor(richEditBrackets: RichEditBrackets | null, autoClosePairs: IAutoClosingPairConditional[], contribution: IBracketElectricCharacterContribution | null | undefined) { - contribution = contribution || {}; + constructor(richEditBrackets: RichEditBrackets | null) { this._richEditBrackets = richEditBrackets; - this._complexAutoClosePairs = autoClosePairs.filter(pair => pair.open.length > 1 && !!pair.close).map(el => new StandardAutoClosingPairConditional(el)); - if (contribution.docComment) { - // IDocComment is legacy, only partially supported - this._complexAutoClosePairs.push(new StandardAutoClosingPairConditional({ open: contribution.docComment.open, close: contribution.docComment.close || '' })); - } } public getElectricCharacters(): string[] { @@ -48,11 +35,6 @@ export class BracketElectricCharacterSupport { } } - // auto close - for (let pair of this._complexAutoClosePairs) { - result.push(pair.open.charAt(pair.open.length - 1)); - } - // Filter duplicate entries result = result.filter((item, pos, array) => { return array.indexOf(item) === pos; @@ -62,12 +44,6 @@ export class BracketElectricCharacterSupport { } public onElectricCharacter(character: string, context: ScopedLineTokens, column: number): IElectricAction | null { - return (this._onElectricAutoClose(character, context, column) || - this._onElectricAutoIndent(character, context, column)); - } - - private _onElectricAutoIndent(character: string, context: ScopedLineTokens, column: number): IElectricAction | null { - if (!this._richEditBrackets || this._richEditBrackets.brackets.length === 0) { return null; } @@ -103,44 +79,4 @@ export class BracketElectricCharacterSupport { matchOpenBracket: bracketText }; } - - private _onElectricAutoClose(character: string, context: ScopedLineTokens, column: number): IElectricAction | null { - if (!this._complexAutoClosePairs.length) { - return null; - } - - let line = context.getLineContent(); - - for (let i = 0, len = this._complexAutoClosePairs.length; i < len; i++) { - let pair = this._complexAutoClosePairs[i]; - - // See if the right electric character was pressed - if (character !== pair.open.charAt(pair.open.length - 1)) { - continue; - } - - // check if the full open bracket matches - let start = column - pair.open.length + 1; - let actual = line.substring(start - 1, column - 1) + character; - if (actual !== pair.open) { - continue; - } - - let lastTokenIndex = context.findTokenIndexAtOffset(column - 1); - let lastTokenStandardType = context.getStandardTokenType(lastTokenIndex); - // If we're in a scope listed in 'notIn', do nothing - if (!pair.isOK(lastTokenStandardType)) { - continue; - } - - // If this line already contains the closing tag, do nothing. - if (line.indexOf(pair.close, column - 1) >= 0) { - continue; - } - - return { appendText: pair.close }; - } - - return null; - } } diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index 80212b74264..4b1afa955ea 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -77,6 +77,10 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens partContent += '​'; break; + case CharCode.Space: + partContent += ' '; + break; + default: partContent += String.fromCharCode(charCode); } diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index 1d46cc3a263..74ff7b7fbfa 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -167,6 +167,7 @@ class WorkerManager extends Disposable { super(); this._modelService = modelService; this._editorWorkerClient = null; + this._lastWorkerUsedTime = (new Date()).getTime(); let stopWorkerInterval = this._register(new IntervalTimer()); stopWorkerInterval.cancelAndSet(() => this._checkStopIdleWorker(), Math.round(STOP_WORKER_DELTA_TIME_MS / 2)); diff --git a/src/vs/editor/common/view/minimapCharRenderer.ts b/src/vs/editor/common/view/minimapCharRenderer.ts index adae1644741..17c835d8c2e 100644 --- a/src/vs/editor/common/view/minimapCharRenderer.ts +++ b/src/vs/editor/common/view/minimapCharRenderer.ts @@ -16,8 +16,8 @@ export class MinimapTokensColorTracker { return this._INSTANCE; } - private _colors: RGBA8[]; - private _backgroundIsLight: boolean; + private _colors!: RGBA8[]; + private _backgroundIsLight!: boolean; private _onDidChange = new Emitter(); public readonly onDidChange: Event = this._onDidChange.event; diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index 2967fb3b003..d6673ac8b8f 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -63,14 +63,14 @@ export class LinesLayout { * @param heightInPx The height of the whitespace, in pixels. * @return An id that can be used later to mutate or delete the whitespace */ - public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): number { + public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string { return this._whitespaces.insertWhitespace(afterLineNumber, ordinal, heightInPx, minWidth); } /** * Change properties associated with a certain whitespace. */ - public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean { + public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean { return this._whitespaces.changeWhitespace(id, newAfterLineNumber, newHeight); } @@ -80,7 +80,7 @@ export class LinesLayout { * @param id The whitespace to remove * @return Returns true if the whitespace is found and it is removed. */ - public removeWhitespace(id: number): boolean { + public removeWhitespace(id: string): boolean { return this._whitespaces.removeWhitespace(id); } diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index f753f65d393..ad24479b4b2 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -173,13 +173,13 @@ export class ViewLayout extends Disposable implements IViewLayout { // ---- IVerticalLayoutProvider - public addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): number { + public addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string { return this._linesLayout.insertWhitespace(afterLineNumber, ordinal, height, minWidth); } - public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean { + public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean { return this._linesLayout.changeWhitespace(id, newAfterLineNumber, newHeight); } - public removeWhitespace(id: number): boolean { + public removeWhitespace(id: string): boolean { return this._linesLayout.removeWhitespace(id); } public getVerticalOffsetForLineNumber(lineNumber: number): number { diff --git a/src/vs/editor/common/viewLayout/whitespaceComputer.ts b/src/vs/editor/common/viewLayout/whitespaceComputer.ts index 537c36ac807..8e5dd347e83 100644 --- a/src/vs/editor/common/viewLayout/whitespaceComputer.ts +++ b/src/vs/editor/common/viewLayout/whitespaceComputer.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as strings from 'vs/base/common/strings'; + export interface IEditorWhitespace { - readonly id: number; + readonly id: string; readonly afterLineNumber: number; readonly heightInLines: number; } @@ -15,6 +17,10 @@ export interface IEditorWhitespace { */ export class WhitespaceComputer { + private static INSTANCE_COUNT = 0; + + private readonly _instanceId: string; + /** * heights[i] is the height in pixels for whitespace at index i */ @@ -48,7 +54,7 @@ export class WhitespaceComputer { /** * ids[i] is the whitespace id of whitespace at index i */ - private readonly _ids: number[]; + private readonly _ids: string[]; /** * index at which a whitespace is positioned (inside heights, afterLineNumbers, prefixSum members) @@ -65,6 +71,7 @@ export class WhitespaceComputer { private _minWidth: number; constructor() { + this._instanceId = strings.singleLetterHash(++WhitespaceComputer.INSTANCE_COUNT); this._heights = []; this._minWidths = []; this._ids = []; @@ -113,21 +120,20 @@ export class WhitespaceComputer { * @param heightInPx The height of the whitespace, in pixels. * @return An id that can be used later to mutate or delete the whitespace */ - public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): number { + public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string { afterLineNumber = afterLineNumber | 0; ordinal = ordinal | 0; heightInPx = heightInPx | 0; minWidth = minWidth | 0; - let id = (++this._lastWhitespaceId); + let id = this._instanceId + (++this._lastWhitespaceId); let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, afterLineNumber, this._ordinals, ordinal); this._insertWhitespaceAtIndex(id, insertionIndex, afterLineNumber, ordinal, heightInPx, minWidth); this._minWidth = -1; /* marker for not being computed */ return id; } - private _insertWhitespaceAtIndex(id: number, insertIndex: number, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): void { - id = id | 0; + private _insertWhitespaceAtIndex(id: string, insertIndex: number, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): void { insertIndex = insertIndex | 0; afterLineNumber = afterLineNumber | 0; ordinal = ordinal | 0; @@ -150,15 +156,14 @@ export class WhitespaceComputer { } } - this._whitespaceId2Index[id.toString()] = insertIndex; + this._whitespaceId2Index[id] = insertIndex; this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1); } /** * Change properties associated with a certain whitespace. */ - public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean { - id = id | 0; + public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean { newAfterLineNumber = newAfterLineNumber | 0; newHeight = newHeight | 0; @@ -175,13 +180,11 @@ export class WhitespaceComputer { * @param newHeightInPx The new height of the whitespace, in pixels * @return Returns true if the whitespace is found and if the new height is different than the old height */ - public changeWhitespaceHeight(id: number, newHeightInPx: number): boolean { - id = id | 0; + public changeWhitespaceHeight(id: string, newHeightInPx: number): boolean { newHeightInPx = newHeightInPx | 0; - let sid = id.toString(); - if (this._whitespaceId2Index.hasOwnProperty(sid)) { - let index = this._whitespaceId2Index[sid]; + if (this._whitespaceId2Index.hasOwnProperty(id)) { + let index = this._whitespaceId2Index[id]; if (this._heights[index] !== newHeightInPx) { this._heights[index] = newHeightInPx; this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1); @@ -198,13 +201,11 @@ export class WhitespaceComputer { * @param newAfterLineNumber The new line number the whitespace will follow * @return Returns true if the whitespace is found and if the new line number is different than the old line number */ - public changeWhitespaceAfterLineNumber(id: number, newAfterLineNumber: number): boolean { - id = id | 0; + public changeWhitespaceAfterLineNumber(id: string, newAfterLineNumber: number): boolean { newAfterLineNumber = newAfterLineNumber | 0; - let sid = id.toString(); - if (this._whitespaceId2Index.hasOwnProperty(sid)) { - let index = this._whitespaceId2Index[sid]; + if (this._whitespaceId2Index.hasOwnProperty(id)) { + let index = this._whitespaceId2Index[id]; if (this._afterLineNumbers[index] !== newAfterLineNumber) { // `afterLineNumber` changed for this whitespace @@ -236,14 +237,10 @@ export class WhitespaceComputer { * @param id The whitespace to remove * @return Returns true if the whitespace is found and it is removed. */ - public removeWhitespace(id: number): boolean { - id = id | 0; - - let sid = id.toString(); - - if (this._whitespaceId2Index.hasOwnProperty(sid)) { - let index = this._whitespaceId2Index[sid]; - delete this._whitespaceId2Index[sid]; + public removeWhitespace(id: string): boolean { + if (this._whitespaceId2Index.hasOwnProperty(id)) { + let index = this._whitespaceId2Index[id]; + delete this._whitespaceId2Index[id]; this._removeWhitespaceAtIndex(index); this._minWidth = -1; /* marker for not being computed */ return true; @@ -459,7 +456,7 @@ export class WhitespaceComputer { * @param index The index of the whitespace. * @return `id` of whitespace at `index`. */ - public getIdForWhitespaceIndex(index: number): number { + public getIdForWhitespaceIndex(index: number): string { index = index | 0; return this._ids[index]; diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 9833c7c50c1..74b236d4b78 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -136,6 +136,13 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor let charCodeIsTab = (charCode === CharCode.Tab); let charCodeClass = classifier.get(charCode); + if (strings.isLowSurrogate(charCode)/* && i + 1 < len */) { + // A surrogate pair must always be considered as a single unit, so it is never to be broken + // => advance visibleColumn by 1 and advance to next char code... + visibleColumn = visibleColumn + 1; + continue; + } + if (charCodeClass === CharacterClass.BREAK_BEFORE) { // This is a character that indicates that a break should happen before it // Since we are certain the character before `i` fits, there's no extra checking needed, diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index f593fdbc563..5ff99437b1b 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -155,13 +155,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private columnsForFullWidthChar: number; private wrappingIndent: WrappingIndent; private tabSize: number; - private lines: ISplitLine[]; + private lines!: ISplitLine[]; - private prefixSumComputer: PrefixSumComputerWithCache; + private prefixSumComputer!: PrefixSumComputerWithCache; private readonly linePositionMapperFactory: ILineMapperFactory; - private hiddenAreasIds: string[]; + private hiddenAreasIds!: string[]; constructor(model: ITextModel, linePositionMapperFactory: ILineMapperFactory, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent) { this.model = model; diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index fe832ef3707..0f16d0a84a2 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -17,7 +17,7 @@ import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceCompute import { ITheme } from 'vs/platform/theme/common/themeService'; export interface IViewWhitespaceViewportData { - readonly id: number; + readonly id: string; readonly afterLineNumber: number; readonly verticalOffset: number; readonly height: number; @@ -74,15 +74,15 @@ export interface IViewLayout { * Reserve rendering space. * @return an identifier that can be later used to remove or change the whitespace. */ - addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): number; + addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string; /** * Change the properties of a whitespace. */ - changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean; + changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean; /** * Remove rendering space */ - removeWhitespace(id: number): boolean; + removeWhitespace(id: string): boolean; /** * Get the layout information for whitespaces currently in the viewport */ diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts index 96944088919..07df48c622e 100644 --- a/src/vs/editor/common/viewModel/viewModelDecorations.ts +++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts @@ -42,7 +42,8 @@ export class ViewModelDecorations implements IDisposable { this._linesCollection = linesCollection; this._coordinatesConverter = coordinatesConverter; this._decorationsCache = Object.create(null); - this._clearCachedModelDecorationsResolver(); + this._cachedModelDecorationsResolver = null; + this._cachedModelDecorationsResolverViewRange = null; } private _clearCachedModelDecorationsResolver(): void { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 5161636aa45..65284147a64 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -6,7 +6,7 @@ import { Color } from 'vs/base/common/color'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; -import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; +import { IConfigurationChangedEvent, EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; @@ -668,12 +668,13 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const fontInfo = this.configuration.editor.fontInfo; const colorMap = this._getColorMap(); + const fontFamily = fontInfo.fontFamily === EDITOR_FONT_DEFAULTS.fontFamily ? fontInfo.fontFamily : `'${fontInfo.fontFamily}', ${EDITOR_FONT_DEFAULTS.fontFamily}`; return ( `
{ bcd: { diagnostics: [], edit: new class implements modes.WorkspaceEdit { - edits: modes.ResourceTextEdit[]; + edits!: modes.ResourceTextEdit[]; }, title: 'abc' } diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 9f36b691705..1aed8453777 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -32,7 +32,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { private _currentCodeLensModel: CodeLensModel | undefined; private _modelChangeCounter: number = 0; private _currentResolveCodeLensSymbolsPromise: CancelablePromise | undefined; - private _detectVisibleLenses: RunOnceScheduler; + private _detectVisibleLenses!: RunOnceScheduler; constructor( private readonly _editor: editorBrowser.ICodeEditor, diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 24ed75a4405..5ae20b27edb 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -60,7 +60,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { private readonly _editor: editorBrowser.ICodeEditor; private readonly _commands = new Map(); - private _widgetPosition: editorBrowser.IContentWidgetPosition; + private _widgetPosition?: editorBrowser.IContentWidgetPosition; constructor( editor: editorBrowser.ICodeEditor, @@ -147,8 +147,8 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { }; } - getPosition(): editorBrowser.IContentWidgetPosition { - return this._widgetPosition; + getPosition(): editorBrowser.IContentWidgetPosition | null { + return this._widgetPosition || null; } isVisible(): boolean { @@ -192,9 +192,9 @@ export class CodeLensHelper { export class CodeLensWidget { private readonly _editor: editorBrowser.ICodeEditor; - private readonly _viewZone: CodeLensViewZone; - private readonly _viewZoneId: number; - private readonly _contentWidget: CodeLensContentWidget; + private readonly _viewZone!: CodeLensViewZone; + private readonly _viewZoneId!: string; + private readonly _contentWidget!: CodeLensContentWidget; private _decorationIds: string[]; private _data: CodeLensItem[]; diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index e225e6a844e..b4e8404b423 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -123,8 +123,8 @@ class SaturationBox extends Disposable { private readonly domNode: HTMLElement; private readonly selection: HTMLElement; private readonly canvas: HTMLCanvasElement; - private width: number; - private height: number; + private width!: number; + private height!: number; private monitor: GlobalMouseMoveMonitor | null; private _onDidChange = new Emitter<{ s: number, v: number }>(); @@ -235,7 +235,7 @@ abstract class Strip extends Disposable { protected domNode: HTMLElement; protected overlay: HTMLElement; protected slider: HTMLElement; - private height: number; + private height!: number; private _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index 26b3f9e9111..a097d994648 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -49,7 +49,7 @@ export const enum Type { export class LineCommentCommand implements editorCommon.ICommand { private readonly _selection: Selection; - private _selectionId: string; + private _selectionId: string | null; private _deltaColumn: number; private _moveEndPositionDown: boolean; private readonly _tabSize: number; @@ -57,9 +57,11 @@ export class LineCommentCommand implements editorCommon.ICommand { constructor(selection: Selection, tabSize: number, type: Type) { this._selection = selection; + this._selectionId = null; this._tabSize = tabSize; this._type = type; this._deltaColumn = 0; + this._moveEndPositionDown = false; } /** @@ -323,7 +325,7 @@ export class LineCommentCommand implements editorCommon.ICommand { } public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { - let result = helper.getTrackedSelection(this._selectionId); + let result = helper.getTrackedSelection(this._selectionId!); if (this._moveEndPositionDown) { result = result.setEndPosition(result.endLineNumber + 1, 1); diff --git a/src/vs/editor/contrib/dnd/dragAndDropCommand.ts b/src/vs/editor/contrib/dnd/dragAndDropCommand.ts index f2c9aa86932..8d1d8a32b02 100644 --- a/src/vs/editor/contrib/dnd/dragAndDropCommand.ts +++ b/src/vs/editor/contrib/dnd/dragAndDropCommand.ts @@ -14,13 +14,14 @@ export class DragAndDropCommand implements editorCommon.ICommand { private readonly selection: Selection; private readonly targetPosition: Position; - private targetSelection: Selection; + private targetSelection: Selection | null; private readonly copy: boolean; constructor(selection: Selection, targetPosition: Position, copy: boolean) { this.selection = selection; this.targetPosition = targetPosition; this.copy = copy; + this.targetSelection = null; } public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { @@ -102,6 +103,6 @@ export class DragAndDropCommand implements editorCommon.ICommand { } public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { - return this.targetSelection; + return this.targetSelection!; } } diff --git a/src/vs/editor/contrib/documentSymbols/outlineModel.ts b/src/vs/editor/contrib/documentSymbols/outlineModel.ts index 180b0d5a55c..e5004629993 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineModel.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineModel.ts @@ -202,16 +202,33 @@ export class OutlineGroup extends TreeElement { } } +class MovingAverage { + + private _n = 1; + private _val = 0; + + update(value: number): this { + this._val = this._val + (value - this._val) / this._n; + this._n += 1; + return this; + } + + get value(): number { + return this._val; + } +} + export class OutlineModel extends TreeElement { + private static readonly _requestDurations = new LRUCache(50, 0.7); private static readonly _requests = new LRUCache, model: OutlineModel | undefined }>(9, 0.75); private static readonly _keys = new class { private _counter = 1; private _data = new WeakMap(); - for(textModel: ITextModel): string { - return `${textModel.id}/${textModel.getVersionId()}/${this._hash(DocumentSymbolProviderRegistry.all(textModel))}`; + for(textModel: ITextModel, version: boolean): string { + return `${textModel.id}/${version ? textModel.getVersionId() : ''}/${this._hash(DocumentSymbolProviderRegistry.all(textModel))}`; } private _hash(providers: DocumentSymbolProvider[]): string { @@ -231,7 +248,7 @@ export class OutlineModel extends TreeElement { static create(textModel: ITextModel, token: CancellationToken): Promise { - let key = this._keys.for(textModel); + let key = this._keys.for(textModel, true); let data = OutlineModel._requests.get(key); if (!data) { @@ -243,6 +260,18 @@ export class OutlineModel extends TreeElement { model: undefined, }; OutlineModel._requests.set(key, data); + + // keep moving average of request durations + const now = Date.now(); + data.promise.then(() => { + let key = this._keys.for(textModel, false); + let avg = this._requestDurations.get(key); + if (!avg) { + avg = new MovingAverage(); + this._requestDurations.set(key, avg); + } + avg.update(Date.now() - now); + }); } if (data!.model) { @@ -272,7 +301,18 @@ export class OutlineModel extends TreeElement { }); } - static _create(textModel: ITextModel, token: CancellationToken): Promise { + static getRequestDelay(textModel: ITextModel | null): number { + if (!textModel) { + return 350; + } + const avg = this._requestDurations.get(this._keys.for(textModel, false)); + if (!avg) { + return 350; + } + return Math.max(350, Math.floor(1.3 * avg.value)); + } + + private static _create(textModel: ITextModel, token: CancellationToken): Promise { const cts = new CancellationTokenSource(token); const result = new OutlineModel(textModel); diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index a77c8d1bd2e..365b3e45c13 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -45,16 +45,19 @@ export class OutlineIdentityProvider implements IIdentityProvider { export class OutlineGroupTemplate { static id = 'OutlineGroupTemplate'; - - labelContainer: HTMLElement; - label: HighlightedLabel; + constructor( + readonly labelContainer: HTMLElement, + readonly label: HighlightedLabel, + ) { } } export class OutlineElementTemplate { static id = 'OutlineElementTemplate'; - container: HTMLElement; - iconLabel: IconLabel; - decoration: HTMLElement; + constructor( + readonly container: HTMLElement, + readonly iconLabel: IconLabel, + readonly decoration: HTMLElement, + ) { } } export class OutlineVirtualDelegate implements IListVirtualDelegate { @@ -80,7 +83,7 @@ export class OutlineGroupRenderer implements ITreeRenderer, index: number, template: OutlineGroupTemplate): void { @@ -109,7 +112,7 @@ export class OutlineElementRenderer implements ITreeRenderer, index: number, template: OutlineElementTemplate): void { diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 739cde26fef..664bc8c8bf3 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -12,19 +12,20 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, ServicesAccessor, registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_VISIBLE, FIND_IDS, FindModelBoundToEditorModel, ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleSearchScopeKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; +import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_VISIBLE, FIND_IDS, FindModelBoundToEditorModel, ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleSearchScopeKeybinding, ToggleWholeWordKeybinding, CONTEXT_REPLACE_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel'; import { FindOptionsWidget } from 'vs/editor/contrib/find/findOptionsWidget'; import { FindReplaceState, FindReplaceStateChangedEvent, INewFindReplaceState } from 'vs/editor/contrib/find/findState'; import { FindWidget, IFindController } from 'vs/editor/contrib/find/findWidget'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { optional } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; const SEARCH_STRING_MAX_LENGTH = 524288; @@ -75,7 +76,7 @@ export class CommonFindController extends Disposable implements editorCommon.IEd protected _state: FindReplaceState; protected _updateHistoryDelayer: Delayer; private _model: FindModelBoundToEditorModel | null; - private readonly _storageService: IStorageService; + protected readonly _storageService: IStorageService; private readonly _clipboardService: IClipboardService; protected readonly _contextKeyService: IContextKeyService; @@ -374,8 +375,8 @@ export class CommonFindController extends Disposable implements editorCommon.IEd export class FindController extends CommonFindController implements IFindController { - private _widget: FindWidget; - private _findOptionsWidget: FindOptionsWidget; + private _widget: FindWidget | null; + private _findOptionsWidget: FindOptionsWidget | null; constructor( editor: ICodeEditor, @@ -383,10 +384,13 @@ export class FindController extends CommonFindController implements IFindControl @IContextKeyService _contextKeyService: IContextKeyService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @IThemeService private readonly _themeService: IThemeService, - @IStorageService storageService: IStorageService, - @optional(IClipboardService) clipboardService: IClipboardService + @INotificationService private readonly _notificationService: INotificationService, + @IStorageService _storageService: IStorageService, + @optional(IClipboardService) clipboardService: IClipboardService, ) { - super(editor, _contextKeyService, storageService, clipboardService); + super(editor, _contextKeyService, _storageService, clipboardService); + this._widget = null; + this._findOptionsWidget = null; } protected _start(opts: IFindStartOptions): void { @@ -394,7 +398,7 @@ export class FindController extends CommonFindController implements IFindControl this._createFindWidget(); } - if (!this._widget.getPosition() && this._editor.getConfiguration().contribInfo.find.autoFindInSelection) { + if (!this._widget!.getPosition() && this._editor.getConfiguration().contribInfo.find.autoFindInSelection) { // not visible yet so we need to set search scope if `editor.find.autoFindInSelection` is `true` opts.updateSearchScope = true; } @@ -402,9 +406,9 @@ export class FindController extends CommonFindController implements IFindControl super._start(opts); if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) { - this._widget.focusReplaceInput(); + this._widget!.focusReplaceInput(); } else if (opts.shouldFocus === FindStartFocusAction.FocusFindInput) { - this._widget.focusFindInput(); + this._widget!.focusFindInput(); } } @@ -413,14 +417,14 @@ export class FindController extends CommonFindController implements IFindControl this._createFindWidget(); } if (this._state.isRevealed) { - this._widget.highlightFindOptions(); + this._widget!.highlightFindOptions(); } else { - this._findOptionsWidget.highlightFindOptions(); + this._findOptionsWidget!.highlightFindOptions(); } } private _createFindWidget() { - this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService)); + this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._themeService, this._storageService, this._notificationService)); this._findOptionsWidget = this._register(new FindOptionsWidget(this._editor, this._state, this._keybindingService, this._themeService)); } } @@ -538,6 +542,27 @@ export class NextMatchFindAction extends MatchFindAction { } } +export class NextMatchFindAction2 extends MatchFindAction { + + constructor() { + super({ + id: FIND_IDS.NextMatchFindAction, + label: nls.localize('findNextMatchAction', "Find Next"), + alias: 'Find Next', + precondition: undefined, + kbOpts: { + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED), + primary: KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + } + }); + } + + protected _run(controller: CommonFindController): boolean { + return controller.moveToNextMatch(); + } +} + export class PreviousMatchFindAction extends MatchFindAction { constructor() { @@ -560,6 +585,27 @@ export class PreviousMatchFindAction extends MatchFindAction { } } +export class PreviousMatchFindAction2 extends MatchFindAction { + + constructor() { + super({ + id: FIND_IDS.PreviousMatchFindAction, + label: nls.localize('findPreviousMatchAction', "Find Previous"), + alias: 'Find Previous', + precondition: undefined, + kbOpts: { + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED), + primary: KeyMod.Shift | KeyCode.Enter, + weight: KeybindingWeight.EditorContrib + } + }); + } + + protected _run(controller: CommonFindController): boolean { + return controller.moveToPrevMatch(); + } +} + export abstract class SelectionMatchFindAction extends EditorAction { public run(accessor: ServicesAccessor | null, editor: ICodeEditor): void { let controller = CommonFindController.get(editor); @@ -693,7 +739,9 @@ registerEditorContribution(FindController); registerEditorAction(StartFindAction); registerEditorAction(StartFindWithSelectionAction); registerEditorAction(NextMatchFindAction); +registerEditorAction(NextMatchFindAction2); registerEditorAction(PreviousMatchFindAction); +registerEditorAction(PreviousMatchFindAction2); registerEditorAction(NextSelectionMatchFindAction); registerEditorAction(PreviousSelectionMatchFindAction); registerEditorAction(StartFindReplaceAction); @@ -779,6 +827,17 @@ registerEditorCommand(new FindCommand({ } })); +registerEditorCommand(new FindCommand({ + id: FIND_IDS.ReplaceOneAction, + precondition: CONTEXT_FIND_WIDGET_VISIBLE, + handler: x => x.replace(), + kbOpts: { + weight: KeybindingWeight.EditorContrib + 5, + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_REPLACE_INPUT_FOCUSED), + primary: KeyCode.Enter + } +})); + registerEditorCommand(new FindCommand({ id: FIND_IDS.ReplaceAllAction, precondition: CONTEXT_FIND_WIDGET_VISIBLE, @@ -790,6 +849,20 @@ registerEditorCommand(new FindCommand({ } })); +registerEditorCommand(new FindCommand({ + id: FIND_IDS.ReplaceAllAction, + precondition: CONTEXT_FIND_WIDGET_VISIBLE, + handler: x => x.replaceAll(), + kbOpts: { + weight: KeybindingWeight.EditorContrib + 5, + kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_REPLACE_INPUT_FOCUSED), + primary: undefined, + mac: { + primary: KeyMod.CtrlCmd | KeyCode.Enter, + } + } +})); + registerEditorCommand(new FindCommand({ id: FIND_IDS.SelectAllMatchesAction, precondition: CONTEXT_FIND_WIDGET_VISIBLE, diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index 7e1140442ea..d6c6f602e48 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -32,8 +32,8 @@ .monaco-editor .find-widget { position: absolute; z-index: 10; - top: -44px; /* find input height + shadow (10px) */ - height: 34px; /* find input height */ + top: -44px; + height: 33px; overflow: hidden; line-height: 19px; transition: top 200ms linear; @@ -47,12 +47,10 @@ /* Find widget when replace is toggled on */ .monaco-editor .find-widget.replaceToggled { top: -74px; /* find input height + replace input height + shadow (10px) */ - height: 64px; /* find input height + replace input height */ } .monaco-editor .find-widget.replaceToggled > .replace-part { display: flex; display: -webkit-flex; - align-items: center; } .monaco-editor .find-widget.visible, @@ -60,13 +58,26 @@ top: 0; } +/* Multiple line find widget */ + +.monaco-editor .find-widget.multipleline { + top: unset; + bottom: 10px; +} + +.monaco-editor .find-widget.multipleline.visible, +.monaco-editor .find-widget.multipleline.replaceToggled.visible { + top: 0px; + bottom: unset; +} + .monaco-editor .find-widget .monaco-inputbox .input { background-color: transparent; /* Style to compensate for //winjs */ min-height: 0; } -.monaco-editor .find-widget .replace-input .input { +.monaco-editor .find-widget .monaco-findInput .input { font-size: 13px; } @@ -76,29 +87,38 @@ font-size: 12px; display: flex; display: -webkit-flex; - align-items: center; } .monaco-editor .find-widget > .find-part .monaco-inputbox, .monaco-editor .find-widget > .replace-part .monaco-inputbox { - height: 25px; + min-height: 25px; } -.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input { - width: 100% !important; - padding-right: 66px; -} -.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input { +.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .mirror { padding-right: 22px; } .monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input, -.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input { +.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .mirror, +.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input, +.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .mirror { padding-top: 2px; padding-bottom: 2px; } +.monaco-editor .find-widget > .find-part .find-actions { + height: 25px; + display: flex; + align-items: center; +} + +.monaco-editor .find-widget > .replace-part .replace-actions { + height: 25px; + display: flex; + align-items: center; +} + .monaco-editor .find-widget .monaco-findInput { vertical-align: middle; display: flex; @@ -106,6 +126,16 @@ flex:1; } +.monaco-editor .find-widget .monaco-findInput .monaco-scrollable-element { + /* Make sure textarea inherits the width correctly */ + width: 100%; +} + +.monaco-editor .find-widget .monaco-findInput .monaco-scrollable-element .scrollbar.vertical { + /* Hide vertical scrollbar */ + opacity: 0; +} + .monaco-editor .find-widget .matchesCount { display: flex; display: -webkit-flex; @@ -237,15 +267,17 @@ display: none; } -.monaco-editor .find-widget > .replace-part > .replace-input { +.monaco-editor .find-widget > .replace-part > .monaco-findInput { position: relative; display: flex; display: -webkit-flex; vertical-align: middle; - width: auto !important; + flex: auto; + flex-grow: 0; + flex-shrink: 0; } -.monaco-editor .find-widget > .replace-part > .replace-input > .controls { +.monaco-editor .find-widget > .replace-part > .monaco-findInput > .controls { position: absolute; top: 3px; right: 2px; diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 84528f398cc..2ea8ba03dd7 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -9,11 +9,12 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; -import { HistoryInputBox, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; +import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; import { IHorizontalSashLayoutProvider, ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; import { Widget } from 'vs/base/browser/ui/widget'; -import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { Delayer } from 'vs/base/common/async'; import { Color } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -30,9 +31,10 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetResizeBorder, errorForeground, inputActiveOptionBorder, inputActiveOptionBackground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; +import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export interface IFindController { replace(): void; @@ -48,7 +50,6 @@ const NLS_TOGGLE_SELECTION_FIND_TITLE = nls.localize('label.toggleSelectionFind' const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close"); const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace"); const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Replace"); -const NLS_PRESERVE_CASE_LABEL = nls.localize('label.preserveCaseCheckbox', "Preserve Case"); const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace"); const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All"); const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode"); @@ -59,14 +60,12 @@ const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results"); const FIND_WIDGET_INITIAL_WIDTH = 411; const PART_WIDTH = 275; const FIND_INPUT_AREA_WIDTH = PART_WIDTH - 54; -const REPLACE_INPUT_AREA_WIDTH = FIND_INPUT_AREA_WIDTH; let MAX_MATCHES_COUNT_WIDTH = 69; let FIND_ALL_CONTROLS_WIDTH = 17/** Find Input margin-left */ + (MAX_MATCHES_COUNT_WIDTH + 3 + 1) /** Match Results */ + 23 /** Button */ * 4 + 2/** sash */; -const FIND_INPUT_AREA_HEIGHT = 34; // The height of Find Widget when Replace Input is not visible. -const FIND_REPLACE_AREA_HEIGHT = 64; // The height of Find Widget when Replace Input is visible. - +const FIND_INPUT_AREA_HEIGHT = 33; // The height of Find Widget when Replace Input is not visible. +const ctrlEnterReplaceAllWarningPromptedKey = 'ctrlEnterReplaceAll.windows.donotask'; export class FindWidgetViewZone implements IViewZone { public readonly afterLineNumber: number; @@ -84,6 +83,22 @@ export class FindWidgetViewZone implements IViewZone { } } +function stopPropagationForMultiLineUpwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) { + const isMultiline = !!value.match(/\n/); + if (textarea && isMultiline && textarea.selectionStart > 0) { + event.stopPropagation(); + return; + } +} + +function stopPropagationForMultiLineDownwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) { + const isMultiline = !!value.match(/\n/); + if (textarea && isMultiline && textarea.selectionEnd < textarea.value.length) { + event.stopPropagation(); + return; + } +} + export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSashLayoutProvider { private static readonly ID = 'editor.contrib.findWidget'; private readonly _codeEditor: ICodeEditor; @@ -92,34 +107,37 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private readonly _contextViewProvider: IContextViewProvider; private readonly _keybindingService: IKeybindingService; private readonly _contextKeyService: IContextKeyService; + private readonly _storageService: IStorageService; + private readonly _notificationService: INotificationService; - private _domNode: HTMLElement; - private _findInput: FindInput; - private _replaceInputBox: HistoryInputBox; + private _domNode!: HTMLElement; + private _cachedHeight: number | null; + private _findInput!: FindInput; + private _replaceInput!: ReplaceInput; - private _toggleReplaceBtn: SimpleButton; - private _matchesCount: HTMLElement; - private _prevBtn: SimpleButton; - private _nextBtn: SimpleButton; - private _toggleSelectionFind: SimpleCheckbox; - private _closeBtn: SimpleButton; - private _preserveCase: Checkbox; - private _replaceBtn: SimpleButton; - private _replaceAllBtn: SimpleButton; + private _toggleReplaceBtn!: SimpleButton; + private _matchesCount!: HTMLElement; + private _prevBtn!: SimpleButton; + private _nextBtn!: SimpleButton; + private _toggleSelectionFind!: SimpleCheckbox; + private _closeBtn!: SimpleButton; + private _replaceBtn!: SimpleButton; + private _replaceAllBtn!: SimpleButton; private _isVisible: boolean; private _isReplaceVisible: boolean; private _ignoreChangeEvent: boolean; + private _ctrlEnterReplaceAllWarningPrompted: boolean; private readonly _findFocusTracker: dom.IFocusTracker; private readonly _findInputFocused: IContextKey; private readonly _replaceFocusTracker: dom.IFocusTracker; private readonly _replaceInputFocused: IContextKey; private _viewZone?: FindWidgetViewZone; - private _viewZoneId?: number; + private _viewZoneId?: string; - private _resizeSash: Sash; - private _resized: boolean; + private _resizeSash!: Sash; + private _resized!: boolean; private readonly _updateHistoryDelayer: Delayer; constructor( @@ -129,7 +147,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas contextViewProvider: IContextViewProvider, keybindingService: IKeybindingService, contextKeyService: IContextKeyService, - themeService: IThemeService + themeService: IThemeService, + storageService: IStorageService, + notificationService: INotificationService, ) { super(); this._codeEditor = codeEditor; @@ -138,6 +158,10 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._contextViewProvider = contextViewProvider; this._keybindingService = keybindingService; this._contextKeyService = contextKeyService; + this._storageService = storageService; + this._notificationService = notificationService; + + this._ctrlEnterReplaceAllWarningPrompted = !!storageService.getBoolean(ctrlEnterReplaceAllWarningPromptedKey, StorageScope.GLOBAL); this._isVisible = false; this._isReplaceVisible = false; @@ -149,6 +173,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._buildDomNode(); this._updateButtons(); this._tryUpdateWidgetWidth(); + this._findInput.inputBox.layout(); this._register(this._codeEditor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { if (e.readOnly) { @@ -203,7 +228,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas })); this._replaceInputFocused = CONTEXT_REPLACE_INPUT_FOCUSED.bindTo(contextKeyService); - this._replaceFocusTracker = this._register(dom.trackFocus(this._replaceInputBox.inputElement)); + this._replaceFocusTracker = this._register(dom.trackFocus(this._replaceInput.inputBox.inputElement)); this._register(this._replaceFocusTracker.onDidFocus(() => { this._replaceInputFocused.set(true); this._updateSearchScope(); @@ -224,15 +249,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (!this._isVisible) { return; } - if (this._viewZoneId === undefined) { - return; - } - this._codeEditor.changeViewZones((accessor) => { - if (this._viewZoneId) { - accessor.removeZone(this._viewZoneId); - } - this._viewZoneId = undefined; - }); + this._viewZoneId = undefined; })); @@ -272,6 +289,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _onStateChanged(e: FindReplaceStateChangedEvent): void { if (e.searchString) { + if (this._state.searchString.indexOf('\n') >= 0) { + dom.addClass(this._domNode, 'multipleline'); + } else { + dom.removeClass(this._domNode, 'multipleline'); + } + try { this._ignoreChangeEvent = true; this._findInput.setValue(this._state.searchString); @@ -281,7 +304,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._updateButtons(); } if (e.replaceString) { - this._replaceInputBox.value = this._state.replaceString; + this._replaceInput.inputBox.value = this._state.replaceString; } if (e.isRevealed) { if (this._state.isRevealed) { @@ -294,8 +317,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (this._state.isReplaceRevealed) { if (!this._codeEditor.getConfiguration().readOnly && !this._isReplaceVisible) { this._isReplaceVisible = true; - this._replaceInputBox.width = this._findInput.inputBox.width; + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); this._updateButtons(); + this._replaceInput.inputBox.layout(); } } else { if (this._isReplaceVisible) { @@ -304,6 +328,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } } } + if ((e.isRevealed || e.isReplaceRevealed) && (this._state.isRevealed || this._state.isReplaceRevealed)) { + if (this._tryUpdateHeight()) { + this._showViewZone(); + } + } + if (e.isRegex) { this._findInput.setRegex(this._state.isRegex); } @@ -345,7 +375,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._findInput.inputBox.addToHistory(); } if (this._state.replaceString) { - this._replaceInputBox.addToHistory(); + this._replaceInput.inputBox.addToHistory(); } } @@ -410,7 +440,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _updateButtons(): void { this._findInput.setEnabled(this._isVisible); - this._replaceInputBox.setEnabled(this._isVisible && this._isReplaceVisible); + this._replaceInput.setEnabled(this._isVisible && this._isReplaceVisible); this._updateToggleSelectionFindButton(); this._closeBtn.setEnabled(this._isVisible); @@ -520,12 +550,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } this._codeEditor.changeViewZones((accessor) => { - if (this._state.isReplaceRevealed) { - viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT; - } else { - viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; - } - + viewZone.heightInPx = this._getHeight(); 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() + viewZone.heightInPx); @@ -533,30 +558,47 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } private _showViewZone(adjustScroll: boolean = true) { - const viewZone = this._viewZone; - if (!this._isVisible || !viewZone) { + if (!this._isVisible) { return; } + const addExtraSpaceOnTop = this._codeEditor.getConfiguration().contribInfo.find.addExtraSpaceOnTop; + + if (!addExtraSpaceOnTop) { + return; + } + + if (this._viewZone === undefined) { + this._viewZone = new FindWidgetViewZone(0); + } + + const viewZone = this._viewZone; + this._codeEditor.changeViewZones((accessor) => { - let scrollAdjustment = FIND_INPUT_AREA_HEIGHT; - if (this._viewZoneId !== undefined) { - if (this._state.isReplaceRevealed) { - viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT; - scrollAdjustment = FIND_REPLACE_AREA_HEIGHT - FIND_INPUT_AREA_HEIGHT; - } else { - viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; - scrollAdjustment = FIND_INPUT_AREA_HEIGHT - FIND_REPLACE_AREA_HEIGHT; + // the view zone already exists, we need to update the height + const newHeight = this._getHeight(); + if (newHeight === viewZone.heightInPx) { + return; } - accessor.removeZone(this._viewZoneId); - } else { - viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; - } - this._viewZoneId = accessor.addZone(viewZone); - if (adjustScroll) { - this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment); + let scrollAdjustment = newHeight - viewZone.heightInPx; + viewZone.heightInPx = newHeight; + accessor.layoutZone(this._viewZoneId); + + if (adjustScroll) { + this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment); + } + + return; + } else { + const scrollAdjustment = this._getHeight(); + viewZone.heightInPx = scrollAdjustment; + this._viewZoneId = accessor.addZone(viewZone); + + if (adjustScroll) { + this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment); + } } }); } @@ -592,8 +634,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder), }; this._findInput.style(inputStyles); - this._replaceInputBox.style(inputStyles); - this._preserveCase.style(inputStyles); + this._replaceInput.style(inputStyles); } private _tryUpdateWidgetWidth() { @@ -623,7 +664,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (widgetWidth > FIND_WIDGET_INITIAL_WIDTH) { // as the widget is resized by users, we may need to change the max width of the widget as the editor width changes. this._domNode.style.maxWidth = `${editorWidth - 28 - minimapWidth - 15}px`; - this._replaceInputBox.inputElement.style.width = `${dom.getTotalWidth(this._findInput.inputBox.inputElement)}px`; + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); return; } } @@ -647,13 +688,47 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } if (this._resized) { - let findInputWidth = dom.getTotalWidth(this._findInput.inputBox.inputElement); + this._findInput.inputBox.layout(); + let findInputWidth = this._findInput.inputBox.width; if (findInputWidth > 0) { - this._replaceInputBox.inputElement.style.width = `${findInputWidth}px`; + this._replaceInput.width = findInputWidth; } } } + private _getHeight(): number { + let totalheight = 0; + + // find input margin top + totalheight += 4; + + // find input height + totalheight += this._findInput.inputBox.height + 2 /** input box border */; + + if (this._isReplaceVisible) { + // replace input margin + totalheight += 4; + + totalheight += this._replaceInput.inputBox.height + 2 /** input box border */; + } + + // margin bottom + totalheight += 4; + return totalheight; + } + + private _tryUpdateHeight(): boolean { + const totalHeight = this._getHeight(); + if (this._cachedHeight !== null && this._cachedHeight === totalHeight) { + return false; + } + + this._cachedHeight = totalHeight; + this._domNode.style.height = `${totalHeight}px`; + + return true; + } + // ----- Public public focusFindInput(): void { @@ -663,9 +738,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } public focusReplaceInput(): void { - this._replaceInputBox.select(); + this._replaceInput.select(); // Edge browser requires focus() in addition to select() - this._replaceInputBox.focus(); + this._replaceInput.focus(); } public highlightFindOptions(): void { @@ -700,22 +775,25 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } private _onFindInputKeyDown(e: IKeyboardEvent): void { + if (e.equals(KeyMod.WinCtrl | KeyCode.Enter)) { + const inputElement = this._findInput.inputBox.inputElement; + const start = inputElement.selectionStart; + const end = inputElement.selectionEnd; + const content = inputElement.value; - if (e.equals(KeyCode.Enter)) { - this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError); - e.preventDefault(); - return; - } - - if (e.equals(KeyMod.Shift | KeyCode.Enter)) { - this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError); - e.preventDefault(); - return; + if (start && end) { + const value = content.substr(0, start) + '\n' + content.substr(end); + this._findInput.inputBox.value = value; + inputElement.setSelectionRange(start + 1, start + 1); + this._findInput.inputBox.layout(); + e.preventDefault(); + return; + } } if (e.equals(KeyCode.Tab)) { if (this._isReplaceVisible) { - this._replaceInputBox.focus(); + this._replaceInput.focus(); } else { this._findInput.focusOnCaseSensitive(); } @@ -728,20 +806,43 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas e.preventDefault(); return; } + + if (e.equals(KeyCode.UpArrow)) { + return stopPropagationForMultiLineUpwards(e, this._findInput.getValue(), this._findInput.domNode.querySelector('textarea')); + } + + if (e.equals(KeyCode.DownArrow)) { + return stopPropagationForMultiLineDownwards(e, this._findInput.getValue(), this._findInput.domNode.querySelector('textarea')); + } } private _onReplaceInputKeyDown(e: IKeyboardEvent): void { + if (e.equals(KeyMod.WinCtrl | KeyCode.Enter)) { + if (platform.isWindows && platform.isNative && !this._ctrlEnterReplaceAllWarningPrompted) { + // this is the first time when users press Ctrl + Enter to replace all + this._notificationService.info( + nls.localize('ctrlEnter.keybindingChanged', + 'Ctrl+Enter now inserts line break instead of replacing all. You can modify the keybinding for editor.action.replaceAll to override this behavior.') + ); - if (e.equals(KeyCode.Enter)) { - this._controller.replace(); - e.preventDefault(); - return; - } + this._ctrlEnterReplaceAllWarningPrompted = true; + this._storageService.store(ctrlEnterReplaceAllWarningPromptedKey, true, StorageScope.GLOBAL); - if (e.equals(KeyMod.CtrlCmd | KeyCode.Enter)) { - this._controller.replaceAll(); - e.preventDefault(); - return; + } + + const inputElement = this._replaceInput.inputBox.inputElement; + const start = inputElement.selectionStart; + const end = inputElement.selectionEnd; + const content = inputElement.value; + + if (start && end) { + const value = content.substr(0, start) + '\n' + content.substr(end); + this._replaceInput.inputBox.value = value; + inputElement.setSelectionRange(start + 1, start + 1); + this._replaceInput.inputBox.layout(); + e.preventDefault(); + return; + } } if (e.equals(KeyCode.Tab)) { @@ -761,6 +862,14 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas e.preventDefault(); return; } + + if (e.equals(KeyCode.UpArrow)) { + return stopPropagationForMultiLineUpwards(e, this._replaceInput.inputBox.value, this._replaceInput.inputBox.element.querySelector('textarea')); + } + + if (e.equals(KeyCode.DownArrow)) { + return stopPropagationForMultiLineDownwards(e, this._replaceInput.inputBox.value, this._replaceInput.inputBox.element.querySelector('textarea')); + } } // ----- sash @@ -784,7 +893,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas return ` (${kb.getLabel()})`; } - private _buildFindPart(): HTMLElement { + private _buildDomNode(): void { + const flexibleHeight = platform.isNative; + const flexibleWidth = platform.isNative; // Find input this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewProvider, { width: FIND_INPUT_AREA_WIDTH, @@ -804,7 +915,10 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } catch (e) { return { content: e.message }; } - } + }, + flexibleHeight, + flexibleWidth, + flexibleMaxHeight: 118 }, this._contextKeyService, true)); this._findInput.setRegex(!!this._state.isRegex); this._findInput.setCaseSensitive(!!this._state.matchCase); @@ -826,11 +940,24 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._register(this._findInput.onCaseSensitiveKeyDown((e) => { if (e.equals(KeyMod.Shift | KeyCode.Tab)) { if (this._isReplaceVisible) { - this._replaceInputBox.focus(); + this._replaceInput.focus(); e.preventDefault(); } } })); + this._register(this._findInput.onRegexKeyDown((e) => { + if (e.equals(KeyCode.Tab)) { + if (this._isReplaceVisible) { + this._replaceInput.focusOnPreserve(); + e.preventDefault(); + } + } + })); + this._register(this._findInput.inputBox.onDidHeightChange((e) => { + if (this._tryUpdateHeight()) { + this._showViewZone(); + } + })); if (platform.isLinux) { this._register(this._findInput.onMouseDown((e) => this._onFindInputMouseDown(e))); } @@ -860,13 +987,16 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas let findPart = document.createElement('div'); findPart.className = 'find-part'; findPart.appendChild(this._findInput.domNode); - findPart.appendChild(this._matchesCount); - findPart.appendChild(this._prevBtn.domNode); - findPart.appendChild(this._nextBtn.domNode); + const actionsContainer = document.createElement('div'); + actionsContainer.className = 'find-actions'; + findPart.appendChild(actionsContainer); + actionsContainer.appendChild(this._matchesCount); + actionsContainer.appendChild(this._prevBtn.domNode); + actionsContainer.appendChild(this._nextBtn.domNode); // Toggle selection button this._toggleSelectionFind = this._register(new SimpleCheckbox({ - parent: findPart, + parent: actionsContainer, title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand), onChange: () => { if (this._toggleSelectionFind.checked) { @@ -906,38 +1036,45 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } })); - findPart.appendChild(this._closeBtn.domNode); + actionsContainer.appendChild(this._closeBtn.domNode); - return findPart; - } - - private _buildReplacePart(): HTMLElement { // Replace input - let replaceInput = document.createElement('div'); - replaceInput.className = 'replace-input'; - replaceInput.style.width = REPLACE_INPUT_AREA_WIDTH + 'px'; - this._replaceInputBox = this._register(new ContextScopedHistoryInputBox(replaceInput, undefined, { - ariaLabel: NLS_REPLACE_INPUT_LABEL, + this._replaceInput = this._register(new ContextScopedReplaceInput(null, undefined, { + label: NLS_REPLACE_INPUT_LABEL, placeholder: NLS_REPLACE_INPUT_PLACEHOLDER, - history: [] - }, this._contextKeyService)); - - - this._register(dom.addStandardDisposableListener(this._replaceInputBox.inputElement, 'keydown', (e) => this._onReplaceInputKeyDown(e))); - this._register(this._replaceInputBox.onDidChange(() => { - this._state.change({ replaceString: this._replaceInputBox.value }, false); + history: [], + flexibleHeight, + flexibleWidth, + flexibleMaxHeight: 118 + }, this._contextKeyService, true)); + this._replaceInput.setPreserveCase(!!this._state.preserveCase); + this._register(this._replaceInput.onKeyDown((e) => this._onReplaceInputKeyDown(e))); + this._register(this._replaceInput.inputBox.onDidChange(() => { + this._state.change({ replaceString: this._replaceInput.inputBox.value }, false); })); - - this._preserveCase = this._register(new Checkbox({ - actionClassName: 'monaco-preserve-case', - title: NLS_PRESERVE_CASE_LABEL, - isChecked: false, + this._register(this._replaceInput.inputBox.onDidHeightChange((e) => { + if (this._isReplaceVisible && this._tryUpdateHeight()) { + this._showViewZone(); + } })); - this._preserveCase.checked = !!this._state.preserveCase; - this._register(this._preserveCase.onChange(viaKeyboard => { - if (!viaKeyboard) { - this._state.change({ preserveCase: !this._state.preserveCase }, false); - this._replaceInputBox.focus(); + this._register(this._replaceInput.onDidOptionChange(() => { + this._state.change({ + preserveCase: this._replaceInput.getPreserveCase() + }, true); + })); + this._register(this._replaceInput.onPreserveCaseKeyDown((e) => { + if (e.equals(KeyCode.Tab)) { + if (this._prevBtn.isEnabled()) { + this._prevBtn.focus(); + } else if (this._nextBtn.isEnabled()) { + this._nextBtn.focus(); + } else if (this._toggleSelectionFind.isEnabled()) { + this._toggleSelectionFind.focus(); + } else if (this._closeBtn.isEnabled()) { + this._closeBtn.focus(); + } + + e.preventDefault(); } })); @@ -965,27 +1102,16 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } })); - let controls = document.createElement('div'); - controls.className = 'controls'; - controls.style.display = 'block'; - controls.appendChild(this._preserveCase.domNode); - replaceInput.appendChild(controls); - let replacePart = document.createElement('div'); replacePart.className = 'replace-part'; - replacePart.appendChild(replaceInput); - replacePart.appendChild(this._replaceBtn.domNode); - replacePart.appendChild(this._replaceAllBtn.domNode); + replacePart.appendChild(this._replaceInput.domNode); - return replacePart; - } + const replaceActionsContainer = document.createElement('div'); + replaceActionsContainer.className = 'replace-actions'; + replacePart.appendChild(replaceActionsContainer); - private _buildDomNode(): void { - // Find part - let findPart = this._buildFindPart(); - - // Replace part - let replacePart = this._buildReplacePart(); + replaceActionsContainer.appendChild(this._replaceBtn.domNode); + replaceActionsContainer.appendChild(this._replaceAllBtn.domNode); // Toggle replace button this._toggleReplaceBtn = this._register(new SimpleButton({ @@ -994,7 +1120,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas onTrigger: () => { this._state.change({ isReplaceRevealed: !this._isReplaceVisible }, false); if (this._isReplaceVisible) { - this._replaceInputBox.width = this._findInput.inputBox.width; + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); + this._replaceInput.inputBox.layout(); } this._showViewZone(); } @@ -1014,10 +1141,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._domNode.appendChild(findPart); this._domNode.appendChild(replacePart); - this._buildSash(); - } - - private _buildSash() { this._resizeSash = new Sash(this._domNode, this, { orientation: Orientation.VERTICAL }); this._resized = false; let originalWidth = FIND_WIDGET_INITIAL_WIDTH; @@ -1041,9 +1164,13 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas return; } this._domNode.style.width = `${width}px`; + this._findInput.inputBox.width = inputBoxWidth; if (this._isReplaceVisible) { - this._replaceInputBox.width = inputBoxWidth; + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); } + + this._findInput.inputBox.layout(); + this._tryUpdateHeight(); })); } @@ -1103,6 +1230,10 @@ class SimpleCheckbox extends Widget { return this._domNode; } + public isEnabled(): boolean { + return (this._domNode.tabIndex >= 0); + } + public get checked(): boolean { return this._checkbox.checked; } @@ -1112,7 +1243,7 @@ class SimpleCheckbox extends Widget { } public focus(): void { - this._checkbox.focus(); + this._domNode.focus(); } private enable(): void { diff --git a/src/vs/editor/contrib/find/replaceAllCommand.ts b/src/vs/editor/contrib/find/replaceAllCommand.ts index 1b542d6cb0b..bdf2942f014 100644 --- a/src/vs/editor/contrib/find/replaceAllCommand.ts +++ b/src/vs/editor/contrib/find/replaceAllCommand.ts @@ -16,7 +16,7 @@ interface IEditOperation { export class ReplaceAllCommand implements editorCommon.ICommand { private readonly _editorSelection: Selection; - private _trackedEditorSelectionId: string; + private _trackedEditorSelectionId: string | null; private readonly _ranges: Range[]; private readonly _replaceStrings: string[]; @@ -24,6 +24,7 @@ export class ReplaceAllCommand implements editorCommon.ICommand { this._editorSelection = editorSelection; this._ranges = ranges; this._replaceStrings = replaceStrings; + this._trackedEditorSelectionId = null; } public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { @@ -66,6 +67,6 @@ export class ReplaceAllCommand implements editorCommon.ICommand { } public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this._trackedEditorSelectionId); + return helper.getTrackedSelection(this._trackedEditorSelectionId!); } } diff --git a/src/vs/editor/contrib/find/replacePattern.ts b/src/vs/editor/contrib/find/replacePattern.ts index 50e90d7f3ea..12a05d93536 100644 --- a/src/vs/editor/contrib/find/replacePattern.ts +++ b/src/vs/editor/contrib/find/replacePattern.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from 'vs/base/common/charCode'; -import { containsUppercaseCharacter } from 'vs/base/common/strings'; +import { buildReplaceStringWithCasePreserved } from 'vs/base/common/search'; const enum ReplacePatternKind { StaticValue = 0, @@ -51,17 +51,8 @@ export class ReplacePattern { public buildReplaceString(matches: string[] | null, preserveCase?: boolean): string { if (this._state.kind === ReplacePatternKind.StaticValue) { - if (preserveCase && matches && (matches[0] !== '')) { - if (matches[0].toUpperCase() === matches[0]) { - return this._state.staticValue.toUpperCase(); - } else if (matches[0].toLowerCase() === matches[0]) { - return this._state.staticValue.toLowerCase(); - } else if (containsUppercaseCharacter(matches[0][0])) { - return this._state.staticValue[0].toUpperCase() + this._state.staticValue.substr(1); - } else { - // we don't understand its pattern yet. - return this._state.staticValue; - } + if (preserveCase) { + return buildReplaceStringWithCasePreserved(matches, this._state.staticValue); } else { return this._state.staticValue; } diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 5bd9bb3a072..6ac1767a275 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -24,7 +24,6 @@ export class TestFindController extends CommonFindController { public hasFocus: boolean; public delayUpdateHistory: boolean = false; - public delayedUpdateHistoryPromise: Promise; private _findInputFocused: IContextKey; @@ -37,6 +36,7 @@ export class TestFindController extends CommonFindController { super(editor, contextKeyService, storageService, clipboardService); this._findInputFocused = CONTEXT_FIND_INPUT_FOCUSED.bindTo(contextKeyService); this._updateHistoryDelayer = new Delayer(50); + this.hasFocus = false; } protected _start(opts: IFindStartOptions): void { diff --git a/src/vs/editor/contrib/find/test/replacePattern.test.ts b/src/vs/editor/contrib/find/test/replacePattern.test.ts index d252ac74c6a..e4c78422b8f 100644 --- a/src/vs/editor/contrib/find/test/replacePattern.test.ts +++ b/src/vs/editor/contrib/find/test/replacePattern.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { ReplacePattern, ReplacePiece, parseReplaceString } from 'vs/editor/contrib/find/replacePattern'; +import { buildReplaceStringWithCasePreserved } from 'vs/base/common/search'; suite('Replace Pattern test', () => { @@ -154,6 +155,29 @@ suite('Replace Pattern test', () => { assert.equal(actual, 'a{}'); }); + test('buildReplaceStringWithCasePreserved test', () => { + let replacePattern = 'Def'; + let actual: string | string[] = 'abc'; + + assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'def'); + actual = 'Abc'; + assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'Def'); + actual = 'ABC'; + assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'DEF'); + + actual = ['abc', 'Abc']; + assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'def'); + actual = ['Abc', 'abc']; + assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); + actual = ['ABC', 'abc']; + assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'DEF'); + + actual = ['AbC']; + assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); + actual = ['aBC']; + assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def'); + }); + test('preserve case', () => { let replacePattern = parseReplaceString('Def'); let actual = replacePattern.buildReplaceString(['abc'], true); diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 9a9ea25c545..cd7cdbe1d13 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -79,6 +79,7 @@ export class FoldingController extends Disposable implements IEditorContribution private cursorChangedScheduler: RunOnceScheduler | null; private readonly localToDispose = this._register(new DisposableStore()); + private mouseDownInfo: { lineNumber: number, iconClicked: boolean } | null; constructor( editor: ICodeEditor, @@ -90,6 +91,15 @@ export class FoldingController extends Disposable implements IEditorContribution this._autoHideFoldingControls = this.editor.getConfiguration().contribInfo.showFoldingControls === 'mouseover'; this._useFoldingProviders = this.editor.getConfiguration().contribInfo.foldingStrategy !== 'indentation'; + this.foldingModel = null; + this.hiddenRangeModel = null; + this.rangeProvider = null; + this.foldingRegionPromise = null; + this.foldingStateMemento = null; + this.foldingModelPromise = null; + this.updateScheduler = null; + this.cursorChangedScheduler = null; + this.mouseDownInfo = null; this.foldingDecorationProvider = new FoldingDecorationProvider(editor); this.foldingDecorationProvider.autoHideFoldingControls = this._autoHideFoldingControls; @@ -331,8 +341,6 @@ export class FoldingController extends Disposable implements IEditorContribution } - private mouseDownInfo: { lineNumber: number, iconClicked: boolean } | null; - private onEditorMouseDown(e: IEditorMouseEvent): void { this.mouseDownInfo = null; diff --git a/src/vs/editor/contrib/folding/foldingRanges.ts b/src/vs/editor/contrib/folding/foldingRanges.ts index e8a7cd7adef..1c2e0d1fc74 100644 --- a/src/vs/editor/contrib/folding/foldingRanges.ts +++ b/src/vs/editor/contrib/folding/foldingRanges.ts @@ -28,6 +28,7 @@ export class FoldingRegions { this._endIndexes = endIndexes; this._collapseStates = new Uint32Array(Math.ceil(startIndexes.length / 32)); this._types = types; + this._parentsComputed = false; } private ensureParentIndices() { @@ -186,4 +187,4 @@ export class FoldingRegion { hidesLine(lineNumber: number) { return this.startLineNumber < lineNumber && lineNumber <= this.endLineNumber; } -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/folding/indentRangeProvider.ts b/src/vs/editor/contrib/folding/indentRangeProvider.ts index f13ed98e9af..68a3a366faa 100644 --- a/src/vs/editor/contrib/folding/indentRangeProvider.ts +++ b/src/vs/editor/contrib/folding/indentRangeProvider.ts @@ -105,7 +105,11 @@ export class RangesCollector { } -interface PreviousRegion { indent: number; line: number; marker: boolean; } +interface PreviousRegion { + indent: number; // indent or -2 if a marker + endAbove: number; // end line number for the region above + line: number; // start line of the region. Only used for marker regions. +} export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldingMarkers, foldingRangesLimit = MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT): FoldingRegions { const tabSize = model.getOptions().tabSize; @@ -117,16 +121,19 @@ export function computeRanges(model: ITextModel, offSide: boolean, markers?: Fol } let previousRegions: PreviousRegion[] = []; - previousRegions.push({ indent: -1, line: model.getLineCount() + 1, marker: false }); // sentinel, to make sure there's at least one entry + let line = model.getLineCount() + 1; + previousRegions.push({ indent: -1, endAbove: line, line }); // sentinel, to make sure there's at least one entry for (let line = model.getLineCount(); line > 0; line--) { let lineContent = model.getLineContent(line); let indent = TextModel.computeIndentLevel(lineContent, tabSize); let previous = previousRegions[previousRegions.length - 1]; if (indent === -1) { - if (offSide && !previous.marker) { - // for offSide languages, empty lines are associated to the next block - previous.line = line; + if (offSide) { + // for offSide languages, empty lines are associated to the previous block + // note: the next block is already written to the results, so this only + // impacts the end position of the block before + previous.endAbove = line; } continue; // only whitespace } @@ -136,7 +143,7 @@ export function computeRanges(model: ITextModel, offSide: boolean, markers?: Fol if (m[1]) { // start pattern match // discard all regions until the folding pattern let i = previousRegions.length - 1; - while (i > 0 && !previousRegions[i].marker) { + while (i > 0 && previousRegions[i].indent !== -2) { i--; } if (i > 0) { @@ -145,15 +152,15 @@ export function computeRanges(model: ITextModel, offSide: boolean, markers?: Fol // new folding range from pattern, includes the end line result.insertFirst(line, previous.line, indent); - previous.marker = false; - previous.indent = indent; previous.line = line; + previous.indent = indent; + previous.endAbove = line; continue; } else { // no end marker found, treat line as a regular line } } else { // end pattern match - previousRegions.push({ indent: -2, line, marker: true }); + previousRegions.push({ indent: -2, endAbove: line, line }); continue; } } @@ -165,16 +172,16 @@ export function computeRanges(model: ITextModel, offSide: boolean, markers?: Fol } while (previous.indent > indent); // new folding range - let endLineNumber = previous.line - 1; + let endLineNumber = previous.endAbove - 1; if (endLineNumber - line >= 1) { // needs at east size 1 result.insertFirst(line, endLineNumber, indent); } } if (previous.indent === indent) { - previous.line = line; + previous.endAbove = line; } else { // previous.indent < indent // new region with a bigger indent - previousRegions.push({ indent, line, marker: false }); + previousRegions.push({ indent, endAbove: line, line }); } } return result.toIndentRanges(model); diff --git a/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts b/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts index 882c097104d..07e20256667 100644 --- a/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts +++ b/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts @@ -316,5 +316,17 @@ suite('Folding with regions', () => { /* 8*/ '#endregionff', ], [], true, markers); }); - + test('Issue 79359', () => { + assertRanges([ + /* 1*/ '#region', + /* 2*/ '', + /* 3*/ 'class A', + /* 4*/ ' foo', + /* 5*/ '', + /* 6*/ 'class A', + /* 7*/ ' foo', + /* 8*/ '', + /* 9*/ '#endregion', + ], [r(1, 9, -1, true), r(3, 4, 0), r(6, 7, 0)], true, markers); + }); }); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts index 717c7ba4d13..7d0dd7ce4e1 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts @@ -34,18 +34,16 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC private readonly editor: ICodeEditor; private readonly toUnhook = new DisposableStore(); - private decorations: string[]; - private currentWordUnderMouse: IWordAtPosition | null; - private previousPromise: CancelablePromise | null; + private decorations: string[] = []; + private currentWordUnderMouse: IWordAtPosition | null = null; + private previousPromise: CancelablePromise | null = null; constructor( editor: ICodeEditor, @ITextModelService private readonly textModelResolverService: ITextModelService, @IModeService private readonly modeService: IModeService ) { - this.decorations = []; this.editor = editor; - this.previousPromise = null; let linkGesture = new ClickLinkGesture(editor); this.toUnhook.add(linkGesture); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 7d93dc2fb37..9da6b13042f 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -165,15 +165,15 @@ class MessageWidget { export class MarkerNavigationWidget extends PeekViewWidget { - private _parentContainer: HTMLElement; - private _container: HTMLElement; - private _icon: HTMLElement; - private _message: MessageWidget; + private _parentContainer!: HTMLElement; + private _container!: HTMLElement; + private _icon!: HTMLElement; + private _message!: MessageWidget; private readonly _callOnDispose = new DisposableStore(); private _severity: MarkerSeverity; private _backgroundColor?: Color; private _onDidSelectRelatedInformation = new Emitter(); - private _heightInPixel: number; + private _heightInPixel!: number; readonly onDidSelectRelatedInformation: Event = this._onDidSelectRelatedInformation.event; @@ -228,7 +228,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { protected _fillHead(container: HTMLElement): void { super._fillHead(container); - this._actionbarWidget.push(this.actions, { label: false, icon: true }); + this._actionbarWidget!.push(this.actions, { label: false, icon: true }); } protected _fillTitleIcon(container: HTMLElement): void { diff --git a/src/vs/editor/contrib/hover/hover.css b/src/vs/editor/contrib/hover/hover.css index 62da9b51907..06639005192 100644 --- a/src/vs/editor/contrib/hover/hover.css +++ b/src/vs/editor/contrib/hover/hover.css @@ -84,13 +84,14 @@ .monaco-editor-hover .hover-row.status-bar .actions { display: flex; + padding: 0px 8px; } .monaco-editor-hover .hover-row.status-bar .actions .action-container { - margin: 0px 8px; + margin-right: 16px; cursor: pointer; } .monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon { padding-right: 4px; -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 8dcf20dd2b8..583e4c65dee 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -34,27 +34,27 @@ export class ModesHoverController implements IEditorContribution { private readonly _toUnhook = new DisposableStore(); private readonly _didChangeConfigurationHandler: IDisposable; - private _contentWidget: ModesContentHoverWidget; - private _glyphWidget: ModesGlyphHoverWidget; + private _contentWidget: ModesContentHoverWidget | null; + private _glyphWidget: ModesGlyphHoverWidget | null; get contentWidget(): ModesContentHoverWidget { if (!this._contentWidget) { - this._createHoverWidget(); + this._createHoverWidgets(); } - return this._contentWidget; + return this._contentWidget!; } get glyphWidget(): ModesGlyphHoverWidget { if (!this._glyphWidget) { - this._createHoverWidget(); + this._createHoverWidgets(); } - return this._glyphWidget; + return this._glyphWidget!; } private _isMouseDown: boolean; private _hoverClicked: boolean; - private _isHoverEnabled: boolean; - private _isHoverSticky: boolean; + private _isHoverEnabled!: boolean; + private _isHoverSticky!: boolean; static get(editor: ICodeEditor): ModesHoverController { return editor.getContribution(ModesHoverController.ID); @@ -69,6 +69,8 @@ export class ModesHoverController implements IEditorContribution { ) { this._isMouseDown = false; this._hoverClicked = false; + this._contentWidget = null; + this._glyphWidget = null; this._hookEvents(); @@ -196,15 +198,15 @@ export class ModesHoverController implements IEditorContribution { } private _hideWidgets(): void { - if (!this._contentWidget || (this._isMouseDown && this._hoverClicked && this._contentWidget.isColorPickerVisible())) { + if (!this._glyphWidget || !this._contentWidget || (this._isMouseDown && this._hoverClicked && this._contentWidget.isColorPickerVisible())) { return; } - this._glyphWidget.hide(); + this._glyphWidget!.hide(); this._contentWidget.hide(); } - private _createHoverWidget() { + private _createHoverWidgets() { this._contentWidget = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService); this._glyphWidget = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); } diff --git a/src/vs/editor/contrib/hover/hoverWidgets.ts b/src/vs/editor/contrib/hover/hoverWidgets.ts index 6489db5a9fa..13dff650038 100644 --- a/src/vs/editor/contrib/hover/hoverWidgets.ts +++ b/src/vs/editor/contrib/hover/hoverWidgets.ts @@ -42,6 +42,7 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent this._id = id; this._editor = editor; this._isVisible = false; + this._stoleFocus = false; this._containerDomNode = document.createElement('div'); this._containerDomNode.className = 'monaco-editor-hover hidden'; @@ -72,6 +73,7 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent this._editor.addContentWidget(this); this._showAtPosition = null; this._showAtRange = null; + this._stoleFocus = false; } public getId(): string { diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 7bc5a7fb022..d42f5153a1d 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -70,6 +70,7 @@ class ModesContentComputer implements IHoverComputer { private readonly _markerDecorationsService: IMarkerDecorationsService ) { this._editor = editor; + this._result = []; } setRange(range: Range): void { @@ -213,6 +214,8 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._computer = new ModesContentComputer(this._editor, markerDecorationsService); this._highlightDecorations = []; this._isChangingDecorations = false; + this._shouldFocus = false; + this._colorPicker = null; this._hoverOperation = new HoverOperation( this._computer, diff --git a/src/vs/editor/contrib/hover/modesGlyphHover.ts b/src/vs/editor/contrib/hover/modesGlyphHover.ts index b2b78438a79..85dece77b64 100644 --- a/src/vs/editor/contrib/hover/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/modesGlyphHover.ts @@ -27,6 +27,7 @@ class MarginComputer implements IHoverComputer { constructor(editor: ICodeEditor) { this._editor = editor; this._lineNumber = -1; + this._result = []; } public setLineNumber(lineNumber: number): void { @@ -100,6 +101,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { ) { super(ModesGlyphHoverWidget.ID, editor); + this._messages = []; this._lastLineNumber = -1; this._markdownRenderer = this._register(new MarkdownRenderer(this._editor, modeService, openerService)); diff --git a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts index 195d83b849c..fce6fc23383 100644 --- a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts +++ b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts @@ -37,8 +37,8 @@ class InPlaceReplaceController implements IEditorContribution { private readonly editor: ICodeEditor; private readonly editorWorkerService: IEditorWorkerService; private decorationIds: string[] = []; - private currentRequest: CancelablePromise; - private decorationRemover: CancelablePromise; + private currentRequest?: CancelablePromise; + private decorationRemover?: CancelablePromise; constructor( editor: ICodeEditor, diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index dc51d0faf45..fd9fcee54ea 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -378,11 +378,12 @@ export class AutoIndentOnPasteCommand implements ICommand { private readonly _edits: { range: IRange; text: string; eol?: EndOfLineSequence; }[]; private readonly _initialSelection: Selection; - private _selectionId: string; + private _selectionId: string | null; constructor(edits: TextEdit[], initialSelection: Selection) { this._initialSelection = initialSelection; this._edits = []; + this._selectionId = null; for (let edit of edits) { if (edit.range && typeof edit.text === 'string') { @@ -415,7 +416,7 @@ export class AutoIndentOnPasteCommand implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this._selectionId); + return helper.getTrackedSelection(this._selectionId!); } } @@ -651,7 +652,7 @@ function getIndentationEditOperations(model: ITextModel, builder: IEditOperation export class IndentationToSpacesCommand implements ICommand { - private selectionId: string; + private selectionId: string | null = null; constructor(private readonly selection: Selection, private tabSize: number) { } @@ -661,13 +662,13 @@ export class IndentationToSpacesCommand implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this.selectionId); + return helper.getTrackedSelection(this.selectionId!); } } export class IndentationToTabsCommand implements ICommand { - private selectionId: string; + private selectionId: string | null = null; constructor(private readonly selection: Selection, private tabSize: number) { } @@ -677,7 +678,7 @@ export class IndentationToTabsCommand implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this.selectionId); + return helper.getTrackedSelection(this.selectionId!); } } @@ -688,4 +689,4 @@ registerEditorAction(IndentUsingTabs); registerEditorAction(IndentUsingSpaces); registerEditorAction(DetectIndentation); registerEditorAction(ReindentLinesAction); -registerEditorAction(ReindentSelectedLinesAction); \ No newline at end of file +registerEditorAction(ReindentSelectedLinesAction); diff --git a/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts b/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts index 799b61088f2..3d207820061 100644 --- a/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts @@ -14,13 +14,17 @@ export class CopyLinesCommand implements editorCommon.ICommand { private readonly _isCopyingDown: boolean; private _selectionDirection: SelectionDirection; - private _selectionId: string; + private _selectionId: string | null; private _startLineNumberDelta: number; private _endLineNumberDelta: number; constructor(selection: Selection, isCopyingDown: boolean) { this._selection = selection; this._isCopyingDown = isCopyingDown; + this._selectionDirection = SelectionDirection.LTR; + this._selectionId = null; + this._startLineNumberDelta = 0; + this._endLineNumberDelta = 0; } public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { @@ -58,7 +62,7 @@ export class CopyLinesCommand implements editorCommon.ICommand { } public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { - let result = helper.getTrackedSelection(this._selectionId); + let result = helper.getTrackedSelection(this._selectionId!); if (this._startLineNumberDelta !== 0 || this._endLineNumberDelta !== 0) { let startLineNumber = result.startLineNumber; diff --git a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts index 1053f758bdd..f1b0fd7befb 100644 --- a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts @@ -20,7 +20,7 @@ export class MoveLinesCommand implements ICommand { private readonly _isMovingDown: boolean; private readonly _autoIndent: boolean; - private _selectionId: string; + private _selectionId: string | null; private _moveEndPositionDown?: boolean; private _moveEndLineSelectionShrink: boolean; @@ -28,6 +28,7 @@ export class MoveLinesCommand implements ICommand { this._selection = selection; this._isMovingDown = isMovingDown; this._autoIndent = autoIndent; + this._selectionId = null; this._moveEndLineSelectionShrink = false; } @@ -36,9 +37,11 @@ export class MoveLinesCommand implements ICommand { let modelLineCount = model.getLineCount(); if (this._isMovingDown && this._selection.endLineNumber === modelLineCount) { + this._selectionId = builder.trackSelection(this._selection); return; } if (!this._isMovingDown && this._selection.startLineNumber === 1) { + this._selectionId = builder.trackSelection(this._selection); return; } @@ -328,7 +331,7 @@ export class MoveLinesCommand implements ICommand { } public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - let result = helper.getTrackedSelection(this._selectionId); + let result = helper.getTrackedSelection(this._selectionId!); if (this._moveEndPositionDown) { result = result.setEndPosition(result.endLineNumber + 1, 1); diff --git a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts index 304084db5f2..6c52081947c 100644 --- a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts @@ -12,12 +12,13 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod export class SortLinesCommand implements editorCommon.ICommand { private readonly selection: Selection; - private selectionId: string; private readonly descending: boolean; + private selectionId: string | null; constructor(selection: Selection, descending: boolean) { this.selection = selection; this.descending = descending; + this.selectionId = null; } public getEditOperations(model: ITextModel, builder: editorCommon.IEditOperationBuilder): void { @@ -30,7 +31,7 @@ export class SortLinesCommand implements editorCommon.ICommand { } public computeCursorState(model: ITextModel, helper: editorCommon.ICursorStateComputerData): Selection { - return helper.getTrackedSelection(this.selectionId); + return helper.getTrackedSelection(this.selectionId!); } public static canRun(model: ITextModel | null, selection: Selection, descending: boolean): boolean { diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 53ea1177390..f9f19e5211c 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -299,10 +299,12 @@ class LinkDetector implements editorCommon.IEditorContribution { return this.openerService.open(uri, { openToSide }); }, err => { + const messageOrError = + err instanceof Error ? (err).message : err; // different error cases - if (err === 'invalid') { + if (messageOrError === 'invalid') { 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') { + } else if (messageOrError === 'missing') { this.notificationService.warn(nls.localize('missing.url', 'Failed to open this link because its target is missing.')); } else { onUnexpectedError(err); diff --git a/src/vs/editor/contrib/markdown/markdownRenderer.ts b/src/vs/editor/contrib/markdown/markdownRenderer.ts index 5825a919bf5..81f0b2ccc87 100644 --- a/src/vs/editor/contrib/markdown/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdown/markdownRenderer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { renderMarkdown, RenderOptions } from 'vs/base/browser/htmlContentRenderer'; +import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer'; import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; import { IModeService } from 'vs/editor/common/services/modeService'; import { URI } from 'vs/base/common/uri'; @@ -33,7 +33,7 @@ export class MarkdownRenderer extends Disposable { super(); } - private getOptions(disposeables: DisposableStore): RenderOptions { + private getOptions(disposeables: DisposableStore): MarkdownRenderOptions { return { codeBlockRenderer: (languageAlias, value) => { // In markdown, diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index 625740080a7..dff7f77f9e4 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -45,6 +45,10 @@ white-space: pre-wrap; } +.monaco-editor .parameter-hints-widget .docs.empty { + display: none; +} + .monaco-editor .parameter-hints-widget .docs .markdown-docs { white-space: initial; } diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 53bf31abcc5..0efd0207cb6 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -206,8 +206,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, dom.append(this.docs, $('p', {}, documentation)); } - dom.toggleClass(this.signature, 'has-docs', !!signature.documentation); - if (signature.documentation === undefined) { /** no op */ } else if (typeof signature.documentation === 'string') { dom.append(this.docs, $('p', {}, signature.documentation)); @@ -218,6 +216,23 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, dom.append(this.docs, renderedContents.element); } + let hasDocs = false; + if (activeParameter && typeof (activeParameter.documentation) === 'string' && activeParameter.documentation.length > 0) { + hasDocs = true; + } + if (activeParameter && typeof (activeParameter.documentation) === 'object' && activeParameter.documentation.value.length > 0) { + hasDocs = true; + } + if (typeof (signature.documentation) === 'string' && signature.documentation.length > 0) { + hasDocs = true; + } + if (typeof (signature.documentation) === 'object' && signature.documentation.value.length > 0) { + hasDocs = true; + } + + dom.toggleClass(this.signature, 'has-docs', hasDocs); + dom.toggleClass(this.docs, 'empty', !hasDocs); + let currentOverload = String(hints.activeSignature + 1); if (hints.signatures.length < 10) { diff --git a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts index c40368e0311..4dd63cadd03 100644 --- a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts @@ -84,12 +84,12 @@ export abstract class PeekViewWidget extends ZoneWidget { private _onDidClose = new Emitter(); - protected _headElement: HTMLDivElement; - protected _primaryHeading: HTMLElement; - protected _secondaryHeading: HTMLElement; - protected _metaHeading: HTMLElement; - protected _actionbarWidget: ActionBar; - protected _bodyElement: HTMLDivElement; + protected _headElement?: HTMLDivElement; + protected _primaryHeading?: HTMLElement; + protected _secondaryHeading?: HTMLElement; + protected _metaHeading?: HTMLElement; + protected _actionbarWidget?: ActionBar; + protected _bodyElement?: HTMLDivElement; constructor(editor: ICodeEditor, options: IPeekViewOptions = {}) { super(editor, options); @@ -139,8 +139,8 @@ export abstract class PeekViewWidget extends ZoneWidget { protected _fillContainer(container: HTMLElement): void { this.setCssClass('peekview-widget'); - this._headElement = dom.$('.head'); - this._bodyElement = dom.$('.body'); + this._headElement = dom.$('.head'); + this._bodyElement = dom.$('.body'); this._fillHead(this._headElement); this._fillBody(this._bodyElement); @@ -151,7 +151,7 @@ export abstract class PeekViewWidget extends ZoneWidget { protected _fillHead(container: HTMLElement): void { const titleElement = dom.$('.peekview-title'); - dom.append(this._headElement, titleElement); + dom.append(this._headElement!, titleElement); dom.addStandardDisposableListener(titleElement, 'click', event => this._onTitleClick(event)); this._fillTitleIcon(titleElement); @@ -161,7 +161,7 @@ export abstract class PeekViewWidget extends ZoneWidget { dom.append(titleElement, this._primaryHeading, this._secondaryHeading, this._metaHeading); const actionsContainer = dom.$('.peekview-actions'); - dom.append(this._headElement, actionsContainer); + dom.append(this._headElement!, actionsContainer); const actionBarOptions = this._getActionBarOptions(); this._actionbarWidget = new ActionBar(actionsContainer, actionBarOptions); @@ -185,20 +185,24 @@ export abstract class PeekViewWidget extends ZoneWidget { } public setTitle(primaryHeading: string, secondaryHeading?: string): void { - this._primaryHeading.innerHTML = strings.escape(primaryHeading); - this._primaryHeading.setAttribute('aria-label', primaryHeading); - if (secondaryHeading) { - this._secondaryHeading.innerHTML = strings.escape(secondaryHeading); - } else { - dom.clearNode(this._secondaryHeading); + if (this._primaryHeading && this._secondaryHeading) { + this._primaryHeading.innerHTML = strings.escape(primaryHeading); + this._primaryHeading.setAttribute('aria-label', primaryHeading); + if (secondaryHeading) { + this._secondaryHeading.innerHTML = strings.escape(secondaryHeading); + } else { + dom.clearNode(this._secondaryHeading); + } } } public setMetaTitle(value: string): void { - if (value) { - this._metaHeading.innerHTML = strings.escape(value); - } else { - dom.clearNode(this._metaHeading); + if (this._metaHeading) { + if (value) { + this._metaHeading.innerHTML = strings.escape(value); + } else { + dom.clearNode(this._metaHeading); + } } } @@ -220,11 +224,15 @@ export abstract class PeekViewWidget extends ZoneWidget { } protected _doLayoutHead(heightInPixel: number, widthInPixel: number): void { - this._headElement.style.height = `${heightInPixel}px`; - this._headElement.style.lineHeight = this._headElement.style.height; + if (this._headElement) { + this._headElement.style.height = `${heightInPixel}px`; + this._headElement.style.lineHeight = this._headElement.style.height; + } } protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void { - this._bodyElement.style.height = `${heightInPixel}px`; + if (this._bodyElement) { + this._bodyElement.style.height = `${heightInPixel}px`; + } } } diff --git a/src/vs/editor/contrib/referenceSearch/referencesController.ts b/src/vs/editor/contrib/referenceSearch/referencesController.ts index 0f756a44550..6f0dcdff9de 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesController.ts @@ -34,8 +34,8 @@ export abstract class ReferencesController implements editorCommon.IEditorContri private readonly _disposables = new DisposableStore(); private readonly _editor: ICodeEditor; - private _widget: ReferenceWidget | null; - private _model: ReferencesModel | null; + private _widget?: ReferenceWidget; + private _model?: ReferencesModel; private _requestIdPool = 0; private _ignoreModelChangeEvent = false; @@ -68,11 +68,11 @@ export abstract class ReferencesController implements editorCommon.IEditorContri dispose(this._disposables); if (this._widget) { dispose(this._widget); - this._widget = null; + this._widget = undefined; } if (this._model) { dispose(this._model); - this._model = null; + this._model = undefined; } } @@ -107,7 +107,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri modelPromise.cancel(); if (this._widget) { this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.GLOBAL); - this._widget = null; + this._widget = undefined; } this.closeWidget(); })); @@ -202,13 +202,13 @@ export abstract class ReferencesController implements editorCommon.IEditorContri public closeWidget(): void { if (this._widget) { dispose(this._widget); - this._widget = null; + this._widget = undefined; } this._referenceSearchVisible.reset(); this._disposables.clear(); if (this._model) { dispose(this._model); - this._model = null; + this._model = undefined; } this._editor.focus(); this._requestIdPool += 1; // Cancel pending requests diff --git a/src/vs/editor/contrib/referenceSearch/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/referencesModel.ts index 591092048d2..35af7095f8b 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesModel.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesModel.ts @@ -89,7 +89,7 @@ export class FileReferences implements IDisposable { private _children: OneReference[]; private _preview?: FilePreview; - private _resolved: boolean; + private _resolved?: boolean; private _loadFailure: any; constructor(private readonly _parent: ReferencesModel, private readonly _uri: URI) { diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index 20fc37f552e..67c49b7cdc1 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -158,8 +158,8 @@ class DecorationsManager implements IDisposable { } export class LayoutData { - ratio: number; - heightInLines: number; + ratio: number = 0.7; + heightInLines: number = 18; static fromJSON(raw: string): LayoutData { let ratio: number | undefined; @@ -191,22 +191,21 @@ export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey('r */ export class ReferenceWidget extends PeekViewWidget { - private _model: ReferencesModel | undefined; - private _decorationsManager: DecorationsManager; + private _model?: ReferencesModel; + private _decorationsManager?: DecorationsManager; private readonly _disposeOnNewModel = new DisposableStore(); private readonly _callOnDispose = new DisposableStore(); private _onDidSelectReference = new Emitter(); - private _tree: WorkbenchAsyncDataTree; - private _treeContainer: HTMLElement; - // private _sash: VSash; - private _splitView: SplitView; - private _preview: ICodeEditor; - private _previewModelReference: IReference; - private _previewNotAvailableMessage: TextModel; - private _previewContainer: HTMLElement; - private _messageContainer: HTMLElement; + private _tree!: WorkbenchAsyncDataTree; + private _treeContainer!: HTMLElement; + private _splitView!: SplitView; + private _preview!: ICodeEditor; + private _previewModelReference!: IReference; + private _previewNotAvailableMessage!: TextModel; + private _previewContainer!: HTMLElement; + private _messageContainer!: HTMLElement; private _dim: dom.Dimension = { height: 0, width: 0 }; constructor( @@ -470,7 +469,7 @@ export class ReferenceWidget extends PeekViewWidget { })); // make sure things are rendered - dom.addClass(this.container, 'results-loaded'); + dom.addClass(this.container!, 'results-loaded'); dom.show(this._treeContainer); dom.show(this._previewContainer); this._splitView.layout(this._dim.width); diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index 85e4c956911..c285d9707cd 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -19,10 +19,10 @@ export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameIn export class RenameInputField implements IContentWidget, IDisposable { private _editor: ICodeEditor; - private _position: Position; - private _domNode: HTMLElement; - private _inputField: HTMLInputElement; - private _visible: boolean; + private _position?: Position; + private _domNode?: HTMLElement; + private _inputField?: HTMLInputElement; + private _visible?: boolean; private readonly _visibleContextKey: IContextKey; private readonly _disposables = new DisposableStore(); @@ -95,7 +95,7 @@ export class RenameInputField implements IContentWidget, IDisposable { this._inputField.style.borderStyle = border ? 'solid' : 'none'; this._inputField.style.borderColor = border ? border.toString() : 'none'; - this._domNode.style.boxShadow = widgetShadowColor ? ` 0 2px 8px ${widgetShadowColor}` : null; + this._domNode!.style.boxShadow = widgetShadowColor ? ` 0 2px 8px ${widgetShadowColor}` : null; } private updateFont(): void { @@ -111,7 +111,7 @@ export class RenameInputField implements IContentWidget, IDisposable { public getPosition(): IContentWidgetPosition | null { return this._visible - ? { position: this._position, preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] } + ? { position: this._position!, preference: [ContentWidgetPositionPreference.BELOW, ContentWidgetPositionPreference.ABOVE] } : null; } @@ -133,10 +133,10 @@ export class RenameInputField implements IContentWidget, IDisposable { public getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number): Promise { this._position = new Position(where.startLineNumber, where.startColumn); - this._inputField.value = value; - this._inputField.setAttribute('selectionStart', selectionStart.toString()); - this._inputField.setAttribute('selectionEnd', selectionEnd.toString()); - this._inputField.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); + this._inputField!.value = value; + this._inputField!.setAttribute('selectionStart', selectionStart.toString()); + this._inputField!.setAttribute('selectionEnd', selectionEnd.toString()); + this._inputField!.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); const disposeOnDone = new DisposableStore(); const always = () => { @@ -154,7 +154,7 @@ export class RenameInputField implements IContentWidget, IDisposable { }; this._currentAcceptInput = () => { - if (this._inputField.value.trim().length === 0 || this._inputField.value === value) { + if (this._inputField!.value.trim().length === 0 || this._inputField!.value === value) { // empty or whitespace only or not changed this.cancelInput(true); return; @@ -162,7 +162,7 @@ export class RenameInputField implements IContentWidget, IDisposable { this._currentAcceptInput = null; this._currentCancelInput = null; - resolve(this._inputField.value); + resolve(this._inputField!.value); }; let onCursorChanged = () => { @@ -187,16 +187,16 @@ export class RenameInputField implements IContentWidget, IDisposable { } private _show(): void { - this._editor.revealLineInCenterIfOutsideViewport(this._position.lineNumber, ScrollType.Smooth); + this._editor.revealLineInCenterIfOutsideViewport(this._position!.lineNumber, ScrollType.Smooth); this._visible = true; this._visibleContextKey.set(true); this._editor.layoutContentWidget(this); setTimeout(() => { - this._inputField.focus(); - this._inputField.setSelectionRange( - parseInt(this._inputField.getAttribute('selectionStart')!), - parseInt(this._inputField.getAttribute('selectionEnd')!)); + this._inputField!.focus(); + this._inputField!.setSelectionRange( + parseInt(this._inputField!.getAttribute('selectionStart')!), + parseInt(this._inputField!.getAttribute('selectionEnd')!)); }, 100); } diff --git a/src/vs/editor/contrib/snippet/snippetController2.ts b/src/vs/editor/contrib/snippet/snippetController2.ts index c24659b44d2..cec786f46fe 100644 --- a/src/vs/editor/contrib/snippet/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/snippetController2.ts @@ -54,7 +54,7 @@ export class SnippetController2 implements IEditorContribution { private _session?: SnippetSession; private _snippetListener = new DisposableStore(); - private _modelVersionId: number; + private _modelVersionId: number = -1; private _currentChoice?: Choice; constructor( diff --git a/src/vs/editor/contrib/snippet/snippetParser.ts b/src/vs/editor/contrib/snippet/snippetParser.ts index 46d069cddbc..61e0fd2a802 100644 --- a/src/vs/editor/contrib/snippet/snippetParser.ts +++ b/src/vs/editor/contrib/snippet/snippetParser.ts @@ -56,12 +56,8 @@ export class Scanner { || (ch >= CharCode.A && ch <= CharCode.Z); } - value: string; - pos: number; - - constructor() { - this.text(''); - } + value: string = ''; + pos: number = 0; text(value: string) { this.value = value; @@ -135,7 +131,7 @@ export abstract class Marker { readonly _markerBrand: any; - public parent: Marker; + public parent!: Marker; protected _children: Marker[] = []; appendChild(child: Marker): this { @@ -219,7 +215,7 @@ export class Text extends Marker { } export abstract class TransformableMarker extends Marker { - public transform: Transform; + public transform?: Transform; } export class Placeholder extends TransformableMarker { @@ -314,7 +310,7 @@ export class Choice extends Marker { export class Transform extends Marker { - regexp: RegExp; + regexp: RegExp = new RegExp(''); resolve(value: string): string { const _this = this; @@ -590,8 +586,8 @@ export class SnippetParser { return value.replace(/\$|}|\\/g, '\\$&'); } - private _scanner = new Scanner(); - private _token: Token; + private _scanner: Scanner = new Scanner(); + private _token: Token = { type: TokenType.EOF, pos: 0, len: 0 }; text(value: string): string { return this.parse(value).toString(); @@ -671,16 +667,24 @@ export class SnippetParser { if (this._token.type === TokenType.EOF) { return false; } - let start = this._token; - while (this._token.type !== type) { + let res = ''; + let pos = this._token.pos; + let prevToken = { type: TokenType.EOF, pos: 0, len: 0 }; + + while (this._token.type !== type || prevToken.type === TokenType.Backslash) { + if (this._token.type === type) { + res += this._scanner.value.substring(pos, prevToken.pos); + pos = this._token.pos; + } + prevToken = this._token; this._token = this._scanner.next(); if (this._token.type === TokenType.EOF) { return false; } } - let value = this._scanner.value.substring(start.pos, this._token.pos); + res += this._scanner.value.substring(pos, this._token.pos); this._token = this._scanner.next(); - return value; + return res; } private _parse(marker: Marker): boolean { diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 6140e44858a..63aa8a230eb 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -41,7 +41,7 @@ export class OneSnippet { private readonly _snippet: TextmateSnippet; private readonly _offset: number; - private _placeholderDecorations: Map; + private _placeholderDecorations?: Map; private _placeholderGroups: Placeholder[][]; _placeholderGroupsIdx: number; _nestingLevel: number = 1; @@ -92,7 +92,7 @@ export class OneSnippet { ); const options = placeholder.isFinalTabstop ? OneSnippet._decor.inactiveFinal : OneSnippet._decor.inactive; const handle = accessor.addDecoration(range, options); - this._placeholderDecorations.set(placeholder, handle); + this._placeholderDecorations!.set(placeholder, handle); } }); } @@ -111,7 +111,7 @@ export class OneSnippet { for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) { // Check if the placeholder has a transformation if (placeholder.transform) { - const id = this._placeholderDecorations.get(placeholder)!; + const id = this._placeholderDecorations!.get(placeholder)!; const range = this._editor.getModel().getDecorationRange(id)!; const currentValue = this._editor.getModel().getValueInRange(range); @@ -148,7 +148,7 @@ export class OneSnippet { // Special case #2: placeholders enclosing active placeholders const selections: Selection[] = []; for (const placeholder of this._placeholderGroups[this._placeholderGroupsIdx]) { - const id = this._placeholderDecorations.get(placeholder)!; + const id = this._placeholderDecorations!.get(placeholder)!; const range = this._editor.getModel().getDecorationRange(id)!; selections.push(new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)); @@ -161,7 +161,7 @@ export class OneSnippet { activePlaceholders.add(placeholder); for (const enclosingPlaceholder of this._snippet.enclosingPlaceholders(placeholder)) { - const id = this._placeholderDecorations.get(enclosingPlaceholder)!; + const id = this._placeholderDecorations!.get(enclosingPlaceholder)!; accessor.changeDecorationOptions(id, enclosingPlaceholder.isFinalTabstop ? OneSnippet._decor.activeFinal : OneSnippet._decor.active); activePlaceholders.add(enclosingPlaceholder); } @@ -169,7 +169,7 @@ export class OneSnippet { // change stickness to never grow when typing at its edges // so that in-active tabstops never grow - this._placeholderDecorations.forEach((id, placeholder) => { + this._placeholderDecorations!.forEach((id, placeholder) => { if (!activePlaceholders.has(placeholder)) { accessor.changeDecorationOptions(id, placeholder.isFinalTabstop ? OneSnippet._decor.inactiveFinal : OneSnippet._decor.inactive); } @@ -188,7 +188,7 @@ export class OneSnippet { let marker: Marker | undefined = placeholder; while (marker) { if (marker instanceof Placeholder) { - const id = this._placeholderDecorations.get(marker)!; + const id = this._placeholderDecorations!.get(marker)!; const range = this._editor.getModel().getDecorationRange(id)!; if (range.isEmpty() && marker.toString().length > 0) { return true; @@ -227,7 +227,7 @@ export class OneSnippet { result.set(placeholder.index, ranges); } - const id = this._placeholderDecorations.get(placeholder)!; + const id = this._placeholderDecorations!.get(placeholder)!; const range = this._editor.getModel().getDecorationRange(id); if (!range) { // one of the placeholder lost its decoration and @@ -278,9 +278,9 @@ export class OneSnippet { // Remove the placeholder at which position are inserting // the snippet and also remove its decoration. - const id = this._placeholderDecorations.get(placeholder)!; + const id = this._placeholderDecorations!.get(placeholder)!; accessor.removeDecoration(id); - this._placeholderDecorations.delete(placeholder); + this._placeholderDecorations!.delete(placeholder); // For each *new* placeholder we create decoration to monitor // how and if it grows/shrinks. @@ -292,7 +292,7 @@ export class OneSnippet { model.getPositionAt(nested._offset + placeholderOffset + placeholderLen) ); const handle = accessor.addDecoration(range, OneSnippet._decor.inactive); - this._placeholderDecorations.set(placeholder, handle); + this._placeholderDecorations!.set(placeholder, handle); } } @@ -304,7 +304,7 @@ export class OneSnippet { public getEnclosingRange(): Range | undefined { let result: Range | undefined; const model = this._editor.getModel(); - this._placeholderDecorations.forEach((decorationId) => { + this._placeholderDecorations!.forEach((decorationId) => { const placeholderRange = withNullAsUndefined(model.getDecorationRange(decorationId)); if (!result) { result = placeholderRange; @@ -489,21 +489,18 @@ export class SnippetSession { return; } - const model = this._editor.getModel(); - // make insert edit and start with first selections const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, this._template, this._options.overwriteBefore, this._options.overwriteAfter, false, this._options.adjustWhitespace, this._options.clipboardText); this._snippets = snippets; - const selections = model.pushEditOperations(this._editor.getSelections(), edits, undoEdits => { + this._editor.executeEdits('snippet', edits, undoEdits => { if (this._snippets[0].hasPlaceholder) { return this._move(true); } else { return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition())); } - })!; - this._editor.setSelections(selections); - this._editor.revealRange(selections[0]); + }); + this._editor.revealRange(this._editor.getSelections()[0]); } merge(template: string, options: ISnippetSessionInsertOptions = _defaultOptions): void { @@ -513,8 +510,7 @@ export class SnippetSession { this._templateMerges.push([this._snippets[0]._nestingLevel, this._snippets[0]._placeholderGroupsIdx, template]); const { edits, snippets } = SnippetSession.createEditsAndSnippets(this._editor, template, options.overwriteBefore, options.overwriteAfter, true, options.adjustWhitespace, options.clipboardText); - this._editor.setSelections(this._editor.getModel().pushEditOperations(this._editor.getSelections(), edits, undoEdits => { - + this._editor.executeEdits('snippet', edits, undoEdits => { for (const snippet of this._snippets) { snippet.merge(snippets); } @@ -525,7 +521,7 @@ export class SnippetSession { } else { return undoEdits.map(edit => Selection.fromPositions(edit.range.getEndPosition())); } - })!); + }); } next(): void { diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index b56f15882dc..a1c080d2b45 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -27,6 +27,7 @@ export const KnownSnippetVariableNames: { [key: string]: true } = Object.freeze( 'CURRENT_DAY_NAME_SHORT': true, 'CURRENT_MONTH_NAME': true, 'CURRENT_MONTH_NAME_SHORT': true, + 'CURRENT_SECONDS_UNIX': true, 'SELECTION': true, 'CLIPBOARD': true, 'TM_SELECTED_TEXT': true, @@ -245,6 +246,8 @@ export class TimeBasedVariableResolver implements VariableResolver { return TimeBasedVariableResolver.monthNames[new Date().getMonth()]; } else if (name === 'CURRENT_MONTH_NAME_SHORT') { return TimeBasedVariableResolver.monthNamesShort[new Date().getMonth()]; + } else if (name === 'CURRENT_SECONDS_UNIX') { + return String(Math.floor(Date.now() / 1000)); } return undefined; diff --git a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts index 60bcebba8b0..f999da850bc 100644 --- a/src/vs/editor/contrib/snippet/test/snippetParser.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetParser.test.ts @@ -418,7 +418,7 @@ suite('SnippetParser', () => { assert.ok(children[3] instanceof Placeholder); assert.equal(children[3].children.length, 0); assert.notEqual((children[3]).transform, undefined); - let transform = (children[3]).transform; + let transform = (children[3]).transform!; assert.equal(transform.regexp, '/\\s:=(.*)/'); assert.equal(transform.children.length, 2); assert.ok(transform.children[0] instanceof FormatString); @@ -754,4 +754,17 @@ suite('SnippetParser', () => { let snippet = new SnippetParser().parse('namespace ${TM_DIRECTORY/[\\/]/\\\\/g};'); assertMarker(snippet, Text, Variable, Text); }); + + test('Snippet cannot escape closing bracket inside conditional insertion variable replacement #78883', function () { + + let snippet = new SnippetParser().parse('${TM_DIRECTORY/(.+)/${1:+import { hello \\} from world}/}'); + let variable = snippet.children[0]; + assert.equal(snippet.children.length, 1); + assert.ok(variable instanceof Variable); + assert.ok(variable.transform); + assert.equal(variable.transform!.children.length, 1); + assert.ok(variable.transform!.children[0] instanceof FormatString); + assert.equal((variable.transform!.children[0]).ifValue, 'import { hello } from world'); + assert.equal((variable.transform!.children[0]).elseValue, undefined); + }); }); diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index 7a5367085e7..b4412f1d130 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -281,6 +281,7 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve3(resolver, 'CURRENT_DAY_NAME_SHORT'); assertVariableResolve3(resolver, 'CURRENT_MONTH_NAME'); assertVariableResolve3(resolver, 'CURRENT_MONTH_NAME_SHORT'); + assertVariableResolve3(resolver, 'CURRENT_SECONDS_UNIX'); }); test('creating snippet - format-condition doesn\'t work #53617', function () { diff --git a/src/vs/editor/contrib/suggest/completionModel.ts b/src/vs/editor/contrib/suggest/completionModel.ts index 29673ee6473..9a806ddf3de 100644 --- a/src/vs/editor/contrib/suggest/completionModel.ts +++ b/src/vs/editor/contrib/suggest/completionModel.ts @@ -29,8 +29,10 @@ export interface ICompletionStats { } export class LineContext { - leadingLineContent: string; - characterCountDelta: number; + constructor( + readonly leadingLineContent: string, + readonly characterCountDelta: number, + ) { } } const enum Refilter { @@ -49,9 +51,9 @@ export class CompletionModel { private _lineContext: LineContext; private _refilterKind: Refilter; - private _filteredItems: StrictCompletionItem[]; - private _isIncomplete: Set; - private _stats: ICompletionStats; + private _filteredItems?: StrictCompletionItem[]; + private _isIncomplete?: Set; + private _stats?: ICompletionStats; constructor( items: CompletionItem[], @@ -89,12 +91,12 @@ export class CompletionModel { get items(): CompletionItem[] { this._ensureCachedState(); - return this._filteredItems; + return this._filteredItems!; } get incomplete(): Set { this._ensureCachedState(); - return this._isIncomplete; + return this._isIncomplete!; } adopt(except: Set): CompletionItem[] { @@ -117,7 +119,7 @@ export class CompletionModel { get stats(): ICompletionStats { this._ensureCachedState(); - return this._stats; + return this._stats!; } private _ensureCachedState(): void { @@ -136,7 +138,7 @@ export class CompletionModel { let wordLow = ''; // incrementally filter less - const source = this._refilterKind === Refilter.All ? this._items : this._filteredItems; + const source = this._refilterKind === Refilter.All ? this._items : this._filteredItems!; const target: StrictCompletionItem[] = []; // picks a score function based on the number of diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 5e35818fac7..d01d1b6bb5b 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -114,7 +114,9 @@ .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .close { background-image: url('./close-light.svg'); - float: right; + position: absolute; + top: 0px; + right: 0px; margin-right: 5px; } @@ -255,7 +257,7 @@ text-overflow: ellipsis; opacity: 0.7; word-break: break-all; - margin: 0; + margin: 0px 24px 0 0; padding: 4px 0 12px 5px; } diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 241a826a54c..d63e47c8811 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -27,7 +27,7 @@ export const Context = { export class CompletionItem { - _brand: 'ISuggestionItem'; + _brand!: 'ISuggestionItem'; readonly resolve: (token: CancellationToken) => Promise; diff --git a/src/vs/editor/contrib/suggest/suggestAlternatives.ts b/src/vs/editor/contrib/suggest/suggestAlternatives.ts index 4d480fa8f4f..e8a6ea1a338 100644 --- a/src/vs/editor/contrib/suggest/suggestAlternatives.ts +++ b/src/vs/editor/contrib/suggest/suggestAlternatives.ts @@ -15,7 +15,7 @@ export class SuggestAlternatives { private readonly _ckOtherSuggestions: IContextKey; - private _index: number; + private _index: number = 0; private _model: CompletionModel | undefined; private _acceptNext: ((selected: ISelectedSuggestion) => any) | undefined; private _listener: IDisposable | undefined; diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index aac58cf801d..958cff7b534 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -370,6 +370,10 @@ export class SuggestController implements IEditorContribution { this._widget.getValue().toggleDetails(); } + toggleExplainMode(): void { + this._widget.getValue().toggleExplainMode(); + } + toggleSuggestionFocus(): void { this._widget.getValue().toggleDetailsFocus(); } @@ -521,6 +525,16 @@ registerEditorCommand(new SuggestCommand({ } })); +registerEditorCommand(new SuggestCommand({ + id: 'toggleExplainMode', + precondition: SuggestContext.Visible, + handler: x => x.toggleExplainMode(), + kbOpts: { + weight: KeybindingWeight.EditorContrib, + primary: KeyMod.CtrlCmd | KeyCode.US_SLASH, + } +})); + registerEditorCommand(new SuggestCommand({ id: 'toggleSuggestionFocus', precondition: SuggestContext.Visible, diff --git a/src/vs/editor/contrib/suggest/suggestMemory.ts b/src/vs/editor/contrib/suggest/suggestMemory.ts index 7e19017b79e..d08685206be 100644 --- a/src/vs/editor/contrib/suggest/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/suggestMemory.ts @@ -22,10 +22,10 @@ export abstract class Memory { if (items.length === 0) { return 0; } - let topScore = items[0].score; + let topScore = items[0].score[0]; for (let i = 1; i < items.length; i++) { const { score, completion: suggestion } = items[i]; - if (score !== topScore) { + if (score[0] !== topScore) { // stop when leaving the group of top matches break; } @@ -81,33 +81,42 @@ export class LRUMemory extends Memory { } select(model: ITextModel, pos: IPosition, items: CompletionItem[]): number { - // in order of completions, select the first - // that has been used in the past - let { word } = model.getWordUntilPosition(pos); - if (word.length !== 0) { - return super.select(model, pos, items); + + if (items.length === 0) { + return 0; } - let lineSuffix = model.getLineContent(pos.lineNumber).substr(pos.column - 10, pos.column - 1); + const lineSuffix = model.getLineContent(pos.lineNumber).substr(pos.column - 10, pos.column - 1); if (/\s$/.test(lineSuffix)) { return super.select(model, pos, items); } - let res = -1; + let topScore = items[0].score[0]; + let indexPreselect = -1; + let indexRecency = -1; let seq = -1; for (let i = 0; i < items.length; i++) { - const { completion: suggestion } = items[i]; - const key = `${model.getLanguageIdentifier().language}/${suggestion.label}`; - const item = this._cache.get(key); - if (item && item.touch > seq && item.type === suggestion.kind && item.insertText === suggestion.insertText) { + if (items[i].score[0] !== topScore) { + // consider only top items + break; + } + const key = `${model.getLanguageIdentifier().language}/${items[i].completion.label}`; + const item = this._cache.peek(key); + if (item && item.touch > seq && item.type === items[i].completion.kind && item.insertText === items[i].completion.insertText) { seq = item.touch; - res = i; + indexRecency = i; + } + if (items[i].completion.preselect && indexPreselect === -1) { + // stop when seeing an auto-select-item + return indexPreselect = i; } } - if (res === -1) { - return super.select(model, pos, items); + if (indexRecency !== -1) { + return indexRecency; + } else if (indexPreselect !== -1) { + return indexPreselect; } else { - return res; + return 0; } } @@ -204,9 +213,9 @@ export class SuggestMemoryService extends Disposable implements ISuggestMemorySe private readonly _storagePrefix = 'suggest/memories'; private readonly _persistSoon: RunOnceScheduler; - private _mode: MemMode; - private _shareMem: boolean; - private _strategy: Memory; + private _mode!: MemMode; + private _shareMem!: boolean; + private _strategy!: Memory; constructor( @IStorageService private readonly _storageService: IStorageService, diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 63de4055d7e..cabf449d896 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -92,8 +92,8 @@ export const enum State { export class SuggestModel implements IDisposable { private readonly _toDispose = new DisposableStore(); - private _quickSuggestDelay: number; - private _triggerCharacterListener: IDisposable; + private _quickSuggestDelay: number = 10; + private _triggerCharacterListener?: IDisposable; private readonly _triggerQuickSuggest = new TimeoutTimer(); private _state: State = State.Idle; @@ -161,7 +161,8 @@ export class SuggestModel implements IDisposable { } dispose(): void { - dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest]); + dispose(this._triggerCharacterListener); + dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerQuickSuggest]); this._toDispose.dispose(); this._completionDisposables.dispose(); this.cancel(); diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 8cbb72a677f..04ed87429ea 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -38,8 +38,6 @@ import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { FileKind } from 'vs/platform/files/common/files'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; const expandSuggestionDocsByDefault = false; @@ -121,7 +119,7 @@ class Renderer implements IListRenderer const text = append(container, $('.contents')); const main = append(text, $('.main')); - data.iconLabel = new IconLabel(main, { supportHighlights: true }); + data.iconLabel = new IconLabel(main, { supportHighlights: true, supportOcticons: true }); data.disposables.add(data.iconLabel); data.typeLabel = append(main, $('span.type-label')); @@ -239,17 +237,6 @@ const enum State { } -let _explainMode = false; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'suggest.toggleExplainMode', - handler() { - _explainMode = !_explainMode; - }, - when: SuggestContext.Visible, - weight: KeybindingWeight.EditorContrib, - primary: KeyMod.CtrlCmd | KeyCode.US_SLASH, -}); - class SuggestionDetails { private el: HTMLElement; @@ -261,7 +248,7 @@ class SuggestionDetails { private docs: HTMLElement; private ariaLabel: string | null; private readonly disposables: DisposableStore; - private renderDisposeable: IDisposable; + private renderDisposeable?: IDisposable; private borderWidth: number = 1; constructor( @@ -308,13 +295,13 @@ class SuggestionDetails { this.docs.textContent = ''; } - renderItem(item: CompletionItem): void { + renderItem(item: CompletionItem, explainMode: boolean): void { this.renderDisposeable = dispose(this.renderDisposeable); let { documentation, detail } = item.completion; // --- documentation - if (_explainMode) { + if (explainMode) { let md = ''; md += `score: ${item.score[0]}${item.word ? `, compared '${item.completion.filterText && (item.completion.filterText + ' (filterText)') || item.completion.label}' with '${item.word}'` : ' (no prefix)'}\n`; md += `distance: ${item.distance}, see localityBonus-setting\n`; @@ -323,7 +310,7 @@ class SuggestionDetails { detail = `Provider: ${item.provider._debugDisplayName}`; } - if (!_explainMode && !canExpandCompletionItem(item)) { + if (!explainMode && !canExpandCompletionItem(item)) { this.type.textContent = ''; this.docs.textContent = ''; addClass(this.el, 'no-docs'); @@ -443,25 +430,24 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate | null; + private currentSuggestionDetails: CancelablePromise | null = null; private focusedItem: CompletionItem | null; - private ignoreFocusEvents = false; - private completionModel: CompletionModel | null; + private ignoreFocusEvents: boolean = false; + private completionModel: CompletionModel | null = null; private element: HTMLElement; private messageElement: HTMLElement; private listElement: HTMLElement; private details: SuggestionDetails; private list: List; - private listHeight: number; + private listHeight?: number; private readonly suggestWidgetVisible: IContextKey; private readonly suggestWidgetMultipleSuggestions: IContextKey; - private readonly editorBlurTimeout = new TimeoutTimer(); private readonly showTimeout = new TimeoutTimer(); private readonly toDispose = new DisposableStore(); @@ -478,13 +464,14 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate ignore recent items + + mem.memorize(buffer, { lineNumber: 1, column: 9 }, item2); + assert.equal(mem.select(buffer, { lineNumber: 1, column: 9 }, items), 1); // foo. + + buffer.setValue(' foo.g'); + assert.equal(mem.select(buffer, { lineNumber: 1, column: 10 }, items), 1); // foo.g, 'gamma' and 'game' have the same score + + item1.score = [10, 0, 0]; + assert.equal(mem.select(buffer, { lineNumber: 1, column: 10 }, items), 0); // foo.g, 'gamma' has higher score + + }); + test('intellisense is not showing top options first #43429', function () { // ensure we don't memorize for whitespace prefixes diff --git a/src/vs/editor/contrib/suggest/wordContextKey.ts b/src/vs/editor/contrib/suggest/wordContextKey.ts index 9461b86d339..7ec28a4cbef 100644 --- a/src/vs/editor/contrib/suggest/wordContextKey.ts +++ b/src/vs/editor/contrib/suggest/wordContextKey.ts @@ -13,7 +13,7 @@ export class WordContextKey extends Disposable { private readonly _ckAtEnd: IContextKey; - private _enabled: boolean; + private _enabled: boolean = false; private _selectionListener?: IDisposable; constructor( diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index 0820b22ce51..4fd4f5d2db0 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -469,6 +469,7 @@ class WordHighlighterContribution extends Disposable implements editorCommon.IEd constructor(editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService) { super(); + this.wordHighligher = null; const createWordHighlighterIfPossible = () => { if (editor.hasModel()) { this.wordHighligher = new WordHighlighter(editor, contextKeyService); diff --git a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts index 34ed306c947..703706bf238 100644 --- a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts @@ -51,7 +51,7 @@ const WIDGET_ID = 'vs.editor.contrib.zoneWidget'; export class ViewZoneDelegate implements IViewZone { public domNode: HTMLElement; - public id: number; + public id: string = ''; // A valid zone id should be greater than 0 public afterLineNumber: number; public afterColumn: number; public heightInLines: number; @@ -109,8 +109,8 @@ class Arrow { private readonly _ruleName = Arrow._IdGenerator.nextId(); private _decorations: string[] = []; - private _color: string; - private _height: number; + private _color: string | null = null; + private _height: number = -1; constructor( private readonly _editor: ICodeEditor @@ -159,15 +159,15 @@ class Arrow { export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { - private _arrow: Arrow; - private _overlayWidget: OverlayWidgetDelegate | null; - private _resizeSash: Sash; + private _arrow: Arrow | null = null; + private _overlayWidget: OverlayWidgetDelegate | null = null; + private _resizeSash: Sash | null = null; private _positionMarkerId: string[] = []; - protected _viewZone: ViewZoneDelegate | null; + protected _viewZone: ViewZoneDelegate | null = null; protected readonly _disposables = new DisposableStore(); - public container: HTMLElement; + public container: HTMLElement | null = null; public domNode: HTMLElement; public editor: ICodeEditor; public options: IOptions; @@ -192,9 +192,6 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { } public dispose(): void { - - this._disposables.dispose(); - if (this._overlayWidget) { this.editor.removeOverlayWidget(this._overlayWidget); this._overlayWidget = null; @@ -211,6 +208,8 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this.editor.deltaDecorations(this._positionMarkerId, []); this._positionMarkerId = []; + + this._disposables.dispose(); } public create(): void { @@ -273,12 +272,16 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { private _onViewZoneHeight(height: number): void { this.domNode.style.height = `${height}px`; - let containerHeight = height - this._decoratingElementsHeight(); - this.container.style.height = `${containerHeight}px`; - const layoutInfo = this.editor.getLayoutInfo(); - this._doLayout(containerHeight, this._getWidth(layoutInfo)); + if (this.container) { + let containerHeight = height - this._decoratingElementsHeight(); + this.container.style.height = `${containerHeight}px`; + const layoutInfo = this.editor.getLayoutInfo(); + this._doLayout(containerHeight, this._getWidth(layoutInfo)); + } - this._resizeSash.layout(); + if (this._resizeSash) { + this._resizeSash.layout(); + } } public get position(): Position | undefined { @@ -373,7 +376,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { let frameThickness = 0; // Render the arrow one 1/3 of an editor line height - if (this.options.showArrow) { + if (this._arrow && this.options.showArrow) { arrowHeight = Math.round(lineHeight / 3); this._arrow.height = arrowHeight; this._arrow.show(position); @@ -407,17 +410,19 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this.editor.addOverlayWidget(this._overlayWidget); }); - if (this.options.showFrame) { + if (this.container && this.options.showFrame) { const width = this.options.frameWidth ? this.options.frameWidth : frameThickness; this.container.style.borderTopWidth = width + 'px'; this.container.style.borderBottomWidth = width + 'px'; } let containerHeight = heightInLines * lineHeight - this._decoratingElementsHeight(); - this.container.style.top = arrowHeight + 'px'; - this.container.style.height = containerHeight + 'px'; - this.container.style.overflow = 'hidden'; + if (this.container) { + this.container.style.top = arrowHeight + 'px'; + this.container.style.height = containerHeight + 'px'; + this.container.style.overflow = 'hidden'; + } this._doLayout(containerHeight, width); @@ -438,6 +443,10 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { } protected setCssClass(className: string, classToReplace?: string): void { + if (!this.container) { + return; + } + if (classToReplace) { this.container.classList.remove(classToReplace); } diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index 22e860f8eb0..1981153c0c8 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -7,7 +7,7 @@ import 'vs/css!./accessibilityHelp'; import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; -import { renderFormattedText } from 'vs/base/browser/htmlContentRenderer'; +import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { Widget } from 'vs/base/browser/ui/widget'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; diff --git a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts index 1fc21b5fc7f..b36f6ecdc23 100644 --- a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts +++ b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts @@ -21,6 +21,7 @@ export class IPadShowKeyboard extends Disposable implements IEditorContribution constructor(editor: ICodeEditor) { super(); this.editor = editor; + this.widget = null; if (browser.isIPad) { this._register(editor.onDidChangeConfiguration(() => this.update())); this.update(); diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts index c8fb32757b4..96bcc2ea3c4 100644 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts +++ b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts @@ -31,9 +31,9 @@ export class QuickOpenController implements editorCommon.IEditorContribution, ID } private readonly editor: ICodeEditor; - private widget: QuickOpenEditorWidget | null; - private rangeHighlightDecorationId: string | null; - private lastKnownEditorSelection: Selection | null; + private widget: QuickOpenEditorWidget | null = null; + private rangeHighlightDecorationId: string | null = null; + private lastKnownEditorSelection: Selection | null = null; constructor(editor: ICodeEditor, @IThemeService private readonly themeService: IThemeService) { this.editor = editor; diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index d333ce03746..54804eb3c0f 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -98,17 +98,20 @@ function withTypedEditor(widget: editorCommon.IEditor, codeEditorCallback: (e export class SimpleEditorModelResolverService implements ITextModelService { public _serviceBrand: any; - private editor: editorCommon.IEditor; + private editor?: editorCommon.IEditor; public setEditor(editor: editorCommon.IEditor): void { this.editor = editor; } public createModelReference(resource: URI): Promise> { - let model: ITextModel | null = withTypedEditor(this.editor, - (editor) => this.findModel(editor, resource), - (diffEditor) => this.findModel(diffEditor.getOriginalEditor(), resource) || this.findModel(diffEditor.getModifiedEditor(), resource) - ); + let model: ITextModel | null = null; + if (this.editor) { + model = withTypedEditor(this.editor, + (editor) => this.findModel(editor, resource), + (diffEditor) => this.findModel(diffEditor.getOriginalEditor(), resource) || this.findModel(diffEditor.getModifiedEditor(), resource) + ); + } if (!model) { return Promise.reject(new Error(`Model not found`)); @@ -468,7 +471,7 @@ export class SimpleConfigurationService implements IConfigurationService { defaults: emptyModel, user: emptyModel, workspace: emptyModel, - folders: {} + folders: [] }; } } @@ -477,12 +480,12 @@ export class SimpleResourceConfigurationService implements ITextResourceConfigur _serviceBrand: any; - public readonly onDidChangeConfiguration: Event; - private readonly _onDidChangeConfigurationEmitter = new Emitter(); + private readonly _onDidChangeConfiguration = new Emitter(); + public readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event; constructor(private readonly configurationService: SimpleConfigurationService) { this.configurationService.onDidChangeConfiguration((e) => { - this._onDidChangeConfigurationEmitter.fire(e); + this._onDidChangeConfiguration.fire(e); }); } @@ -519,7 +522,7 @@ export class SimpleResourcePropertiesService implements ITextResourcePropertiesS } export class StandaloneTelemetryService implements ITelemetryService { - _serviceBrand: void; + _serviceBrand: void = undefined; public isOptedIn = false; @@ -687,7 +690,7 @@ export class SimpleLayoutService implements ILayoutService { public onLayout = Event.None; - private _dimension: IDimension; + private _dimension?: IDimension; get dimension(): IDimension { if (!this._dimension) { this._dimension = dom.getClientArea(window.document.body); diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 85b3aaaf0cf..31a3f56b704 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -153,7 +153,7 @@ function createAriaDomNode() { */ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandaloneCodeEditor { - private readonly _standaloneKeybindingService: StandaloneKeybindingService; + private readonly _standaloneKeybindingService: StandaloneKeybindingService | null; constructor( domElement: HTMLElement, @@ -178,6 +178,8 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon if (keybindingService instanceof StandaloneKeybindingService) { this._standaloneKeybindingService = keybindingService; + } else { + this._standaloneKeybindingService = null; } // Create the ARIA dom node as soon as the first editor is instantiated diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 8750db8f943..ddba66741d9 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -38,6 +38,9 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProductService } from 'vs/platform/product/common/product'; +import { IStorageService } from 'vs/platform/storage/common/storage'; type Omit = Pick>; @@ -51,7 +54,13 @@ function withAllStandaloneServices(domElement: H } if (!services.has(IOpenerService)) { - services.set(IOpenerService, new OpenerService(services.get(ICodeEditorService), services.get(ICommandService))); + services.set(IOpenerService, new OpenerService( + services.get(ICodeEditorService), + services.get(ICommandService), + services.get(IStorageService), + services.get(IDialogService), + services.get(IProductService) + )); } let result = callback(services); diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index cfe1ec119f6..e8e9dffbe84 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -482,6 +482,20 @@ export function registerFoldingRangeProvider(languageId: string, provider: modes return modes.FoldingRangeProviderRegistry.register(languageId, provider); } +/** + * Register a declaration provider + */ +export function registerDeclarationProvider(languageId: string, provider: modes.DeclarationProvider): IDisposable { + return modes.DeclarationProviderRegistry.register(languageId, provider); +} + +/** + * Register a selection range provider + */ +export function registerSelectionRangeProvider(languageId: string, provider: modes.SelectionRangeProvider): IDisposable { + return modes.SelectionRangeRegistry.register(languageId, provider); +} + /** * Contains additional diagnostic information about the context in which * a [code action](#CodeActionProvider.provideCodeActions) is run. @@ -542,6 +556,8 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { registerLinkProvider: registerLinkProvider, registerColorProvider: registerColorProvider, registerFoldingRangeProvider: registerFoldingRangeProvider, + registerDeclarationProvider: registerDeclarationProvider, + registerSelectionRangeProvider: registerSelectionRangeProvider, // enums DocumentHighlightKind: standaloneEnums.DocumentHighlightKind, diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 3f04994e55c..2a47cc6113b 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -160,7 +160,7 @@ export class StandaloneThemeServiceImpl implements IStandaloneThemeService { private readonly _knownThemes: Map; private readonly _styleElement: HTMLStyleElement; - private _theme: IStandaloneTheme; + private _theme!: IStandaloneTheme; private readonly _onThemeChange: Emitter; private readonly _onIconThemeChange: Emitter; private readonly environment: IEnvironmentService = Object.create(null); diff --git a/src/vs/editor/standalone/common/monarch/monarchCompile.ts b/src/vs/editor/standalone/common/monarch/monarchCompile.ts index f056854e44a..2c98c6ba429 100644 --- a/src/vs/editor/standalone/common/monarch/monarchCompile.ts +++ b/src/vs/editor/standalone/common/monarch/monarchCompile.ts @@ -27,11 +27,9 @@ function isArrayOf(elemType: (x: any) => boolean, obj: any): boolean { if (!(Array.isArray(obj))) { return false; } - for (let idx in obj) { - if (obj.hasOwnProperty(idx)) { - if (!(elemType(obj[idx]))) { - return false; - } + for (const el of obj) { + if (!(elemType(el))) { + return false; } } return true; @@ -298,10 +296,8 @@ function compileAction(lexer: monarchCommon.ILexerMin, ruleName: string, action: } else if (Array.isArray(action)) { let results: monarchCommon.FuzzyAction[] = []; - for (let idx in action) { - if (action.hasOwnProperty(idx)) { - results[idx] = compileAction(lexer, ruleName, action[idx]); - } + for (let i = 0, len = action.length; i < len; i++) { + results[i] = compileAction(lexer, ruleName, action[i]); } return { group: results }; } @@ -331,13 +327,10 @@ function compileAction(lexer: monarchCommon.ILexerMin, ruleName: string, action: const def = lexer.defaultToken; return { test: function (id, matches, state, eos) { - for (let idx in cases) { - if (cases.hasOwnProperty(idx)) { - const _case = cases[idx]; - const didmatch = (!_case.test || _case.test(id, matches, state, eos)); - if (didmatch) { - return _case.value; - } + for (const _case of cases) { + const didmatch = (!_case.test || _case.test(id, matches, state, eos)); + if (didmatch) { + return _case.value; } } return def; @@ -425,64 +418,61 @@ export function compile(languageId: string, json: IMonarchLanguage): monarchComm // Compile an array of rules into newrules where RegExp objects are created. function addRules(state: string, newrules: monarchCommon.IRule[], rules: any[]) { - for (let idx in rules) { - if (rules.hasOwnProperty(idx)) { - const rule = rules[idx]; - let include = rule.include; - if (include) { - if (typeof (include) !== 'string') { - throw monarchCommon.createError(lexer, 'an \'include\' attribute must be a string at: ' + state); - } - if (include[0] === '@') { - include = include.substr(1); // peel off starting @ - } - if (!json.tokenizer[include]) { - throw monarchCommon.createError(lexer, 'include target \'' + include + '\' is not defined at: ' + state); - } - addRules(state + '.' + include, newrules, json.tokenizer[include]); + for (const rule of rules) { + + let include = rule.include; + if (include) { + if (typeof (include) !== 'string') { + throw monarchCommon.createError(lexer, 'an \'include\' attribute must be a string at: ' + state); } - else { - const newrule = new Rule(state); + if (include[0] === '@') { + include = include.substr(1); // peel off starting @ + } + if (!json.tokenizer[include]) { + throw monarchCommon.createError(lexer, 'include target \'' + include + '\' is not defined at: ' + state); + } + addRules(state + '.' + include, newrules, json.tokenizer[include]); + } + else { + const newrule = new Rule(state); - - // Set up new rule attributes - if (Array.isArray(rule) && rule.length >= 1 && rule.length <= 3) { - newrule.setRegex(lexerMin, rule[0]); - if (rule.length >= 3) { - if (typeof (rule[1]) === 'string') { - newrule.setAction(lexerMin, { token: rule[1], next: rule[2] }); - } - else if (typeof (rule[1]) === 'object') { - const rule1 = rule[1]; - rule1.next = rule[2]; - newrule.setAction(lexerMin, rule1); - } - else { - throw monarchCommon.createError(lexer, 'a next state as the last element of a rule can only be given if the action is either an object or a string, at: ' + state); - } + // Set up new rule attributes + if (Array.isArray(rule) && rule.length >= 1 && rule.length <= 3) { + newrule.setRegex(lexerMin, rule[0]); + if (rule.length >= 3) { + if (typeof (rule[1]) === 'string') { + newrule.setAction(lexerMin, { token: rule[1], next: rule[2] }); + } + else if (typeof (rule[1]) === 'object') { + const rule1 = rule[1]; + rule1.next = rule[2]; + newrule.setAction(lexerMin, rule1); } else { - newrule.setAction(lexerMin, rule[1]); + throw monarchCommon.createError(lexer, 'a next state as the last element of a rule can only be given if the action is either an object or a string, at: ' + state); } } else { - if (!rule.regex) { - throw monarchCommon.createError(lexer, 'a rule must either be an array, or an object with a \'regex\' or \'include\' field at: ' + state); - } - if (rule.name) { - if (typeof rule.name === 'string') { - newrule.name = rule.name; - } - } - if (rule.matchOnlyAtStart) { - newrule.matchOnlyAtLineStart = bool(rule.matchOnlyAtLineStart, false); - } - newrule.setRegex(lexerMin, rule.regex); - newrule.setAction(lexerMin, rule.action); + newrule.setAction(lexerMin, rule[1]); } - - newrules.push(newrule); } + else { + if (!rule.regex) { + throw monarchCommon.createError(lexer, 'a rule must either be an array, or an object with a \'regex\' or \'include\' field at: ' + state); + } + if (rule.name) { + if (typeof rule.name === 'string') { + newrule.name = rule.name; + } + } + if (rule.matchOnlyAtStart) { + newrule.matchOnlyAtLineStart = bool(rule.matchOnlyAtLineStart, false); + } + newrule.setRegex(lexerMin, rule.regex); + newrule.setAction(lexerMin, rule.action); + } + + newrules.push(newrule); } } } @@ -520,26 +510,24 @@ export function compile(languageId: string, json: IMonarchLanguage): monarchComm { open: '<', close: '>', token: 'delimiter.angle' }]; } let brackets: IMonarchLanguageBracket[] = []; - for (let bracketIdx in json.brackets) { - if (json.brackets.hasOwnProperty(bracketIdx)) { - let desc = json.brackets[bracketIdx]; - if (desc && Array.isArray(desc) && desc.length === 3) { - desc = { token: desc[2], open: desc[0], close: desc[1] }; - } - if (desc.open === desc.close) { - throw monarchCommon.createError(lexer, 'open and close brackets in a \'brackets\' attribute must be different: ' + desc.open + - '\n hint: use the \'bracket\' attribute if matching on equal brackets is required.'); - } - if (typeof desc.open === 'string' && typeof desc.token === 'string' && typeof desc.close === 'string') { - brackets.push({ - token: desc.token + lexer.tokenPostfix, - open: monarchCommon.fixCase(lexer, desc.open), - close: monarchCommon.fixCase(lexer, desc.close) - }); - } - else { - throw monarchCommon.createError(lexer, 'every element in the \'brackets\' array must be a \'{open,close,token}\' object or array'); - } + for (let el of json.brackets) { + let desc: any = el; + if (desc && Array.isArray(desc) && desc.length === 3) { + desc = { token: desc[2], open: desc[0], close: desc[1] }; + } + if (desc.open === desc.close) { + throw monarchCommon.createError(lexer, 'open and close brackets in a \'brackets\' attribute must be different: ' + desc.open + + '\n hint: use the \'bracket\' attribute if matching on equal brackets is required.'); + } + if (typeof desc.open === 'string' && typeof desc.token === 'string' && typeof desc.close === 'string') { + brackets.push({ + token: desc.token + lexer.tokenPostfix, + open: monarchCommon.fixCase(lexer, desc.open), + close: monarchCommon.fixCase(lexer, desc.close) + }); + } + else { + throw monarchCommon.createError(lexer, 'every element in the \'brackets\' array must be a \'{open,close,token}\' object or array'); } } lexer.brackets = brackets; diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index a5c97ad6f28..39642a4d6e6 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -228,8 +228,6 @@ class MonarchLineState implements modes.IState { } } -const hasOwnProperty = Object.hasOwnProperty; - interface IMonarchTokensCollector { enterMode(startOffset: number, modeId: string): void; emit(startOffset: number, type: string): void; @@ -423,22 +421,24 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { public getLoadStatus(): ILoadStatus { let promises: Thenable[] = []; for (let nestedModeId in this._embeddedModes) { - const tokenizationSupport = modes.TokenizationRegistry.get(nestedModeId); - if (tokenizationSupport) { - // The nested mode is already loaded - if (tokenizationSupport instanceof MonarchTokenizer) { - const nestedModeStatus = tokenizationSupport.getLoadStatus(); - if (nestedModeStatus.loaded === false) { - promises.push(nestedModeStatus.promise); + if (this._embeddedModes.hasOwnProperty(nestedModeId)) { + const tokenizationSupport = modes.TokenizationRegistry.get(nestedModeId); + if (tokenizationSupport) { + // The nested mode is already loaded + if (tokenizationSupport instanceof MonarchTokenizer) { + const nestedModeStatus = tokenizationSupport.getLoadStatus(); + if (nestedModeStatus.loaded === false) { + promises.push(nestedModeStatus.promise); + } } + continue; } - continue; - } - const tokenizationSupportPromise = modes.TokenizationRegistry.getPromise(nestedModeId); - if (tokenizationSupportPromise) { - // The nested mode is in the process of being loaded - promises.push(tokenizationSupportPromise); + const tokenizationSupportPromise = modes.TokenizationRegistry.getPromise(nestedModeId); + if (tokenizationSupportPromise) { + // The nested mode is in the process of being loaded + promises.push(tokenizationSupportPromise); + } } } @@ -490,11 +490,7 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { let popOffset = -1; let hasEmbeddedPopRule = false; - for (let idx in rules) { - if (!hasOwnProperty.call(rules, idx)) { - continue; - } - let rule: monarchCommon.IRule = rules[idx]; + for (const rule of rules) { if (!monarchCommon.isIAction(rule.action) || rule.action.nextEmbedded !== '@pop') { continue; } @@ -619,16 +615,13 @@ export class MonarchTokenizer implements modes.ITokenizationSupport { // try each rule until we match let restOfLine = line.substr(pos); - for (let idx in rules) { - if (hasOwnProperty.call(rules, idx)) { - let rule: monarchCommon.IRule = rules[idx]; - if (pos === 0 || !rule.matchOnlyAtLineStart) { - matches = restOfLine.match(rule.regex); - if (matches) { - matched = matches[0]; - action = rule.action; - break; - } + for (const rule of rules) { + if (pos === 0 || !rule.matchOnlyAtLineStart) { + matches = restOfLine.match(rule.regex); + if (matches) { + matched = matches[0]; + action = rule.action; + break; } } } diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index c2a4e4a4560..59e221a8c2f 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -3633,6 +3633,75 @@ suite('Editor Controller - Indentation Rules', () => { model.dispose(); mode.dispose(); }); + + test('', () => { + class JSONMode extends MockMode { + private static readonly _id = new LanguageIdentifier('indentRulesMode', 4); + constructor() { + super(JSONMode._id); + this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), { + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + indentationRules: { + increaseIndentPattern: new RegExp('^.*\\{[^}\"\\\']*$|^.*\\([^\\)\"\\\']*$|^\\s*(public|private|protected):\\s*$|^\\s*@(public|private|protected)\\s*$|^\\s*\\{\\}$'), + decreaseIndentPattern: new RegExp('^\\s*(\\s*/[*].*[*]/\\s*)*\\}|^\\s*(\\s*/[*].*[*]/\\s*)*\\)|^\\s*(public|private|protected):\\s*$|^\\s*@(public|private|protected)\\s*$'), + } + })); + } + } + + let mode = new JSONMode(); + let model = createTextModel( + [ + '{', + ' "scripts: {"', + ' "watch": "a {"', + ' "build{": "b"', + ' "tasks": []', + ' "tasks": ["a"]', + ' "}"', + '"}"' + ].join('\n'), + { + tabSize: 2, + indentSize: 2 + }, + mode.getLanguageIdentifier() + ); + + withTestCodeEditor(null, { model: model, autoIndent: true }, (editor, cursor) => { + moveTo(cursor, 3, 19, false); + assertCursor(cursor, new Selection(3, 19, 3, 19)); + + cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); + assert.deepEqual(model.getLineContent(4), ' '); + + moveTo(cursor, 5, 18, false); + assertCursor(cursor, new Selection(5, 18, 5, 18)); + + cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); + assert.deepEqual(model.getLineContent(6), ' '); + + moveTo(cursor, 7, 15, false); + assertCursor(cursor, new Selection(7, 15, 7, 15)); + + cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); + assert.deepEqual(model.getLineContent(8), ' '); + assert.deepEqual(model.getLineContent(9), ' ]'); + + moveTo(cursor, 10, 18, false); + assertCursor(cursor, new Selection(10, 18, 10, 18)); + + cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); + assert.deepEqual(model.getLineContent(11), ' ]'); + }); + + model.dispose(); + mode.dispose(); + }); }); interface ICursorOpts { @@ -3929,8 +3998,12 @@ suite('autoClosingPairs', () => { { open: '\'', close: '\'', notIn: ['string', 'comment'] }, { open: '\"', close: '\"', notIn: ['string'] }, { open: '`', close: '`', notIn: ['string', 'comment'] }, - { open: '/**', close: ' */', notIn: ['string'] } + { open: '/**', close: ' */', notIn: ['string'] }, + { open: 'begin', close: 'end', notIn: ['string'] } ], + __electricCharacterSupport: { + docComment: { open: '/**', close: ' */' } + } })); } @@ -4370,6 +4443,28 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('multi-character autoclose', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + '', + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + + model.setValue('begi'); + cursor.setSelections('test', [new Selection(1, 5, 1, 5)]); + cursorCommand(cursor, H.Type, { text: 'n' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'beginend'); + + model.setValue('/*'); + cursor.setSelections('test', [new Selection(1, 3, 1, 3)]); + cursorCommand(cursor, H.Type, { text: '*' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), '/** */'); + }); + mode.dispose(); + }); + test('issue #55314: Do not auto-close when ending with open', () => { const languageId = new LanguageIdentifier('myElectricMode', 5); class ElectricMode extends MockMode { @@ -4408,7 +4503,7 @@ suite('autoClosingPairs', () => { model.forceTokenization(model.getLineCount()); assertType(model, cursor, 3, 4, '"', '"', `does not double quote when ending with open`); model.forceTokenization(model.getLineCount()); - assertType(model, cursor, 4, 2, '"', '""', `double quote when ending with open`); + assertType(model, cursor, 4, 2, '"', '"', `does not double quote when ending with open`); model.forceTokenization(model.getLineCount()); assertType(model, cursor, 4, 3, '"', '"', `does not double quote when ending with open`); }); @@ -4595,6 +4690,56 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #78527 - does not close quote on odd count', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + 'std::cout << \'"\' << entryMap' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + cursor.setSelections('test', [new Selection(1, 29, 1, 29)]); + + cursorCommand(cursor, H.Type, { text: '[' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'std::cout << \'"\' << entryMap[]'); + + cursorCommand(cursor, H.Type, { text: '"' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'std::cout << \'"\' << entryMap[""]'); + + cursorCommand(cursor, H.Type, { text: 'a' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'std::cout << \'"\' << entryMap["a"]'); + + cursorCommand(cursor, H.Type, { text: '"' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'std::cout << \'"\' << entryMap["a"]'); + + cursorCommand(cursor, H.Type, { text: ']' }, 'keyboard'); + assert.strictEqual(model.getLineContent(1), 'std::cout << \'"\' << entryMap["a"]'); + }); + mode.dispose(); + }); + + test('issue #78975 - Parentheses swallowing does not work when parentheses are inserted by autocomplete', () => { + let mode = new AutoClosingMode(); + usingCursor({ + text: [ + '
{ + cursor.setSelections('test', [new Selection(1, 8, 1, 8)]); + + cursor.executeEdits('snippet', [{ range: new Range(1, 6, 1, 8), text: 'id=""' }], () => [new Selection(1, 10, 1, 10)]); + assert.strictEqual(model.getLineContent(1), '
{ let mode = new AutoClosingMode(); usingCursor({ @@ -4653,31 +4798,18 @@ suite('autoClosingPairs', () => { // on the mac US intl kb layout - // Typing ` + space + // Typing ' + space cursorCommand(cursor, H.CompositionStart, null, 'keyboard'); cursorCommand(cursor, H.Type, { text: '\'' }, 'keyboard'); cursorCommand(cursor, H.ReplacePreviousChar, { replaceCharCnt: 1, text: '\'' }, 'keyboard'); cursorCommand(cursor, H.CompositionEnd, null, 'keyboard'); - assert.equal(model.getValue(), '\'\''); - // Typing " + space within string - cursor.setSelections('test', [new Selection(1, 2, 1, 2)]); - cursorCommand(cursor, H.CompositionStart, null, 'keyboard'); - cursorCommand(cursor, H.Type, { text: '"' }, 'keyboard'); - cursorCommand(cursor, H.ReplacePreviousChar, { replaceCharCnt: 1, text: '"' }, 'keyboard'); - cursorCommand(cursor, H.CompositionEnd, null, 'keyboard'); - - assert.equal(model.getValue(), '\'"\''); - - // Typing ' + space after ' - model.setValue('\''); - cursor.setSelections('test', [new Selection(1, 2, 1, 2)]); + // Typing one more ' + space cursorCommand(cursor, H.CompositionStart, null, 'keyboard'); cursorCommand(cursor, H.Type, { text: '\'' }, 'keyboard'); cursorCommand(cursor, H.ReplacePreviousChar, { replaceCharCnt: 1, text: '\'' }, 'keyboard'); cursorCommand(cursor, H.CompositionEnd, null, 'keyboard'); - assert.equal(model.getValue(), '\'\''); // Typing ' as a closing tag diff --git a/src/vs/editor/test/browser/services/openerService.test.ts b/src/vs/editor/test/browser/services/openerService.test.ts index eadf902357a..3e1ef9e80cb 100644 --- a/src/vs/editor/test/browser/services/openerService.test.ts +++ b/src/vs/editor/test/browser/services/openerService.test.ts @@ -7,6 +7,10 @@ import { URI } from 'vs/base/common/uri'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; import { CommandsRegistry, ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; +import { deepClone } from 'vs/base/common/objects'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProductService } from 'vs/platform/product/common/product'; +import { IStorageService } from 'vs/platform/storage/common/storage'; suite('OpenerService', function () { @@ -24,19 +28,77 @@ suite('OpenerService', function () { } }; + function getStorageService(trustedDomainsSetting: string[]) { + let _settings = deepClone(trustedDomainsSetting); + + return new class implements IStorageService { + get = () => JSON.stringify(_settings); + store = (key: string, val: string) => _settings = JSON.parse(val); + + // Don't care + _serviceBrand: any; + + onDidChangeStorage = () => ({ dispose: () => { } }); + onWillSaveState = () => ({ dispose: () => { } }); + + getBoolean = () => true; + getNumber = () => 0; + remove = () => { }; + logStorage = () => { }; + }; + } + + function getDialogService() { + return new class implements IDialogService { + _showInvoked = 0; + show = () => { + this._showInvoked++; + return Promise.resolve({} as any); + } + get confirmInvoked() { return this._showInvoked; } + + // Don't care + _serviceBrand: any; + confirm = () => { + return Promise.resolve({} as any); + } + }; + } + + function getProductService(): IProductService { + return new class { + nameShort: 'VS Code'; + + _serviceBrand: any; + } as IProductService; + } + + setup(function () { lastCommand = undefined; }); test('delegate to editorService, scheme:///fff', function () { - const openerService = new OpenerService(editorService, NullCommandService); + const openerService = new OpenerService( + editorService, + NullCommandService, + getStorageService([]), + getDialogService(), + getProductService() + ); openerService.open(URI.parse('another:///somepath')); assert.equal(editorService.lastInput!.options!.selection, undefined); }); test('delegate to editorService, scheme:///fff#L123', function () { - const openerService = new OpenerService(editorService, NullCommandService); + const openerService = new OpenerService( + editorService, + NullCommandService, + getStorageService([]), + getDialogService(), + getProductService() + ); openerService.open(URI.parse('file:///somepath#L23')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); @@ -59,7 +121,13 @@ suite('OpenerService', function () { test('delegate to editorService, scheme:///fff#123,123', function () { - const openerService = new OpenerService(editorService, NullCommandService); + const openerService = new OpenerService( + editorService, + NullCommandService, + getStorageService([]), + getDialogService(), + getProductService() + ); openerService.open(URI.parse('file:///somepath#23')); assert.equal(editorService.lastInput!.options!.selection!.startLineNumber, 23); @@ -78,7 +146,13 @@ suite('OpenerService', function () { test('delegate to commandsService, command:someid', function () { - const openerService = new OpenerService(editorService, commandService); + const openerService = new OpenerService( + editorService, + commandService, + getStorageService([]), + getDialogService(), + getProductService() + ); const id = `aCommand${Math.random()}`; CommandsRegistry.registerCommand(id, function () { }); @@ -98,4 +172,70 @@ suite('OpenerService', function () { assert.equal(lastCommand!.args[0], 12); assert.equal(lastCommand!.args[1], true); }); + + test('links are protected by dialog.show', function () { + const dialogService = getDialogService(); + const openerService = new OpenerService( + editorService, + commandService, + getStorageService([]), + dialogService, + getProductService() + ); + + openerService.open(URI.parse('https://www.microsoft.com')); + assert.equal(dialogService.confirmInvoked, 1); + }); + + test('links on the whitelisted domains can be opened without dialog.show', function () { + const dialogService = getDialogService(); + const openerService = new OpenerService( + editorService, + commandService, + getStorageService(['https://microsoft.com']), + dialogService, + getProductService() + ); + + openerService.open(URI.parse('https://microsoft.com')); + openerService.open(URI.parse('https://microsoft.com/')); + openerService.open(URI.parse('https://microsoft.com/en-us/')); + openerService.open(URI.parse('https://microsoft.com/en-us/?foo=bar')); + openerService.open(URI.parse('https://microsoft.com/en-us/?foo=bar#baz')); + + assert.equal(dialogService.confirmInvoked, 0); + }); + + test('variations of links are protected by dialog confirmation', function () { + const dialogService = getDialogService(); + const openerService = new OpenerService( + editorService, + commandService, + getStorageService(['https://microsoft.com']), + dialogService, + getProductService() + ); + + openerService.open(URI.parse('http://microsoft.com')); + openerService.open(URI.parse('https://www.microsoft.com')); + + assert.equal(dialogService.confirmInvoked, 2); + }); + + test('* removes all link protection', function () { + const dialogService = getDialogService(); + const openerService = new OpenerService( + editorService, + commandService, + getStorageService(['*']), + dialogService, + getProductService() + ); + + openerService.open(URI.parse('https://code.visualstudio.com/')); + openerService.open(URI.parse('https://www.microsoft.com')); + openerService.open(URI.parse('https://www.github.com')); + + assert.equal(dialogService.confirmInvoked, 0); + }); }); diff --git a/src/vs/editor/test/common/model/textModel.test.ts b/src/vs/editor/test/common/model/textModel.test.ts index 812edc45468..0771acc14ae 100644 --- a/src/vs/editor/test/common/model/textModel.test.ts +++ b/src/vs/editor/test/common/model/textModel.test.ts @@ -608,6 +608,46 @@ suite('Editor Model - TextModel', () => { ]); }); + test('issue #62143: Broken indentation detection', () => { + // works before the fix + assertGuess(true, 2, [ + 'x', + 'x', + ' x', + ' x' + ]); + + // works before the fix + assertGuess(true, 2, [ + 'x', + ' - item2', + ' - item3' + ]); + + // works before the fix + testGuessIndentation(true, 2, true, 2, [ + 'x x', + ' x', + ' x', + ]); + + // fails before the fix + // empty space inline breaks the indentation guess + testGuessIndentation(true, 2, true, 2, [ + 'x x', + ' x', + ' x', + ' x' + ]); + + testGuessIndentation(true, 2, true, 2, [ + '', + '- item1', + ' - item2', + ' - item3' + ]); + }); + test('validatePosition', () => { let m = TextModel.createFromString('line one\nline two'); diff --git a/src/vs/editor/test/common/modes/supports/characterPair.test.ts b/src/vs/editor/test/common/modes/supports/characterPair.test.ts index ba9aa15f503..3f3f6880450 100644 --- a/src/vs/editor/test/common/modes/supports/characterPair.test.ts +++ b/src/vs/editor/test/common/modes/supports/characterPair.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { StandardTokenType } from 'vs/editor/common/modes'; import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair'; import { TokenText, createFakeScopedLineTokens } from 'vs/editor/test/common/modesTestUtils'; +import { StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration'; suite('CharacterPairSupport', () => { @@ -52,8 +53,21 @@ suite('CharacterPairSupport', () => { assert.deepEqual(characaterPairSupport.getSurroundingPairs(), []); }); + function findAutoClosingPair(characterPairSupport: CharacterPairSupport, character: string): StandardAutoClosingPairConditional | null { + for (const autoClosingPair of characterPairSupport.getAutoClosingPairs()) { + if (autoClosingPair.open === character) { + return autoClosingPair; + } + } + return null; + } + function testShouldAutoClose(characterPairSupport: CharacterPairSupport, line: TokenText[], character: string, column: number): boolean { - return characterPairSupport.shouldAutoClosePair(character, createFakeScopedLineTokens(line), column); + const autoClosingPair = findAutoClosingPair(characterPairSupport, character); + if (!autoClosingPair) { + return false; + } + return CharacterPairSupport.shouldAutoClosePair(autoClosingPair, createFakeScopedLineTokens(line), column); } test('shouldAutoClosePair in empty line', () => { diff --git a/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts b/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts index 6035379e05a..22b818c6b67 100644 --- a/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts +++ b/src/vs/editor/test/common/modes/supports/electricCharacter.test.ts @@ -21,86 +21,20 @@ suite('Editor Modes - Auto Indentation', () => { assert.deepEqual(actual, null); } - function testAppends(electricCharacterSupport: BracketElectricCharacterSupport, line: TokenText[], character: string, offset: number, appendText: string): void { - let actual = _testOnElectricCharacter(electricCharacterSupport, line, character, offset); - assert.deepEqual(actual, { appendText: appendText }); - } - function testMatchBracket(electricCharacterSupport: BracketElectricCharacterSupport, line: TokenText[], character: string, offset: number, matchOpenBracket: string): void { let actual = _testOnElectricCharacter(electricCharacterSupport, line, character, offset); assert.deepEqual(actual, { matchOpenBracket: matchOpenBracket }); } - test('Doc comments', () => { - let brackets = new BracketElectricCharacterSupport(null, [{ open: '/**', close: ' */' }], null); - - testAppends(brackets, [ - { text: '/*', type: StandardTokenType.Other }, - ], '*', 3, ' */'); - - testDoesNothing(brackets, [ - { text: '/*', type: StandardTokenType.Other }, - { text: ' ', type: StandardTokenType.Other }, - { text: '*/', type: StandardTokenType.Other }, - ], '*', 3); - }); - test('getElectricCharacters uses all sources and dedups', () => { let sup = new BracketElectricCharacterSupport( new RichEditBrackets(fakeLanguageIdentifier, [ ['{', '}'], ['(', ')'] - ]), [ - { open: '{', close: '}', notIn: ['string', 'comment'] }, - { open: '"', close: '"', notIn: ['string', 'comment'] }, - { open: 'begin', close: 'end', notIn: ['string'] } - ], - { docComment: { open: '/**', close: ' */' } } + ]) ); - assert.deepEqual(sup.getElectricCharacters(), ['}', ')', 'n', '*']); - }); - - test('auto-close', () => { - let sup = new BracketElectricCharacterSupport( - new RichEditBrackets(fakeLanguageIdentifier, [ - ['{', '}'], - ['(', ')'] - ]), [ - { open: '{', close: '}', notIn: ['string', 'comment'] }, - { open: '"', close: '"', notIn: ['string', 'comment'] }, - { open: 'begin', close: 'end', notIn: ['string'] } - ], - { docComment: { open: '/**', close: ' */' } } - ); - - testDoesNothing(sup, [], 'a', 0); - - testDoesNothing(sup, [{ text: 'egi', type: StandardTokenType.Other }], 'b', 1); - testDoesNothing(sup, [{ text: 'bgi', type: StandardTokenType.Other }], 'e', 2); - testDoesNothing(sup, [{ text: 'bei', type: StandardTokenType.Other }], 'g', 3); - testDoesNothing(sup, [{ text: 'beg', type: StandardTokenType.Other }], 'i', 4); - - testDoesNothing(sup, [{ text: 'egin', type: StandardTokenType.Other }], 'b', 1); - testDoesNothing(sup, [{ text: 'bgin', type: StandardTokenType.Other }], 'e', 2); - testDoesNothing(sup, [{ text: 'bein', type: StandardTokenType.Other }], 'g', 3); - testDoesNothing(sup, [{ text: 'begn', type: StandardTokenType.Other }], 'i', 4); - testAppends(sup, [{ text: 'begi', type: StandardTokenType.Other }], 'n', 5, 'end'); - - testDoesNothing(sup, [{ text: '3gin', type: StandardTokenType.Other }], 'b', 1); - testDoesNothing(sup, [{ text: 'bgin', type: StandardTokenType.Other }], '3', 2); - testDoesNothing(sup, [{ text: 'b3in', type: StandardTokenType.Other }], 'g', 3); - testDoesNothing(sup, [{ text: 'b3gn', type: StandardTokenType.Other }], 'i', 4); - testDoesNothing(sup, [{ text: 'b3gi', type: StandardTokenType.Other }], 'n', 5); - - testDoesNothing(sup, [{ text: 'begi', type: StandardTokenType.String }], 'n', 5); - - testAppends(sup, [{ text: '"', type: StandardTokenType.String }, { text: 'begi', type: StandardTokenType.Other }], 'n', 6, 'end'); - testDoesNothing(sup, [{ text: '"', type: StandardTokenType.String }, { text: 'begi', type: StandardTokenType.String }], 'n', 6); - - testAppends(sup, [{ text: '/*', type: StandardTokenType.String }], '*', 3, ' */'); - - testDoesNothing(sup, [{ text: 'begi', type: StandardTokenType.Other }, { text: 'end', type: StandardTokenType.Other }], 'n', 5); + assert.deepEqual(sup.getElectricCharacters(), ['}', ')']); }); test('matchOpenBracket', () => { @@ -108,12 +42,7 @@ suite('Editor Modes - Auto Indentation', () => { new RichEditBrackets(fakeLanguageIdentifier, [ ['{', '}'], ['(', ')'] - ]), [ - { open: '{', close: '}', notIn: ['string', 'comment'] }, - { open: '"', close: '"', notIn: ['string', 'comment'] }, - { open: 'begin', close: 'end', notIn: ['string'] } - ], - { docComment: { open: '/**', close: ' */' } } + ]) ); testDoesNothing(sup, [{ text: '\t{', type: StandardTokenType.Other }], '\t', 1); diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index 781ea1bb6a5..56dda979b62 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -109,9 +109,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', 'world!', '
' ].join('') @@ -122,9 +122,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', 'w', '
' ].join('') @@ -135,9 +135,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'Ciao', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -147,9 +147,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'iao', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -158,9 +158,9 @@ suite('Editor Modes - textToHtmlTokenizer', () => { tokenizeLineToHTML(text, lineTokens, colorMap, 4, 11, 4), [ '
', - ' ', + ' ', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -170,7 +170,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { [ '
', 'hello', - ' ', + ' ', '
' ].join('') ); @@ -193,6 +193,88 @@ suite('Editor Modes - textToHtmlTokenizer', () => { ].join('') ); }); + test('tokenizeLineToHTML handle spaces #35954', () => { + const text = ' Ciao hello world!'; + const lineTokens = new ViewLineTokens([ + new ViewLineToken( + 2, + ( + (1 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new ViewLineToken( + 6, + ( + (3 << MetadataConsts.FOREGROUND_OFFSET) + | ((FontStyle.Bold | FontStyle.Italic) << MetadataConsts.FONT_STYLE_OFFSET) + ) >>> 0 + ), + new ViewLineToken( + 9, + ( + (1 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new ViewLineToken( + 14, + ( + (4 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new ViewLineToken( + 15, + ( + (1 << MetadataConsts.FOREGROUND_OFFSET) + ) >>> 0 + ), + new ViewLineToken( + 21, + ( + (5 << MetadataConsts.FOREGROUND_OFFSET) + | ((FontStyle.Underline) << MetadataConsts.FONT_STYLE_OFFSET) + ) >>> 0 + ) + ]); + const colorMap = [null!, '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff']; + + assert.equal( + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 21, 4), + [ + '
', + '  ', + 'Ciao', + '   ', + 'hello', + ' ', + 'world!', + '
' + ].join('') + ); + + assert.equal( + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 17, 4), + [ + '
', + '  ', + 'Ciao', + '   ', + 'hello', + ' ', + 'wo', + '
' + ].join('') + ); + + assert.equal( + tokenizeLineToHTML(text, lineTokens, colorMap, 0, 3, 4), + [ + '
', + '  ', + 'C', + '
' + ].join('') + ); + }); }); diff --git a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts index 47538e39437..aec12368fa1 100644 --- a/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts +++ b/src/vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts @@ -114,6 +114,11 @@ suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => { assert.equal(mapper!.getWrappedLinesIndent(), ' \t'); }); + test('issue #75494: surrogate pairs', () => { + let factory = new CharacterHardWrappingLineMapperFactory('', ' ', ''); + assertLineMapping(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', WrappingIndent.Same); + }); + test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => { let factory = new CharacterHardWrappingLineMapperFactory('', ' ', ''); let mapper = assertLineMapping(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent); diff --git a/src/vs/loader.js b/src/vs/loader.js index e3bebe82544..a222ee64009 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -235,6 +235,17 @@ var AMDLoader; *--------------------------------------------------------------------------------------------*/ var AMDLoader; (function (AMDLoader) { + function ensureError(err) { + if (err instanceof Error) { + return err; + } + var result = new Error(err.message || String(err) || 'Unknown Error'); + if (err.stack) { + result.stack = err.stack; + } + return result; + } + AMDLoader.ensureError = ensureError; ; var ConfigurationOptionsUtil = /** @class */ (function () { function ConfigurationOptionsUtil() { @@ -244,22 +255,16 @@ var AMDLoader; */ ConfigurationOptionsUtil.validateConfigurationOptions = function (options) { function defaultOnError(err) { - if (err.errorCode === 'load') { + if (err.phase === 'loading') { console.error('Loading "' + err.moduleId + '" failed'); - console.error('Detail: ', err.detail); - if (err.detail && err.detail.stack) { - console.error(err.detail.stack); - } + console.error(err); console.error('Here are the modules that depend on it:'); console.error(err.neededBy); return; } - if (err.errorCode === 'factory') { + if (err.phase === 'factory') { console.error('The factory method of "' + err.moduleId + '" has thrown an exception'); - console.error(err.detail); - if (err.detail && err.detail.stack) { - console.error(err.detail.stack); - } + console.error(err); return; } } @@ -302,7 +307,7 @@ var AMDLoader; if (!Array.isArray(options.nodeModules)) { options.nodeModules = []; } - if (typeof options.nodeCachedData === 'object') { + if (options.nodeCachedData && typeof options.nodeCachedData === 'object') { if (typeof options.nodeCachedData.seed !== 'string') { options.nodeCachedData.seed = 'seed'; } @@ -310,7 +315,9 @@ var AMDLoader; options.nodeCachedData.writeDelay = 1000 * 7; } if (!options.nodeCachedData.path || typeof options.nodeCachedData.path !== 'string') { - options.onError('INVALID cached data configuration, \'path\' MUST be set'); + var err = ensureError(new Error('INVALID cached data configuration, \'path\' MUST be set')); + err.phase = 'configuration'; + options.onError(err); options.nodeCachedData = undefined; } } @@ -702,8 +709,11 @@ var AMDLoader; var recorder = moduleManager.getRecorder(); var cachedDataPath = that._getCachedDataPath(nodeCachedData, filename); var options = { filename: filename }; + var hashData; try { - options.cachedData = that._fs.readFileSync(cachedDataPath); + var data = that._fs.readFileSync(cachedDataPath); + hashData = data.slice(0, 16); + options.cachedData = data.slice(16); recorder.record(60 /* CachedDataFound */, cachedDataPath); } catch (_e) { @@ -717,7 +727,8 @@ var AMDLoader; var args = [this.exports, require, this, filename, dirname, process, _commonjsGlobal, Buffer]; var result = compileWrapper.apply(this.exports, args); // cached data aftermath - setTimeout(function () { return that._handleCachedData(script, cachedDataPath, !options.cachedData, moduleManager); }, Math.ceil(moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay * Math.random())); + that._handleCachedData(script, scriptSource, cachedDataPath, !options.cachedData, moduleManager); + that._verifyCachedData(script, scriptSource, cachedDataPath, hashData); return result; }; }; @@ -748,7 +759,7 @@ var AMDLoader; var vmScriptPathOrUri_1 = this._getElectronRendererScriptPathOrUri(normalizedScriptSrc_1); var wantsCachedData_1 = Boolean(opts.nodeCachedData); var cachedDataPath_1 = wantsCachedData_1 ? this._getCachedDataPath(opts.nodeCachedData, scriptSrc) : undefined; - this._readSourceAndCachedData(normalizedScriptSrc_1, cachedDataPath_1, recorder, function (err, data, cachedData) { + this._readSourceAndCachedData(normalizedScriptSrc_1, cachedDataPath_1, recorder, function (err, data, cachedData, hashData) { if (err) { errorback(err); return; @@ -763,7 +774,8 @@ var AMDLoader; scriptSource = nodeInstrumenter(scriptSource, normalizedScriptSrc_1); var scriptOpts = { filename: vmScriptPathOrUri_1, cachedData: cachedData }; var script = _this._createAndEvalScript(moduleManager, scriptSource, scriptOpts, callback, errorback); - _this._handleCachedData(script, cachedDataPath_1, wantsCachedData_1 && !cachedData, moduleManager); + _this._handleCachedData(script, scriptSource, cachedDataPath_1, wantsCachedData_1 && !cachedData, moduleManager); + _this._verifyCachedData(script, scriptSource, cachedDataPath_1, hashData); }); } }; @@ -808,13 +820,13 @@ var AMDLoader; var basename = this._path.basename(filename).replace(/\.js$/, ''); return this._path.join(config.path, basename + "-" + hash + ".code"); }; - NodeScriptLoader.prototype._handleCachedData = function (script, cachedDataPath, createCachedData, moduleManager) { + NodeScriptLoader.prototype._handleCachedData = function (script, scriptSource, cachedDataPath, createCachedData, moduleManager) { var _this = this; if (script.cachedDataRejected) { // cached data got rejected -> delete and re-create this._fs.unlink(cachedDataPath, function (err) { moduleManager.getRecorder().record(62 /* CachedDataRejected */, cachedDataPath); - _this._createAndWriteCachedData(script, cachedDataPath, moduleManager); + _this._createAndWriteCachedData(script, scriptSource, cachedDataPath, moduleManager); if (err) { moduleManager.getConfig().onError(err); } @@ -822,22 +834,29 @@ var AMDLoader; } else if (createCachedData) { // no cached data, but wanted - this._createAndWriteCachedData(script, cachedDataPath, moduleManager); + this._createAndWriteCachedData(script, scriptSource, cachedDataPath, moduleManager); } }; - NodeScriptLoader.prototype._createAndWriteCachedData = function (script, cachedDataPath, moduleManager) { + // Cached data format: | SOURCE_HASH | V8_CACHED_DATA | + // -SOURCE_HASH is the md5 hash of the JS source (always 16 bytes) + // -V8_CACHED_DATA is what v8 produces + NodeScriptLoader.prototype._createAndWriteCachedData = function (script, scriptSource, cachedDataPath, moduleManager) { var _this = this; var timeout = Math.ceil(moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay * (1 + Math.random())); var lastSize = -1; var iteration = 0; + var hashData = undefined; var createLoop = function () { setTimeout(function () { + if (!hashData) { + hashData = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest(); + } var cachedData = script.createCachedData(); if (cachedData.length === 0 || cachedData.length === lastSize || iteration >= 5) { return; } lastSize = cachedData.length; - _this._fs.writeFile(cachedDataPath, cachedData, function (err) { + _this._fs.writeFile(cachedDataPath, Buffer.concat([hashData, cachedData]), function (err) { if (err) { moduleManager.getConfig().onError(err); } @@ -858,15 +877,16 @@ var AMDLoader; } else { // cached data case: read both files in parallel - var source_1; - var cachedData_1; + var source_1 = undefined; + var cachedData_1 = undefined; + var hashData_1 = undefined; var steps_1 = 2; var step_1 = function (err) { if (err) { callback(err); } else if (--steps_1 === 0) { - callback(undefined, source_1, cachedData_1); + callback(undefined, source_1, cachedData_1, hashData_1); } }; this._fs.readFile(sourcePath, { encoding: 'utf8' }, function (err, data) { @@ -874,12 +894,39 @@ var AMDLoader; step_1(err); }); this._fs.readFile(cachedDataPath, function (err, data) { - cachedData_1 = data && data.length > 0 ? data : undefined; + if (!err && data && data.length > 0) { + hashData_1 = data.slice(0, 16); + cachedData_1 = data.slice(16); + recorder.record(60 /* CachedDataFound */, cachedDataPath); + } + else { + recorder.record(61 /* CachedDataMissed */, cachedDataPath); + } step_1(); // ignored: cached data is optional - recorder.record(err ? 61 /* CachedDataMissed */ : 60 /* CachedDataFound */, cachedDataPath); }); } }; + NodeScriptLoader.prototype._verifyCachedData = function (script, scriptSource, cachedDataPath, hashData) { + var _this = this; + if (!hashData) { + // nothing to do + return; + } + if (script.cachedDataRejected) { + // invalid anyways + return; + } + setTimeout(function () { + // check source hash - the contract is that file paths change when file content + // change (e.g use the commit or version id as cache path). this check is + // for violations of this contract. + var hashDataNow = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest(); + if (!hashData.equals(hashDataNow)) { + console.warn("FAILED TO VERIFY CACHED DATA. Deleting '" + cachedDataPath + "' now, but a RESTART IS REQUIRED"); + _this._fs.unlink(cachedDataPath, function (err) { return console.error("FAILED to unlink: '" + cachedDataPath + "'", err); }); + } + }, Math.ceil(5000 * (1 + Math.random()))); + }; NodeScriptLoader._BOM = 0xFEFF; NodeScriptLoader._PREFIX = '(function (require, define, __filename, __dirname) { '; NodeScriptLoader._SUFFIX = '\n});'; @@ -956,6 +1003,7 @@ var AMDLoader; this._errorback = errorback; this.moduleIdResolver = moduleIdResolver; this.exports = {}; + this.error = null; this.exportsPassedIn = false; this.unresolvedDependenciesCount = this.dependencies.length; this._isComplete = false; @@ -1007,11 +1055,11 @@ var AMDLoader; } } if (producedError) { - config.onError({ - errorCode: 'factory', - moduleId: this.strId, - detail: producedError - }); + var err = AMDLoader.ensureError(producedError); + err.phase = 'factory'; + err.moduleId = this.strId; + this.error = err; + config.onError(err); } this.dependencies = null; this._callback = null; @@ -1022,6 +1070,8 @@ var AMDLoader; * One of the direct dependencies or a transitive dependency has failed to load. */ Module.prototype.onDependencyError = function (err) { + this._isComplete = true; + this.error = err; if (this._errorback) { this._errorback(err); return true; @@ -1272,6 +1322,9 @@ var AMDLoader; if (!m.isComplete()) { throw new Error('Check dependency list! Synchronous require cannot resolve module \'' + _strModuleId + '\'. This module has not been resolved completely yet.'); } + if (m.error) { + throw m.error; + } return m.exports; }; ModuleManager.prototype.configure = function (params, shouldOverwrite) { @@ -1301,16 +1354,15 @@ var AMDLoader; this.defineModule(this._moduleIdProvider.getStrModuleId(moduleId), defineCall.dependencies, defineCall.callback, null, defineCall.stack); } }; - ModuleManager.prototype._createLoadError = function (moduleId, err) { + ModuleManager.prototype._createLoadError = function (moduleId, _err) { var _this = this; var strModuleId = this._moduleIdProvider.getStrModuleId(moduleId); var neededBy = (this._inverseDependencies2[moduleId] || []).map(function (intModuleId) { return _this._moduleIdProvider.getStrModuleId(intModuleId); }); - return { - errorCode: 'load', - moduleId: strModuleId, - neededBy: neededBy, - detail: err - }; + var err = AMDLoader.ensureError(_err); + err.phase = 'loading'; + err.moduleId = strModuleId; + err.neededBy = neededBy; + return err; }; /** * Callback from the scriptLoader when a module hasn't been loaded. @@ -1318,6 +1370,9 @@ var AMDLoader; */ ModuleManager.prototype._onLoadError = function (moduleId, err) { var error = this._createLoadError(moduleId, err); + if (!this._modules2[moduleId]) { + this._modules2[moduleId] = new Module(moduleId, this._moduleIdProvider.getStrModuleId(moduleId), [], function () { }, function () { }, null); + } // Find any 'local' error handlers, walk the entire chain of inverse dependencies if necessary. var seenModuleId = []; for (var i = 0, len = this._moduleIdProvider.getMaxModuleId(); i < len; i++) { @@ -1525,6 +1580,10 @@ var AMDLoader; } var dependencyModule = this._modules2[dependency.id]; if (dependencyModule && dependencyModule.isComplete()) { + if (dependencyModule.error) { + module.onDependencyError(dependencyModule.error); + return; + } module.unresolvedDependenciesCount--; continue; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index c6bc300217c..a1cf394ed39 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2649,6 +2649,11 @@ declare namespace monaco.editor { * Defaults to true. */ lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); + /** + * Controls the minimal number of visible leading and trailing lines surrounding the cursor. + * Defaults to 0. + */ + cursorSurroundingLines?: number; /** * Render last line number when the file ends with a newline. * Defaults to true. @@ -3297,6 +3302,7 @@ declare namespace monaco.editor { readonly ariaLabel: string; readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; + readonly cursorSurroundingLines: number; readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; @@ -3599,17 +3605,17 @@ declare namespace monaco.editor { * @param zone Zone to create * @return A unique identifier to the view zone. */ - addZone(zone: IViewZone): number; + addZone(zone: IViewZone): string; /** * Remove a zone * @param id A unique identifier to the view zone, as returned by the `addZone` call. */ - removeZone(id: number): void; + removeZone(id: string): void; /** * Change a zone's position. * The editor will rescan the `afterLineNumber` and `afterColumn` properties of a view zone. */ - layoutZone(id: number): void; + layoutZone(id: string): void; } /** @@ -4048,7 +4054,7 @@ declare namespace monaco.editor { * @param edits The edits to execute. * @param endCursorState Cursor state after the edits were applied. */ - executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: Selection[]): boolean; + executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean; /** * Execute multiple (concomitant) commands on the editor. * @param source The source of the call. @@ -4449,6 +4455,16 @@ declare namespace monaco.languages { */ export function registerFoldingRangeProvider(languageId: string, provider: FoldingRangeProvider): IDisposable; + /** + * Register a declaration provider + */ + export function registerDeclarationProvider(languageId: string, provider: DeclarationProvider): IDisposable; + + /** + * Register a selection range provider + */ + export function registerSelectionRangeProvider(languageId: string, provider: SelectionRangeProvider): IDisposable; + /** * Contains additional diagnostic information about the context in which * a [code action](#CodeActionProvider.provideCodeActions) is run. @@ -4545,7 +4561,9 @@ declare namespace monaco.languages { * * @deprecated Will be replaced by a better API soon. */ - __electricCharacterSupport?: IBracketElectricCharacterContribution; + __electricCharacterSupport?: { + docComment?: IDocComment; + }; } /** @@ -4620,10 +4638,6 @@ declare namespace monaco.languages { action: EnterAction; } - export interface IBracketElectricCharacterContribution { - docComment?: IDocComment; - } - /** * Definition of documentation comments (e.g. Javadoc/JSdoc) */ diff --git a/src/vs/nls.d.ts b/src/vs/nls.d.ts index 38bbd076068..3942ff08669 100644 --- a/src/vs/nls.d.ts +++ b/src/vs/nls.d.ts @@ -8,5 +8,12 @@ export interface ILocalizeInfo { comment: string[]; } +/** + * Localize a message. `message` can contain `{n}` notation where it is replaced by the nth value in `...args`. + */ export declare function localize(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string; + +/** + * Localize a message. `message` can contain `{n}` notation where it is replaced by the nth value in `...args`. + */ export declare function localize(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string; diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 76729934062..01d88e2ece8 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addClasses, createCSSRule, removeClasses, asDomUri } from 'vs/base/browser/dom'; +import { addClasses, createCSSRule, removeClasses, asCSSUrl } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; import { ActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction } from 'vs/base/common/actions'; @@ -21,7 +21,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati class AlternativeKeyEmitter extends Emitter { private readonly _subscriptions = new DisposableStore(); - private _isPressed: boolean; + private _isPressed: boolean = false; private static instance: AlternativeKeyEmitter; private _suppressAltKeyUp: boolean = false; @@ -137,7 +137,7 @@ export class MenuEntryActionViewItem extends ActionViewItem { static readonly ICON_PATH_TO_CSS_RULES: Map = new Map(); - private _wantsAltCommand: boolean; + private _wantsAltCommand: boolean = false; private readonly _itemClassDispose = this._register(new MutableDisposable()); private readonly _altKey: AlternativeKeyEmitter; @@ -244,8 +244,8 @@ export class MenuEntryActionViewItem extends ActionViewItem { iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; } else { iconClass = ids.nextId(); - createCSSRule(`.icon.${iconClass}`, `background-image: url("${asDomUri(item.iconLocation.light || item.iconLocation.dark).toString()}")`); - createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${asDomUri(item.iconLocation.dark).toString()}")`); + createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.light || item.iconLocation.dark)}`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(item.iconLocation.dark)}`); MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index e0ee881b773..352a1ed945f 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -31,8 +31,8 @@ class Menu extends Disposable implements IMenu { private readonly _onDidChange = this._register(new Emitter()); - private _menuGroups: MenuItemGroup[]; - private _contextKeys: Set; + private _menuGroups!: MenuItemGroup[]; + private _contextKeys!: Set; constructor( private readonly _id: MenuId, diff --git a/src/vs/platform/browser/contextScopedHistoryWidget.ts b/src/vs/platform/browser/contextScopedHistoryWidget.ts index 5e5e8603683..8625db10ee8 100644 --- a/src/vs/platform/browser/contextScopedHistoryWidget.ts +++ b/src/vs/platform/browser/contextScopedHistoryWidget.ts @@ -10,6 +10,7 @@ import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview import { IHistoryNavigationWidget } from 'vs/base/browser/history'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ReplaceInput, IReplaceInputOptions } from 'vs/base/browser/ui/findinput/replaceInput'; export const HistoryNavigationWidgetContext = 'historyNavigationWidget'; export const HistoryNavigationEnablementContext = 'historyNavigationEnabled'; @@ -60,6 +61,16 @@ export class ContextScopedFindInput extends FindInput { super(container, contextViewProvider, showFindOptions, options); this._register(createAndBindHistoryNavigationWidgetScopedContextKeyService(contextKeyService, { target: this.inputBox.element, historyNavigator: this.inputBox }).scopedContextKeyService); } +} + +export class ContextScopedReplaceInput extends ReplaceInput { + + constructor(container: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, options: IReplaceInputOptions, + @IContextKeyService contextKeyService: IContextKeyService, showReplaceOptions: boolean = false + ) { + super(container, contextViewProvider, showReplaceOptions, options); + this._register(createAndBindHistoryNavigationWidgetScopedContextKeyService(contextKeyService, { target: this.inputBox.element, historyNavigator: this.inputBox }).scopedContextKeyService); + } } diff --git a/src/vs/platform/clipboard/browser/clipboardService.ts b/src/vs/platform/clipboard/browser/clipboardService.ts index 9502c4bdc97..c5e8a2676d2 100644 --- a/src/vs/platform/clipboard/browser/clipboardService.ts +++ b/src/vs/platform/clipboard/browser/clipboardService.ts @@ -10,15 +10,23 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class BrowserClipboardService implements IClipboardService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private _internalResourcesClipboard: URI[] | undefined; async writeText(text: string, type?: string): Promise { + if (type) { + return; // TODO@sbatten + } + return navigator.clipboard.writeText(text); } async readText(type?: string): Promise { + if (type) { + return ''; // TODO@sbatten + } + return navigator.clipboard.readText(); } @@ -46,4 +54,4 @@ export class BrowserClipboardService implements IClipboardService { } } -registerSingleton(IClipboardService, BrowserClipboardService, true); \ No newline at end of file +registerSingleton(IClipboardService, BrowserClipboardService, true); diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 22a97c872cd..7249d295d87 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -5,7 +5,7 @@ import * as objects from 'vs/base/common/objects'; import * as types from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -124,7 +124,7 @@ export interface IConfigurationData { defaults: IConfigurationModel; user: IConfigurationModel; workspace: IConfigurationModel; - folders: { [folder: string]: IConfigurationModel }; + folders: [UriComponents, IConfigurationModel][]; } export function compare(from: IConfigurationModel, to: IConfigurationModel): { added: string[], removed: string[], updated: string[] } { diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 1765a06822f..373a2e1b086 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -8,7 +8,7 @@ import { ResourceMap } from 'vs/base/common/map'; import * as arrays from 'vs/base/common/arrays'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { OVERRIDE_PROPERTY_PATTERN, ConfigurationScope, IConfigurationRegistry, Extensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, IConfigurationChangeEvent, ConfigurationTarget, removeFromValueTree, toOverrides } from 'vs/platform/configuration/common/configuration'; import { Workspace } from 'vs/platform/workspace/common/workspace'; @@ -545,11 +545,11 @@ export class Configuration { overrides: this._workspaceConfiguration.overrides, keys: this._workspaceConfiguration.keys }, - folders: this._folderConfigurations.keys().reduce((result, folder) => { + folders: this._folderConfigurations.keys().reduce<[UriComponents, IConfigurationModel][]>((result, folder) => { const { contents, overrides, keys } = this._folderConfigurations.get(folder)!; - result[folder.toString()] = { contents, overrides, keys }; + result.push([folder, { contents, overrides, keys }]); return result; - }, Object.create({})) + }, []) }; } diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 0d27f890edf..329276d4e46 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -99,6 +99,10 @@ export const enum ConfigurationScope { * Resource specific configuration, which can be configured in the user, workspace or folder settings. */ RESOURCE, + /** + * Machine specific configuration that can also be configured in workspace or folder settings. + */ + MACHINE_OVERRIDABLE, } export interface IConfigurationPropertySchema extends IJSONSchema { diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index 4d3479aae8b..8ab9ccc2d82 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -85,6 +85,10 @@ export class ContextMenuHandler { menu.onDidBlur(() => this.contextViewService.hideContextView(true), null, menuDisposables); domEvent(window, EventType.BLUR)(() => { this.contextViewService.hideContextView(true); }, null, menuDisposables); domEvent(window, EventType.MOUSE_DOWN)((e: MouseEvent) => { + if (e.defaultPrevented) { + return; + } + let event = new StandardMouseEvent(e); let element: HTMLElement | null = event.target; diff --git a/src/vs/platform/diagnostics/common/diagnosticsService.ts b/src/vs/platform/diagnostics/common/diagnostics.ts similarity index 69% rename from src/vs/platform/diagnostics/common/diagnosticsService.ts rename to src/vs/platform/diagnostics/common/diagnostics.ts index ae854fcfb7c..9adfc578f64 100644 --- a/src/vs/platform/diagnostics/common/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/common/diagnostics.ts @@ -5,8 +5,6 @@ import { UriComponents } from 'vs/base/common/uri'; import { ProcessItem } from 'vs/base/common/processes'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; import { IWorkspace } from 'vs/platform/workspace/common/workspace'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -63,18 +61,10 @@ export interface PerformanceInfo { workspaceInfo?: string; } -export const ID = 'diagnosticsService'; -export const IDiagnosticsService = createDecorator(ID); - -export interface IDiagnosticsService { - _serviceBrand: any; - - getPerformanceInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; - getSystemInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; - getDiagnostics(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; - reportWorkspaceStats(workspace: IWorkspace): Promise; +export interface IWorkspaceInformation extends IWorkspace { + telemetryId: string | undefined; } export function isRemoteDiagnosticError(x: any): x is IRemoteDiagnosticError { return !!x.hostName && !!x.errorMessage; -} \ No newline at end of file +} diff --git a/src/vs/platform/diagnostics/node/diagnosticsIpc.ts b/src/vs/platform/diagnostics/node/diagnosticsIpc.ts index 28cabc55e7b..76af1758163 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsIpc.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsIpc.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IDiagnosticsService, IRemoteDiagnosticInfo, IRemoteDiagnosticError, SystemInfo, PerformanceInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IRemoteDiagnosticInfo, IRemoteDiagnosticError, SystemInfo, PerformanceInfo } from 'vs/platform/diagnostics/common/diagnostics'; +import { IDiagnosticsService } from './diagnosticsService'; import { Event } from 'vs/base/common/event'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; @@ -36,7 +37,7 @@ export class DiagnosticsChannel implements IServerChannel { export class DiagnosticsService implements IDiagnosticsService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; constructor(private channel: IChannel) { } @@ -55,4 +56,4 @@ export class DiagnosticsService implements IDiagnosticsService { public reportWorkspaceStats(workspace: IWorkspace): Promise { return this.channel.call('reportWorkspaceStats', workspace); } -} \ No newline at end of file +} diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 9e74621348c..b94ff1f9145 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as osLib from 'os'; import { virtualMachineHint } from 'vs/base/node/id'; -import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, IDiagnosticsService, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IMachineInfo, WorkspaceStats, WorkspaceStatItem, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; import { readdir, stat, exists, readFile } from 'fs'; import { join, basename } from 'vs/base/common/path'; import { parse, ParseError } from 'vs/base/common/json'; @@ -16,8 +16,20 @@ import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ProcessItem } from 'vs/base/common/processes'; import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; -import { IWorkspace } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const ID = 'diagnosticsService'; +export const IDiagnosticsService = createDecorator(ID); + +export interface IDiagnosticsService { + _serviceBrand: any; + + getPerformanceInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; + getSystemInfo(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; + getDiagnostics(mainProcessInfo: IMainProcessInfo, remoteInfo: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]): Promise; + reportWorkspaceStats(workspace: IWorkspaceInformation): Promise; +} export interface VersionInfo { vscodeVersion: string; @@ -514,7 +526,7 @@ export class DiagnosticsService implements IDiagnosticsService { } } - public async reportWorkspaceStats(workspace: IWorkspace): Promise { + public async reportWorkspaceStats(workspace: IWorkspaceInformation): Promise { workspace.folders.forEach(folder => { const folderUri = URI.revive(folder.uri); if (folderUri.scheme === 'file') { @@ -525,16 +537,19 @@ export class DiagnosticsService implements IDiagnosticsService { count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; type WorkspaceStatsClassification = { + 'workspace.id': { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; fileTypes: WorkspaceStatItemClassification; configTypes: WorkspaceStatItemClassification; launchConfigs: WorkspaceStatItemClassification; }; type WorkspaceStatsEvent = { + 'workspace.id': string | undefined; fileTypes: WorkspaceStatItem[]; configTypes: WorkspaceStatItem[]; launchConfigs: WorkspaceStatItem[]; }; this.telemetryService.publicLog2('workspace.stats', { + 'workspace.id': workspace.telemetryId, fileTypes: stats.fileTypes, configTypes: stats.configFiles, launchConfigs: stats.launchConfigFiles @@ -545,4 +560,4 @@ export class DiagnosticsService implements IDiagnosticsService { } }); } -} \ No newline at end of file +} diff --git a/src/vs/platform/dialogs/browser/dialogService.ts b/src/vs/platform/dialogs/browser/dialogService.ts index 5a906768029..347e242dd6d 100644 --- a/src/vs/platform/dialogs/browser/dialogService.ts +++ b/src/vs/platform/dialogs/browser/dialogService.ts @@ -40,32 +40,35 @@ export class DialogService implements IDialogService { buttons.push(nls.localize('cancelButton', "Cancel")); } - const severity = this.getSeverity(confirmation.type || 'none'); - const result = await this.show(severity, confirmation.message, buttons, { cancelId: 1, detail: confirmation.detail }); + const dialogDisposables = new DisposableStore(); + const dialog = new Dialog( + this.layoutService.container, + confirmation.message, + buttons, + { + detail: confirmation.detail, + cancelId: 1, + type: confirmation.type, + keyEventProcessor: (event: StandardKeyboardEvent) => { + EventHelper.stop(event, true); + }, + checkboxChecked: confirmation.checkbox ? confirmation.checkbox.checked : undefined, + checkboxLabel: confirmation.checkbox ? confirmation.checkbox.label : undefined + }); - return { confirmed: result === 0 }; - } + dialogDisposables.add(dialog); + dialogDisposables.add(attachDialogStyler(dialog, this.themeService)); - private getSeverity(type: DialogType): Severity { - switch (type) { - case 'error': - return Severity.Error; - case 'warning': - return Severity.Warning; - case 'question': - case 'info': - return Severity.Info; - case 'none': - default: - return Severity.Ignore; - } + const result = await dialog.show(); + dialogDisposables.dispose(); + + return { confirmed: result.button === 0, checkboxChecked: result.checkboxChecked }; } private getDialogType(severity: Severity): DialogType { return (severity === Severity.Info) ? 'question' : (severity === Severity.Error) ? 'error' : (severity === Severity.Warning) ? 'warning' : 'none'; } - async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { this.logService.trace('DialogService#show', message); @@ -86,9 +89,9 @@ export class DialogService implements IDialogService { dialogDisposables.add(dialog); dialogDisposables.add(attachDialogStyler(dialog, this.themeService)); - const choice = await dialog.show(); + const result = await dialog.show(); dialogDisposables.dispose(); - return choice; + return result.button; } } diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index ea57ce91233..6999633d9f0 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -127,6 +127,8 @@ export const IDialogService = createDecorator('dialogService'); export interface IDialogOptions { cancelId?: number; detail?: string; + checkboxLabel?: string; + checkboxChecked?: boolean; } /** @@ -234,4 +236,4 @@ export function getConfirmMessage(start: string, resourcesToConfirm: URI[]): str message.push(''); return message.join('\n'); -} \ No newline at end of file +} diff --git a/src/vs/platform/download/common/downloadService.ts b/src/vs/platform/download/common/downloadService.ts index a2b8365df4c..54d2f1a9835 100644 --- a/src/vs/platform/download/common/downloadService.ts +++ b/src/vs/platform/download/common/downloadService.ts @@ -20,7 +20,7 @@ export class DownloadService implements IDownloadService { ) { } async download(resource: URI, target: URI, cancellationToken: CancellationToken = CancellationToken.None): Promise { - if (resource.scheme === Schemas.file) { + if (resource.scheme === Schemas.file || resource.scheme === Schemas.vscodeRemote) { await this.fileService.copy(resource, target); return; } diff --git a/src/vs/platform/driver/browser/baseDriver.ts b/src/vs/platform/driver/browser/baseDriver.ts new file mode 100644 index 00000000000..38b5626a500 --- /dev/null +++ b/src/vs/platform/driver/browser/baseDriver.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom'; +import { coalesce } from 'vs/base/common/arrays'; +import { IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; + +function serializeElement(element: Element, recursive: boolean): IElement { + const attributes = Object.create(null); + + for (let j = 0; j < element.attributes.length; j++) { + const attr = element.attributes.item(j); + if (attr) { + attributes[attr.name] = attr.value; + } + } + + const children: IElement[] = []; + + if (recursive) { + for (let i = 0; i < element.children.length; i++) { + const child = element.children.item(i); + if (child) { + children.push(serializeElement(child, true)); + } + } + } + + const { left, top } = getTopLeftOffset(element as HTMLElement); + + return { + tagName: element.tagName, + className: element.className, + textContent: element.textContent || '', + attributes, + children, + left, + top + }; +} + +export abstract class BaseWindowDriver implements IWindowDriver { + + constructor() { } + + abstract click(selector: string, xoffset?: number, yoffset?: number): Promise; + abstract doubleClick(selector: string): Promise; + + async setValue(selector: string, text: string): Promise { + const element = document.querySelector(selector); + + if (!element) { + return Promise.reject(new Error(`Element not found: ${selector}`)); + } + + const inputElement = element as HTMLInputElement; + inputElement.value = text; + + const event = new Event('input', { bubbles: true, cancelable: true }); + inputElement.dispatchEvent(event); + } + + async getTitle(): Promise { + return document.title; + } + + async isActiveElement(selector: string): Promise { + const element = document.querySelector(selector); + + if (element !== document.activeElement) { + const chain: string[] = []; + let el = document.activeElement; + + while (el) { + const tagName = el.tagName; + const id = el.id ? `#${el.id}` : ''; + const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join(''); + chain.unshift(`${tagName}${id}${classes}`); + + el = el.parentElement; + } + + throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`); + } + + return true; + } + + async getElements(selector: string, recursive: boolean): Promise { + const query = document.querySelectorAll(selector); + const result: IElement[] = []; + + for (let i = 0; i < query.length; i++) { + const element = query.item(i); + result.push(serializeElement(element, recursive)); + } + + return result; + } + + async getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }> { + const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined; + return this._getElementXY(selector, offset); + } + + async typeInEditor(selector: string, text: string): Promise { + const element = document.querySelector(selector); + + if (!element) { + throw new Error(`Editor not found: ${selector}`); + } + + const textarea = element as HTMLTextAreaElement; + const start = textarea.selectionStart; + const newStart = start + text.length; + const value = textarea.value; + const newValue = value.substr(0, start) + text + value.substr(start); + + textarea.value = newValue; + textarea.setSelectionRange(newStart, newStart); + + const event = new Event('input', { 'bubbles': true, 'cancelable': true }); + textarea.dispatchEvent(event); + } + + async getTerminalBuffer(selector: string): Promise { + const element = document.querySelector(selector); + + if (!element) { + throw new Error(`Terminal not found: ${selector}`); + } + + const xterm = (element as any).xterm; + + if (!xterm) { + throw new Error(`Xterm not found: ${selector}`); + } + + const lines: string[] = []; + + for (let i = 0; i < xterm.buffer.length; i++) { + lines.push(xterm.buffer.getLine(i)!.translateToString(true)); + } + + return lines; + } + + async writeInTerminal(selector: string, text: string): Promise { + const element = document.querySelector(selector); + + if (!element) { + throw new Error(`Element not found: ${selector}`); + } + + const xterm = (element as any).xterm; + + if (!xterm) { + throw new Error(`Xterm not found: ${selector}`); + } + + xterm._core._coreService.triggerDataEvent(text); + } + + protected async _getElementXY(selector: string, offset?: { x: number, y: number }): Promise<{ x: number; y: number; }> { + const element = document.querySelector(selector); + + if (!element) { + return Promise.reject(new Error(`Element not found: ${selector}`)); + } + + const { left, top } = getTopLeftOffset(element as HTMLElement); + const { width, height } = getClientArea(element as HTMLElement); + let x: number, y: number; + + if (offset) { + x = left + offset.x; + y = top + offset.y; + } else { + x = left + (width / 2); + y = top + (height / 2); + } + + x = Math.round(x); + y = Math.round(y); + + return { x, y }; + } + + abstract async openDevTools(): Promise; +} diff --git a/src/vs/platform/driver/browser/driver.ts b/src/vs/platform/driver/browser/driver.ts new file mode 100644 index 00000000000..f13d45a5f13 --- /dev/null +++ b/src/vs/platform/driver/browser/driver.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver'; + +class BrowserWindowDriver extends BaseWindowDriver { + click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise { + throw new Error('Method not implemented.'); + } + doubleClick(selector: string): Promise { + throw new Error('Method not implemented.'); + } + openDevTools(): Promise { + throw new Error('Method not implemented.'); + } +} + +export async function registerWindowDriver(): Promise { + (window).driver = new BrowserWindowDriver(); + + return toDisposable(() => { + return { dispose: () => { } }; + }); +} diff --git a/src/vs/platform/driver/common/driver.ts b/src/vs/platform/driver/common/driver.ts new file mode 100644 index 00000000000..29fde875994 --- /dev/null +++ b/src/vs/platform/driver/common/driver.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +// !! Do not remove the following START and END markers, they are parsed by the smoketest build + +//*START +export interface IElement { + tagName: string; + className: string; + textContent: string; + attributes: { [name: string]: string; }; + children: IElement[]; + top: number; + left: number; +} + +export interface IDriver { + _serviceBrand: any; + + getWindowIds(): Promise; + capturePage(windowId: number): Promise; + reloadWindow(windowId: number): Promise; + exitApplication(): Promise; + dispatchKeybinding(windowId: number, keybinding: string): Promise; + click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; + doubleClick(windowId: number, selector: string): Promise; + setValue(windowId: number, selector: string, text: string): Promise; + getTitle(windowId: number): Promise; + isActiveElement(windowId: number, selector: string): Promise; + getElements(windowId: number, selector: string, recursive?: boolean): Promise; + getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }>; + typeInEditor(windowId: number, selector: string, text: string): Promise; + getTerminalBuffer(windowId: number, selector: string): Promise; + writeInTerminal(windowId: number, selector: string, text: string): Promise; +} +//*END + +export const ID = 'driverService'; +export const IDriver = createDecorator(ID); + +export interface IWindowDriver { + click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; + doubleClick(selector: string): Promise; + setValue(selector: string, text: string): Promise; + getTitle(): Promise; + isActiveElement(selector: string): Promise; + getElements(selector: string, recursive: boolean): Promise; + getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }>; + typeInEditor(selector: string, text: string): Promise; + getTerminalBuffer(selector: string): Promise; + writeInTerminal(selector: string, text: string): Promise; +} diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index a956739bed7..09e2d6b3379 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -4,55 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IWindowDriver, IElement, WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver'; +import { WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom'; import * as electron from 'electron'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { Terminal } from 'xterm'; import { timeout } from 'vs/base/common/async'; -import { coalesce } from 'vs/base/common/arrays'; +import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver'; -function serializeElement(element: Element, recursive: boolean): IElement { - const attributes = Object.create(null); - - for (let j = 0; j < element.attributes.length; j++) { - const attr = element.attributes.item(j); - if (attr) { - attributes[attr.name] = attr.value; - } - } - - const children: IElement[] = []; - - if (recursive) { - for (let i = 0; i < element.children.length; i++) { - const child = element.children.item(i); - if (child) { - children.push(serializeElement(child, true)); - } - } - } - - const { left, top } = getTopLeftOffset(element as HTMLElement); - - return { - tagName: element.tagName, - className: element.className, - textContent: element.textContent || '', - attributes, - children, - left, - top - }; -} - -class WindowDriver implements IWindowDriver { +class WindowDriver extends BaseWindowDriver { constructor( @IWindowService private readonly windowService: IWindowService - ) { } + ) { + super(); + } click(selector: string, xoffset?: number, yoffset?: number): Promise { const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined; @@ -63,31 +29,6 @@ class WindowDriver implements IWindowDriver { return this._click(selector, 2); } - private async _getElementXY(selector: string, offset?: { x: number, y: number }): Promise<{ x: number; y: number; }> { - const element = document.querySelector(selector); - - if (!element) { - return Promise.reject(new Error(`Element not found: ${selector}`)); - } - - const { left, top } = getTopLeftOffset(element as HTMLElement); - const { width, height } = getClientArea(element as HTMLElement); - let x: number, y: number; - - if (offset) { - x = left + offset.x; - y = top + offset.y; - } else { - x = left + (width / 2); - y = top + (height / 2); - } - - x = Math.round(x); - y = Math.round(y); - - return { x, y }; - } - private async _click(selector: string, clickCount: number, offset?: { x: number, y: number }): Promise { const { x, y } = await this._getElementXY(selector, offset); @@ -99,116 +40,6 @@ class WindowDriver implements IWindowDriver { await timeout(100); } - async setValue(selector: string, text: string): Promise { - const element = document.querySelector(selector); - - if (!element) { - return Promise.reject(new Error(`Element not found: ${selector}`)); - } - - const inputElement = element as HTMLInputElement; - inputElement.value = text; - - const event = new Event('input', { bubbles: true, cancelable: true }); - inputElement.dispatchEvent(event); - } - - async getTitle(): Promise { - return document.title; - } - - async isActiveElement(selector: string): Promise { - const element = document.querySelector(selector); - - if (element !== document.activeElement) { - const chain: string[] = []; - let el = document.activeElement; - - while (el) { - const tagName = el.tagName; - const id = el.id ? `#${el.id}` : ''; - const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join(''); - chain.unshift(`${tagName}${id}${classes}`); - - el = el.parentElement; - } - - throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`); - } - - return true; - } - - async getElements(selector: string, recursive: boolean): Promise { - const query = document.querySelectorAll(selector); - const result: IElement[] = []; - - for (let i = 0; i < query.length; i++) { - const element = query.item(i); - result.push(serializeElement(element, recursive)); - } - - return result; - } - - async typeInEditor(selector: string, text: string): Promise { - const element = document.querySelector(selector); - - if (!element) { - throw new Error(`Editor not found: ${selector}`); - } - - const textarea = element as HTMLTextAreaElement; - const start = textarea.selectionStart; - const newStart = start + text.length; - const value = textarea.value; - const newValue = value.substr(0, start) + text + value.substr(start); - - textarea.value = newValue; - textarea.setSelectionRange(newStart, newStart); - - const event = new Event('input', { 'bubbles': true, 'cancelable': true }); - textarea.dispatchEvent(event); - } - - async getTerminalBuffer(selector: string): Promise { - const element = document.querySelector(selector); - - if (!element) { - throw new Error(`Terminal not found: ${selector}`); - } - - const xterm: Terminal = (element as any).xterm; - - if (!xterm) { - throw new Error(`Xterm not found: ${selector}`); - } - - const lines: string[] = []; - - for (let i = 0; i < xterm.buffer.length; i++) { - lines.push(xterm.buffer.getLine(i)!.translateToString(true)); - } - - return lines; - } - - async writeInTerminal(selector: string, text: string): Promise { - const element = document.querySelector(selector); - - if (!element) { - throw new Error(`Element not found: ${selector}`); - } - - const xterm: Terminal = (element as any).xterm; - - if (!xterm) { - throw new Error(`Xterm not found: ${selector}`); - } - - xterm._core._coreService.triggerDataEvent(text); - } - async openDevTools(): Promise { await this.windowService.openDevTools({ mode: 'detach' }); } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 2b341fb2ed6..8096b8f6abd 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDriver, DriverChannel, IElement, WindowDriverChannelClient, IWindowDriverRegistry, WindowDriverRegistryChannel, IWindowDriver, IDriverOptions } from 'vs/platform/driver/node/driver'; +import { DriverChannel, WindowDriverChannelClient, IWindowDriverRegistry, WindowDriverRegistryChannel, IDriverOptions } from 'vs/platform/driver/node/driver'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { serve as serveNet } from 'vs/base/parts/ipc/node/ipc.net'; import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -17,6 +17,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; +import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; function isSilentKeyCode(keyCode: KeyCode) { return keyCode < KeyCode.KEY_0; @@ -163,6 +164,11 @@ export class Driver implements IDriver, IWindowDriverRegistry { return await windowDriver.getElements(selector, recursive); } + async getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }> { + const windowDriver = await this.getWindowDriver(windowId); + return await windowDriver.getElementXY(selector, xoffset, yoffset); + } + async typeInEditor(windowId: number, selector: string, text: string): Promise { const windowDriver = await this.getWindowDriver(windowId); await windowDriver.typeInEditor(selector, text); diff --git a/src/vs/platform/driver/node/driver.ts b/src/vs/platform/driver/node/driver.ts index e77790a5183..2c525c0e694 100644 --- a/src/vs/platform/driver/node/driver.ts +++ b/src/vs/platform/driver/node/driver.ts @@ -5,45 +5,9 @@ import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; - -export const ID = 'driverService'; -export const IDriver = createDecorator(ID); - -// !! Do not remove the following START and END markers, they are parsed by the smoketest build - -//*START -export interface IElement { - tagName: string; - className: string; - textContent: string; - attributes: { [name: string]: string; }; - children: IElement[]; - top: number; - left: number; -} - -export interface IDriver { - _serviceBrand: any; - - getWindowIds(): Promise; - capturePage(windowId: number): Promise; - reloadWindow(windowId: number): Promise; - exitApplication(): Promise; - dispatchKeybinding(windowId: number, keybinding: string): Promise; - click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; - doubleClick(windowId: number, selector: string): Promise; - setValue(windowId: number, selector: string, text: string): Promise; - getTitle(windowId: number): Promise; - isActiveElement(windowId: number, selector: string): Promise; - getElements(windowId: number, selector: string, recursive?: boolean): Promise; - typeInEditor(windowId: number, selector: string, text: string): Promise; - getTerminalBuffer(windowId: number, selector: string): Promise; - writeInTerminal(windowId: number, selector: string, text: string): Promise; -} -//*END +import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; export class DriverChannel implements IServerChannel { @@ -66,6 +30,7 @@ export class DriverChannel implements IServerChannel { case 'getTitle': return this.driver.getTitle(arg[0]); case 'isActiveElement': return this.driver.isActiveElement(arg[0], arg[1]); case 'getElements': return this.driver.getElements(arg[0], arg[1], arg[2]); + case 'getElementXY': return this.driver.getElementXY(arg[0], arg[1], arg[2]); case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1], arg[2]); case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg[0], arg[1]); case 'writeInTerminal': return this.driver.writeInTerminal(arg[0], arg[1], arg[2]); @@ -125,6 +90,10 @@ export class DriverChannelClient implements IDriver { return this.channel.call('getElements', [windowId, selector, recursive]); } + getElementXY(windowId: number, selector: string, xoffset: number | undefined, yoffset: number | undefined): Promise<{ x: number, y: number }> { + return this.channel.call('getElementXY', [windowId, selector, xoffset, yoffset]); + } + typeInEditor(windowId: number, selector: string, text: string): Promise { return this.channel.call('typeInEditor', [windowId, selector, text]); } @@ -180,18 +149,6 @@ export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry } } -export interface IWindowDriver { - click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; - doubleClick(selector: string): Promise; - setValue(selector: string, text: string): Promise; - getTitle(): Promise; - isActiveElement(selector: string): Promise; - getElements(selector: string, recursive: boolean): Promise; - typeInEditor(selector: string, text: string): Promise; - getTerminalBuffer(selector: string): Promise; - writeInTerminal(selector: string, text: string): Promise; -} - export class WindowDriverChannel implements IServerChannel { constructor(private driver: IWindowDriver) { } @@ -208,6 +165,7 @@ export class WindowDriverChannel implements IServerChannel { case 'getTitle': return this.driver.getTitle(); case 'isActiveElement': return this.driver.isActiveElement(arg); case 'getElements': return this.driver.getElements(arg[0], arg[1]); + case 'getElementXY': return this.driver.getElementXY(arg[0], arg[1], arg[2]); case 'typeInEditor': return this.driver.typeInEditor(arg[0], arg[1]); case 'getTerminalBuffer': return this.driver.getTerminalBuffer(arg); case 'writeInTerminal': return this.driver.writeInTerminal(arg[0], arg[1]); @@ -247,6 +205,10 @@ export class WindowDriverChannelClient implements IWindowDriver { return this.channel.call('getElements', [selector, recursive]); } + getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number, y: number }> { + return this.channel.call('getElementXY', [selector, xoffset, yoffset]); + } + typeInEditor(selector: string, text: string): Promise { return this.channel.call('typeInEditor', [selector, text]); } diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index a6c9eb9d11c..b13a82f4447 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; export interface ParsedArgs { @@ -38,6 +38,7 @@ export interface ParsedArgs { 'builtin-extensions-dir'?: string; extensionDevelopmentPath?: string | string[]; // one or more local paths or URIs extensionTestsPath?: string; // either a local path or a URI + 'extension-development-confirm-save'?: boolean; 'inspect-extensions'?: string; 'inspect-brk-extensions'?: string; debugId?: string; @@ -97,7 +98,8 @@ export interface IExtensionHostDebugParams extends IDebugParams { export const BACKUPS = 'Backups'; export interface IEnvironmentService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; args: ParsedArgs; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index a1d33c98577..5e22e54d396 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as minimist from 'minimist'; +import * as minimist from 'vscode-minimist'; import * as os from 'os'; import { localize } from 'vs/nls'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; @@ -69,6 +69,7 @@ export const options: Option[] = [ { id: 'locate-extension', type: 'string' }, { id: 'extensionDevelopmentPath', type: 'string' }, { id: 'extensionTestsPath', type: 'string' }, + { id: 'extension-development-confirm-save', type: 'boolean' }, { id: 'debugId', type: 'string' }, { id: 'inspect-search', type: 'string', deprecates: 'debugSearch' }, { id: 'inspect-brk-search', type: 'string', deprecates: 'debugBrkSearch' }, @@ -132,6 +133,10 @@ export function parseArgs(args: string[], isOptionSupported = (_: Option) => tru delete parsedArgs[o.deprecates]; } } + + // https://github.com/microsoft/vscode/issues/58177 + parsedArgs._ = parsedArgs._.filter(arg => arg.length > 0); + return parsedArgs; } diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index e4635b7fc32..c5c4e66272c 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -16,10 +16,11 @@ import { toLocalISOString } from 'vs/base/common/date'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { URI } from 'vs/base/common/uri'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; // Read this before there's any chance it is overwritten // Related to https://github.com/Microsoft/vscode/issues/30624 -const xdgRuntimeDir = process.env['XDG_RUNTIME_DIR']; +export const xdgRuntimeDir = process.env['XDG_RUNTIME_DIR']; function getNixIPCHandle(userDataPath: string, type: string): string { const vscodePortable = process.env['VSCODE_PORTABLE']; @@ -76,7 +77,7 @@ function getCLIPath(execPath: string, appRoot: string, isBuilt: boolean): string export class EnvironmentService implements IEnvironmentService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; get args(): ParsedArgs { return this._args; } diff --git a/src/vs/platform/environment/test/node/environmentService.test.ts b/src/vs/platform/environment/test/node/environmentService.test.ts index dd34d5a9149..39d83f414b3 100644 --- a/src/vs/platform/environment/test/node/environmentService.test.ts +++ b/src/vs/platform/environment/test/node/environmentService.test.ts @@ -52,4 +52,15 @@ suite('EnvironmentService', () => { assert.equal(parse(['--user-data-dir', './dir'], { cwd: () => '/foo', env: { 'VSCODE_CWD': '/bar' } }), path.resolve('/bar/dir'), 'should use VSCODE_CWD as the cwd when --user-data-dir is specified'); }); + + // https://github.com/microsoft/vscode/issues/78440 + test('careful with boolean file names', function () { + let actual = parseArgs(['-r', 'arg.txt']); + assert(actual['reuse-window']); + assert.deepEqual(actual._, ['arg.txt']); + + actual = parseArgs(['-r', 'true.txt']); + assert(actual['reuse-window']); + assert.deepEqual(actual._, ['true.txt']); + }); }); diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index e291d436115..2d39c512975 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -817,4 +817,4 @@ export async function resolveMarketplaceHeaders(version: string, environmentServ return headers; -} \ No newline at end of file +} diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index b075553498d..dd19996782d 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -195,6 +195,7 @@ export interface IExtensionManagementService { zip(extension: ILocalExtension): Promise; unzip(zipLocation: URI, type: ExtensionType): Promise; + getManifest(vsix: URI): Promise; install(vsix: URI): Promise; installFromGallery(extension: IGalleryExtension): Promise; uninstall(extension: ILocalExtension, force?: boolean): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index b88350f44c7..349223b9210 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -9,7 +9,7 @@ import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; import { cloneAndChange } from 'vs/base/common/objects'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI { return URI.revive(transformer ? transformer.transformIncoming(uri) : uri); @@ -62,6 +62,7 @@ export class ExtensionManagementChannel implements IServerChannel { case 'zip': return this.service.zip(transformIncomingExtension(args[0], uriTransformer)).then(uri => transformOutgoingURI(uri, uriTransformer)); case 'unzip': return this.service.unzip(transformIncomingURI(args[0], uriTransformer), args[1]); case 'install': return this.service.install(transformIncomingURI(args[0], uriTransformer)); + case 'getManifest': return this.service.getManifest(transformIncomingURI(args[0], uriTransformer)); case 'installFromGallery': return this.service.installFromGallery(args[0]); case 'uninstall': return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), args[1]); case 'reinstallFromGallery': return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer)); @@ -99,6 +100,10 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer return Promise.resolve(this.channel.call('install', [vsix])).then(local => transformIncomingExtension(local, null)); } + getManifest(vsix: URI): Promise { + return Promise.resolve(this.channel.call('getManifest', [vsix])); + } + installFromGallery(extension: IGalleryExtension): Promise { return Promise.resolve(this.channel.call('installFromGallery', [extension])).then(local => transformIncomingExtension(local, null)); } diff --git a/src/vs/platform/extensionManagement/node/media/defaultIcon.png b/src/vs/platform/extensionManagement/common/media/defaultIcon.png similarity index 100% rename from src/vs/platform/extensionManagement/node/media/defaultIcon.png rename to src/vs/platform/extensionManagement/common/media/defaultIcon.png diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 70cd46c824a..7d0b568fc7f 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -161,6 +161,12 @@ export class ExtensionManagementService extends Disposable implements IExtension return this.install(zipLocation, type).then(local => local.identifier); } + async getManifest(vsix: URI): Promise { + const downloadLocation = await this.downloadVsix(vsix); + const zipPath = path.resolve(downloadLocation.fsPath); + return getManifest(zipPath); + } + private collectFiles(extension: ILocalExtension): Promise { const collectFilesFromDirectory = async (dir: string): Promise => { @@ -983,4 +989,4 @@ export class ExtensionManagementService extends Disposable implements IExtension */ this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode })); } -} \ No newline at end of file +} diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 93a52746e6b..a043fdfcaa3 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -106,7 +106,7 @@ export interface IExtensionContributions { localizations?: ILocalization[]; } -export type ExtensionKind = 'ui' | 'workspace'; +export type ExtensionKind = 'ui' | 'workspace' | 'web'; export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier { return thing @@ -221,4 +221,4 @@ export interface IExtensionDescription extends IExtensionManifest { 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/fileService.ts b/src/vs/platform/files/common/fileService.ts index 5beb3a1c8b8..34a6974e03c 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -21,7 +21,7 @@ import { Schemas } from 'vs/base/common/network'; export class FileService extends Disposable implements IFileService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly BUFFER_SIZE = 64 * 1024; @@ -164,21 +164,25 @@ export class FileService extends Disposable implements IFileService { private async doResolveFile(resource: URI, options?: IResolveFileOptions): Promise { const provider = await this.withProvider(resource); - // leverage a trie to check for recursive resolving const resolveTo = options && options.resolveTo; - const trie = TernarySearchTree.forPaths(); - trie.set(resource.toString(), true); - if (isNonEmptyArray(resolveTo)) { - resolveTo.forEach(uri => trie.set(uri.toString(), true)); - } - const resolveSingleChildDescendants = !!(options && options.resolveSingleChildDescendants); const resolveMetadata = !!(options && options.resolveMetadata); const stat = await provider.stat(resource); + let trie: TernarySearchTree | undefined; + return this.toFileStat(provider, resource, stat, undefined, resolveMetadata, (stat, siblings) => { + // lazy trie to check for recursive resolving + if (!trie) { + trie = TernarySearchTree.forPaths(); + trie.set(resource.toString(), true); + if (isNonEmptyArray(resolveTo)) { + resolveTo.forEach(uri => trie!.set(uri.toString(), true)); + } + } + // check for recursive resolving if (Boolean(trie.findSuperstr(stat.resource.toString()) || trie.get(stat.resource.toString()))) { return true; @@ -253,8 +257,12 @@ export class FileService extends Disposable implements IFileService { } async exists(resource: URI): Promise { + const provider = await this.withProvider(resource); + try { - return !!(await this.resolve(resource)); + const stat = await provider.stat(resource); + + return !!stat; } catch (error) { return false; } @@ -1077,4 +1085,4 @@ export class FileService extends Disposable implements IFileService { } //#endregion -} \ 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 ad600a2a430..40d21fce9de 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -620,26 +620,26 @@ export interface IReadFileOptions { * that have been read already with the same etag. * It is the task of the caller to makes sure to handle this error case from the promise. */ - etag?: string; + readonly etag?: string; /** * Is an integer specifying where to begin reading from in the file. If position is null, * data will be read from the current file position. */ - position?: number; + readonly position?: number; /** * Is an integer specifying how many bytes to read from the file. By default, all bytes * will be read. */ - length?: number; + readonly length?: number; /** * If provided, the size of the file will be checked against the limits. */ limits?: { - size?: number; - memory?: number; + readonly size?: number; + readonly memory?: number; }; } @@ -648,12 +648,12 @@ export interface IWriteFileOptions { /** * The last known modification time of the file. This can be used to prevent dirty writes. */ - mtime?: number; + readonly mtime?: number; /** * The etag of the file. This can be used to prevent dirty writes. */ - etag?: string; + readonly etag?: string; } export interface IResolveFileOptions { @@ -662,22 +662,22 @@ export interface IResolveFileOptions { * Automatically continue resolving children of a directory until the provided resources * are found. */ - resolveTo?: URI[]; + readonly resolveTo?: readonly URI[]; /** * Automatically continue resolving children of a directory if the number of children is 1. */ - resolveSingleChildDescendants?: boolean; + readonly resolveSingleChildDescendants?: boolean; /** * Will resolve mtime, size and etag of files if enabled. This can have a negative impact * on performance and thus should only be used when these values are required. */ - resolveMetadata?: boolean; + readonly resolveMetadata?: boolean; } export interface IResolveMetadataFileOptions extends IResolveFileOptions { - resolveMetadata: true; + readonly resolveMetadata: true; } export interface ICreateFileOptions { @@ -686,7 +686,7 @@ export interface ICreateFileOptions { * Overwrite the file to create if it already exists on disk. Otherwise * an error will be thrown (FILE_MODIFIED_SINCE). */ - overwrite?: boolean; + readonly overwrite?: boolean; } export class FileOperationError extends Error { diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 8ef39ec23f0..12d5847389d 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { mkdir, open, close, read, write, fdatasync, Dirent, Stats } from 'fs'; +import { mkdir, open, close, read, write, fdatasync } from 'fs'; import { promisify } from 'util'; import { IDisposable, Disposable, toDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { statLink, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists, readdirWithFileTypes } from 'vs/base/node/pfs'; +import { statLink, readdir, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists } from 'vs/base/node/pfs'; import { normalize, basename, dirname } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/extpath'; @@ -62,8 +62,15 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro try { const { stat, isSymbolicLink } = await statLink(this.toFilePath(resource)); // cannot use fs.stat() here to support links properly + let type: number; + if (isSymbolicLink) { + type = FileType.SymbolicLink | (stat.isDirectory() ? FileType.Directory : FileType.File); + } else { + type = stat.isFile() ? FileType.File : stat.isDirectory() ? FileType.Directory : FileType.Unknown; + } + return { - type: this.toType(stat, isSymbolicLink), + type, ctime: stat.ctime.getTime(), mtime: stat.mtime.getTime(), size: stat.size @@ -75,19 +82,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro async readdir(resource: URI): Promise<[string, FileType][]> { try { - const children = await readdirWithFileTypes(this.toFilePath(resource)); + const children = await readdir(this.toFilePath(resource)); const result: [string, FileType][] = []; await Promise.all(children.map(async child => { try { - let type: FileType; - if (child.isSymbolicLink()) { - type = (await this.stat(joinPath(resource, child.name))).type; // always resolve target the link points to if any - } else { - type = this.toType(child); - } - - result.push([child.name, type]); + const stat = await this.stat(joinPath(resource, child)); + result.push([child, stat.type]); } catch (error) { this.logService.trace(error); // ignore errors for individual entries that can arise from permission denied } @@ -99,14 +100,6 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } } - private toType(entry: Stats | Dirent, isSymbolicLink = entry.isSymbolicLink()): FileType { - if (isSymbolicLink) { - return FileType.SymbolicLink | (entry.isDirectory() ? FileType.Directory : FileType.File); - } - - return entry.isFile() ? FileType.File : entry.isDirectory() ? FileType.Directory : FileType.Unknown; - } - //#endregion //#region File Reading/Writing @@ -148,6 +141,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } } + private mapHandleToPos: Map = new Map(); + private writeHandles: Set = new Set(); private canFlush: boolean = true; @@ -187,6 +182,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro const handle = await promisify(open)(filePath, flags); + // remember this handle to track file position of the handle + // we init the position to 0 since the file descriptor was + // just created and the position was not moved so far (see + // also http://man7.org/linux/man-pages/man2/open.2.html - + // "The file offset is set to the beginning of the file.") + this.mapHandleToPos.set(handle, 0); + // remember that this handle was used for writing if (opts.create) { this.writeHandles.add(handle); @@ -200,6 +202,10 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro async close(fd: number): Promise { try { + + // remove this handle from map of positions + this.mapHandleToPos.delete(fd); + // if a handle is closed that was used for writing, ensure // to flush the contents to disk if possible. if (this.writeHandles.delete(fd) && this.canFlush) { @@ -220,15 +226,81 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + const normalizedPos = this.normalizePos(fd, pos); + + let bytesRead: number | null = null; try { - const result = await promisify(read)(fd, data, offset, length, pos); + const result = await promisify(read)(fd, data, offset, length, normalizedPos); + if (typeof result === 'number') { - return result; // node.d.ts fail + bytesRead = result; // node.d.ts fail + } else { + bytesRead = result.bytesRead; } - return result.bytesRead; + return bytesRead; } catch (error) { throw this.toFileSystemProviderError(error); + } finally { + this.updatePos(fd, normalizedPos, bytesRead); + } + } + + private normalizePos(fd: number, pos: number): number | null { + + // when calling fs.read/write we try to avoid passing in the "pos" argument and + // rather prefer to pass in "null" because this avoids an extra seek(pos) + // call that in some cases can even fail (e.g. when opening a file over FTP - + // see https://github.com/microsoft/vscode/issues/73884). + // + // as such, we compare the passed in position argument with our last known + // position for the file descriptor and use "null" if they match. + if (pos === this.mapHandleToPos.get(fd)) { + return null; + } + + return pos; + } + + private updatePos(fd: number, pos: number | null, bytesLength: number | null): void { + const lastKnownPos = this.mapHandleToPos.get(fd); + if (typeof lastKnownPos === 'number') { + + // pos !== null signals that previously a position was used that is + // not null. node.js documentation explains, that in this case + // the internal file pointer is not moving and as such we do not move + // our position pointer. + // + // Docs: "If position is null, data will be read from the current file position, + // and the file position will be updated. If position is an integer, the file position + // will remain unchanged." + if (typeof pos === 'number') { + // do not modify the position + } + + // bytesLength = number is a signal that the read/write operation was + // successful and as such we need to advance the position in the Map + // + // Docs (http://man7.org/linux/man-pages/man2/read.2.html): + // "On files that support seeking, the read operation commences at the + // file offset, and the file offset is incremented by the number of + // bytes read." + // + // Docs (http://man7.org/linux/man-pages/man2/write.2.html): + // "For a seekable file (i.e., one to which lseek(2) may be applied, for + // example, a regular file) writing takes place at the file offset, and + // the file offset is incremented by the number of bytes actually + // written." + else if (typeof bytesLength === 'number') { + this.mapHandleToPos.set(fd, lastKnownPos + bytesLength); + } + + // bytesLength = null signals an error in the read/write operation + // and as such we drop the handle from the Map because the position + // is unspecificed at this point. + else { + this.mapHandleToPos.delete(fd); + } } } @@ -240,15 +312,23 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } private async doWrite(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + const normalizedPos = this.normalizePos(fd, pos); + + let bytesWritten: number | null = null; try { - const result = await promisify(write)(fd, data, offset, length, pos); + const result = await promisify(write)(fd, data, offset, length, normalizedPos); + if (typeof result === 'number') { - return result; // node.d.ts fail + bytesWritten = result; // node.d.ts fail + } else { + bytesWritten = result.bytesWritten; } - return result.bytesWritten; + return bytesWritten; } catch (error) { throw this.toFileSystemProviderError(error); + } finally { + this.updatePos(fd, normalizedPos, bytesWritten); } } @@ -441,14 +521,17 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro watcherOptions?: IWatcherOptions ): WindowsWatcherService | UnixWatcherService | NsfwWatcherService }; - let watcherOptions = undefined; + let watcherOptions: IWatcherOptions | undefined = undefined; + + // requires a polling watcher if (this.watcherOptions && this.watcherOptions.usePolling) { - // requires a polling watcher watcherImpl = UnixWatcherService; watcherOptions = this.watcherOptions; - } else { - // Single Folder Watcher + } + + // Single Folder Watcher + else { if (this.recursiveFoldersToWatch.length === 1) { if (isWindows) { watcherImpl = WindowsWatcherService; @@ -471,6 +554,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro if (msg.type === 'error') { this._onDidWatchErrorOccur.fire(msg.message); } + this.logService[msg.type](msg.message); }, this.logService.getLevel() === LogLevel.Trace, @@ -478,7 +562,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro ); if (!this.recursiveWatcherLogLevelListener) { - this.recursiveWatcherLogLevelListener = this.logService.onDidChangeLogLevel(_ => { + this.recursiveWatcherLogLevelListener = this.logService.onDidChangeLogLevel(() => { if (this.recursiveWatcher) { this.recursiveWatcher.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace); } @@ -496,11 +580,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro if (msg.type === 'error') { this._onDidWatchErrorOccur.fire(msg.message); } + this.logService[msg.type](msg.message); }, this.logService.getLevel() === LogLevel.Trace ); - const logLevelListener = this.logService.onDidChangeLogLevel(_ => { + + const logLevelListener = this.logService.onDidChangeLogLevel(() => { watcherService.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace); }); @@ -553,4 +639,4 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro dispose(this.recursiveWatcherLogLevelListener); this.recursiveWatcherLogLevelListener = undefined; } -} \ No newline at end of file +} diff --git a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts index 94050df7ee7..1948481a857 100644 --- a/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts +++ b/src/vs/platform/files/node/watcher/win32/csharpWatcherService.ts @@ -18,7 +18,7 @@ export class OutOfProcessWin32FolderWatcher { private ignored: glob.ParsedPattern[]; - private handle: cp.ChildProcess; + private handle: cp.ChildProcess | undefined; private restartCounter: number; constructor( @@ -134,7 +134,7 @@ export class OutOfProcessWin32FolderWatcher { public dispose(): void { if (this.handle) { this.handle.kill(); - this.handle = null!; // StrictNullOverride: nulling out ok in dispose + this.handle = undefined; } } } diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index 6d10050ec0e..238ccd8ede7 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -62,9 +62,9 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { totalBytesRead: number = 0; - private invalidStatSize: boolean; + private invalidStatSize: boolean = false; - private _testCapabilities: FileSystemProviderCapabilities; + private _testCapabilities!: FileSystemProviderCapabilities; get capabilities(): FileSystemProviderCapabilities { if (!this._testCapabilities) { this._testCapabilities = @@ -115,7 +115,7 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { } } -suite('Disk File Service', () => { +suite('Disk File Service', function () { const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'diskfileservice'); const testSchema = 'test'; @@ -127,6 +127,12 @@ suite('Disk File Service', () => { const disposables = new DisposableStore(); + // Given issues such as https://github.com/microsoft/vscode/issues/78602 + // we see random test failures when accessing the native file system. To + // diagnose further, we retry node.js file access tests up to 3 times to + // rule out any random disk issue. + this.retries(3); + setup(async () => { const logService = new NullLogService(); @@ -613,7 +619,7 @@ suite('Disk File Service', () => { await testMoveFolderAcrossProviders(); }); - test('move - directory - across providers (unbuffered => buffered)', async () => { + test('move - directory - across providers (unbuffered => buffered)', async function () { setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); setCapabilities(testProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); @@ -1915,4 +1921,94 @@ suite('Disk File Service', () => { function hasChange(changes: IFileChange[], type: FileChangeType, resource: URI): boolean { return changes.some(change => change.type === type && isEqual(change.resource, resource)); } -}); \ No newline at end of file + + test('read - mixed positions', async () => { + const resource = URI.file(join(testDir, 'lorem.txt')); + + // read multiple times from position 0 + let buffer = VSBuffer.alloc(1024); + let fd = await fileProvider.open(resource, { create: false }); + for (let i = 0; i < 3; i++) { + await fileProvider.read(fd, 0, buffer.buffer, 0, 26); + assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + } + await fileProvider.close(fd); + + // read multiple times at various locations + buffer = VSBuffer.alloc(1024); + fd = await fileProvider.open(resource, { create: false }); + + let posInFile = 0; + + await fileProvider.read(fd, posInFile, buffer.buffer, 0, 26); + assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + posInFile += 26; + + await fileProvider.read(fd, posInFile, buffer.buffer, 0, 1); + assert.equal(buffer.slice(0, 1).toString(), ','); + posInFile += 1; + + await fileProvider.read(fd, posInFile, buffer.buffer, 0, 12); + assert.equal(buffer.slice(0, 12).toString(), ' consectetur'); + posInFile += 12; + + await fileProvider.read(fd, 98 /* no longer in sequence of posInFile */, buffer.buffer, 0, 9); + assert.equal(buffer.slice(0, 9).toString(), 'fermentum'); + + await fileProvider.read(fd, 27, buffer.buffer, 0, 12); + assert.equal(buffer.slice(0, 12).toString(), ' consectetur'); + + await fileProvider.read(fd, 26, buffer.buffer, 0, 1); + assert.equal(buffer.slice(0, 1).toString(), ','); + + await fileProvider.read(fd, 0, buffer.buffer, 0, 26); + assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + + await fileProvider.read(fd, posInFile /* back in sequence */, buffer.buffer, 0, 11); + assert.equal(buffer.slice(0, 11).toString(), ' adipiscing'); + + await fileProvider.close(fd); + }); + + test('write - mixed positions', async () => { + const resource = URI.file(join(testDir, 'lorem.txt')); + + const buffer = VSBuffer.alloc(1024); + const fdWrite = await fileProvider.open(resource, { create: true }); + const fdRead = await fileProvider.open(resource, { create: false }); + + let posInFileWrite = 0; + let posInFileRead = 0; + + const initialContents = VSBuffer.fromString('Lorem ipsum dolor sit amet'); + await fileProvider.write(fdWrite, posInFileWrite, initialContents.buffer, 0, initialContents.byteLength); + posInFileWrite += initialContents.byteLength; + + await fileProvider.read(fdRead, posInFileRead, buffer.buffer, 0, 26); + assert.equal(buffer.slice(0, 26).toString(), 'Lorem ipsum dolor sit amet'); + posInFileRead += 26; + + const contents = VSBuffer.fromString('Hello World'); + + await fileProvider.write(fdWrite, posInFileWrite, contents.buffer, 0, contents.byteLength); + posInFileWrite += contents.byteLength; + + await fileProvider.read(fdRead, posInFileRead, buffer.buffer, 0, contents.byteLength); + assert.equal(buffer.slice(0, contents.byteLength).toString(), 'Hello World'); + posInFileRead += contents.byteLength; + + await fileProvider.write(fdWrite, 6, contents.buffer, 0, contents.byteLength); + + await fileProvider.read(fdRead, 0, buffer.buffer, 0, 11); + assert.equal(buffer.slice(0, 11).toString(), 'Lorem Hello'); + + await fileProvider.write(fdWrite, posInFileWrite, contents.buffer, 0, contents.byteLength); + posInFileWrite += contents.byteLength; + + await fileProvider.read(fdRead, posInFileWrite - contents.byteLength, buffer.buffer, 0, contents.byteLength); + assert.equal(buffer.slice(0, contents.byteLength).toString(), 'Hello World'); + + await fileProvider.close(fdWrite); + await fileProvider.close(fdRead); + }); +}); diff --git a/src/vs/platform/history/common/historyStorage.ts b/src/vs/platform/history/common/historyStorage.ts index 089d73ec313..b026460963d 100644 --- a/src/vs/platform/history/common/historyStorage.ts +++ b/src/vs/platform/history/common/historyStorage.ts @@ -71,7 +71,7 @@ export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefine result.workspaces.push({ folderUri: URI.file(workspace) }); } else if (isLegacySerializedWorkspace(workspace)) { result.workspaces.push({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } }); - } else if (isUriComponents(window)) { + } else if (isUriComponents(workspace)) { // added by 1.26-insiders result.workspaces.push({ folderUri: URI.revive(workspace) }); } diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 6447b2c65e1..01e9cd3f4d8 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -40,7 +40,7 @@ export class HistoryMainService implements IHistoryMainService { private static readonly recentlyOpenedStorageKey = 'openedPathsList'; - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private _onRecentlyOpenedChange = new Emitter(); readonly onRecentlyOpenedChange: CommonEvent = this._onRecentlyOpenedChange.event; @@ -387,4 +387,4 @@ function indexOfFolder(arr: IRecent[], folderURI: ISingleFolderWorkspaceIdentifi function indexOfFile(arr: IRecentFile[], fileURI: URI): number { return arrays.firstIndex(arr, f => areResourcesEqual(f.fileUri, fileURI)); -} \ No newline at end of file +} diff --git a/src/vs/platform/ipc/electron-browser/mainProcessService.ts b/src/vs/platform/ipc/electron-browser/mainProcessService.ts index 31ac99261c6..e2f756cc803 100644 --- a/src/vs/platform/ipc/electron-browser/mainProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/mainProcessService.ts @@ -21,7 +21,7 @@ export interface IMainProcessService { export class MainProcessService extends Disposable implements IMainProcessService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private mainProcessConnection: Client; @@ -40,4 +40,4 @@ export class MainProcessService extends Disposable implements IMainProcessServic registerChannel(channelName: string, channel: IServerChannel): void { this.mainProcessConnection.registerChannel(channelName, channel); } -} \ No newline at end of file +} diff --git a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts index 399aa628cc6..f01e3f0b5de 100644 --- a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts @@ -23,7 +23,7 @@ export interface ISharedProcessService { export class SharedProcessService implements ISharedProcessService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private withSharedProcessConnection: Promise>; @@ -43,4 +43,4 @@ export class SharedProcessService implements ISharedProcessService { registerChannel(channelName: string, channel: IServerChannel): void { this.withSharedProcessConnection.then(connection => connection.registerChannel(channelName, channel)); } -} \ No newline at end of file +} diff --git a/src/vs/platform/issue/electron-browser/issueService.ts b/src/vs/platform/issue/electron-browser/issueService.ts index e9d287e7628..6228ef33671 100644 --- a/src/vs/platform/issue/electron-browser/issueService.ts +++ b/src/vs/platform/issue/electron-browser/issueService.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IIssueService, IssueReporterData, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { IIssueService, IssueReporterData, ProcessExplorerData } from 'vs/platform/issue/node/issue'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class IssueService implements IIssueService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private channel: IChannel; @@ -29,4 +29,4 @@ export class IssueService implements IIssueService { getSystemStatus(): Promise { return this.channel.call('getSystemStatus'); } -} \ No newline at end of file +} diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueService.ts index 97c788209ed..aa31eb1913a 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueService.ts @@ -6,10 +6,11 @@ import { localize } from 'vs/nls'; import * as objects from 'vs/base/common/objects'; import { parseArgs } from 'vs/platform/environment/node/argv'; -import { IIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { IIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/node/issue'; import { BrowserWindow, ipcMain, screen, Event, dialog } from 'electron'; import { ILaunchService } from 'vs/platform/launch/electron-main/launchService'; -import { PerformanceInfo, IDiagnosticsService, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { PerformanceInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; +import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; @@ -21,10 +22,10 @@ const DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; export class IssueService implements IIssueService { _serviceBrand: any; - _issueWindow: BrowserWindow | null; - _issueParentWindow: BrowserWindow | null; - _processExplorerWindow: BrowserWindow | null; - _processExplorerParentWindow: BrowserWindow | null; + _issueWindow: BrowserWindow | null = null; + _issueParentWindow: BrowserWindow | null = null; + _processExplorerWindow: BrowserWindow | null = null; + _processExplorerParentWindow: BrowserWindow | null = null; constructor( private machineId: string, diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/node/issue.ts similarity index 100% rename from src/vs/platform/issue/common/issue.ts rename to src/vs/platform/issue/node/issue.ts diff --git a/src/vs/platform/issue/node/issueIpc.ts b/src/vs/platform/issue/node/issueIpc.ts index 4eca97ef53c..271bcf5ceee 100644 --- a/src/vs/platform/issue/node/issueIpc.ts +++ b/src/vs/platform/issue/node/issueIpc.ts @@ -5,7 +5,7 @@ import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IIssueService } from 'vs/platform/issue/common/issue'; +import { IIssueService } from 'vs/platform/issue/node/issue'; export class IssueChannel implements IServerChannel { diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index 626a376d04b..c9924cf39d5 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -19,7 +19,7 @@ import { BrowserWindow, ipcMain, Event as IpcEvent, app } 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 { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { IMainProcessInfo, IWindowInfo } from 'vs/platform/launch/common/launchService'; export const ID = 'launchService'; @@ -94,7 +94,7 @@ export class LaunchChannel implements IServerChannel { export class LaunchChannelClient implements ILaunchService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; constructor(private channel: IChannel) { } @@ -121,7 +121,7 @@ export class LaunchChannelClient implements ILaunchService { export class LaunchService implements ILaunchService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; constructor( @ILogService private readonly logService: ILogService, diff --git a/src/vs/platform/lifecycle/browser/lifecycleService.ts b/src/vs/platform/lifecycle/browser/lifecycleService.ts index f5e4c4c3f3c..79301acb261 100644 --- a/src/vs/platform/lifecycle/browser/lifecycleService.ts +++ b/src/vs/platform/lifecycle/browser/lifecycleService.ts @@ -8,10 +8,11 @@ import { ILogService } from 'vs/platform/log/common/log'; import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; import { localize } from 'vs/nls'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { addDisposableListener, EventType } from 'vs/base/browser/dom'; export class BrowserLifecycleService extends AbstractLifecycleService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; constructor( @ILogService readonly logService: ILogService @@ -22,10 +23,10 @@ export class BrowserLifecycleService extends AbstractLifecycleService { } private registerListeners(): void { - window.onbeforeunload = () => this.beforeUnload(); + addDisposableListener(window, EventType.BEFORE_UNLOAD, () => this.onBeforeUnload()); } - private beforeUnload(): string | null { + private onBeforeUnload(): string | null { let veto = false; // Before Shutdown @@ -34,7 +35,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService { if (value === true) { veto = true; } else if (value instanceof Promise && !veto) { - console.warn(new Error('Long running onBeforeShutdown currently not supported')); + console.warn(new Error('Long running onBeforeShutdown currently not supported in the web')); veto = true; } }, @@ -49,11 +50,14 @@ export class BrowserLifecycleService extends AbstractLifecycleService { // No Veto: continue with Will Shutdown this._onWillShutdown.fire({ join() { - console.warn(new Error('Long running onWillShutdown currently not supported')); + console.warn(new Error('Long running onWillShutdown currently not supported in the web')); }, reason: ShutdownReason.QUIT }); + // Finally end with Shutdown event + this._onShutdown.fire(); + return null; } -} \ No newline at end of file +} diff --git a/src/vs/platform/lifecycle/common/lifecycleService.ts b/src/vs/platform/lifecycle/common/lifecycleService.ts index 2225c04ca99..326a1b371b9 100644 --- a/src/vs/platform/lifecycle/common/lifecycleService.ts +++ b/src/vs/platform/lifecycle/common/lifecycleService.ts @@ -13,7 +13,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export abstract class AbstractLifecycleService extends Disposable implements ILifecycleService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; protected readonly _onBeforeShutdown = this._register(new Emitter()); readonly onBeforeShutdown: Event = this._onBeforeShutdown.event; @@ -72,4 +72,4 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi await barrier.wait(); } -} \ No newline at end of file +} diff --git a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts b/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts index b4896df9ffe..7e4fd29a3a6 100644 --- a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts @@ -18,7 +18,7 @@ export class LifecycleService extends AbstractLifecycleService { private static readonly LAST_SHUTDOWN_REASON_KEY = 'lifecyle.lastShutdownReason'; - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private shutdownReason: ShutdownReason; diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts index 058efdc2500..c44298bc61e 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts @@ -131,7 +131,7 @@ export const enum LifecycleMainPhase { export class LifecycleService extends Disposable implements ILifecycleService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static readonly QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 0ef822bae84..6c5ac488c5f 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -890,6 +890,7 @@ function workbenchTreeDataPreamble; + _serviceBrand!: ServiceIdentifier; private channel: IChannel; @@ -24,4 +24,4 @@ export class LocalizationsService implements ILocalizationsService { getLanguageIds(type?: LanguageType): Promise { return this.channel.call('getLanguageIds', type); } -} \ No newline at end of file +} diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index 9467d8a6282..2311778a2fa 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -93,7 +93,7 @@ class LanguagePacksCache extends Disposable { private languagePacks: { [language: string]: ILanguagePack } = {}; private languagePacksFilePath: string; private languagePacksFileLimiter: Queue; - private initializedCache: boolean; + private initializedCache: boolean | undefined; constructor( @IEnvironmentService environmentService: IEnvironmentService, @@ -184,4 +184,4 @@ class LanguagePacksCache extends Disposable { .then(() => result, error => this.logService.error(error)); }); } -} \ No newline at end of file +} diff --git a/src/vs/platform/markers/common/markers.ts b/src/vs/platform/markers/common/markers.ts index 235c4b12f36..26d05bc8c2e 100644 --- a/src/vs/platform/markers/common/markers.ts +++ b/src/vs/platform/markers/common/markers.ts @@ -129,6 +129,10 @@ export interface MarkerStatistics { export namespace IMarkerData { const emptyString = ''; export function makeKey(markerData: IMarkerData): string { + return makeKeyOptionalMessage(markerData, true); + } + + export function makeKeyOptionalMessage(markerData: IMarkerData, useMessage: boolean): string { let result: string[] = [emptyString]; if (markerData.source) { result.push(markerData.source.replace('¦', '\¦')); @@ -145,7 +149,10 @@ export namespace IMarkerData { } else { result.push(emptyString); } - if (markerData.message) { + + // Modifed to not include the message as part of the marker key to work around + // https://github.com/microsoft/vscode/issues/77475 + if (markerData.message && useMessage) { result.push(markerData.message.replace('¦', '\¦')); } else { result.push(emptyString); diff --git a/src/vs/platform/menubar/electron-browser/menubarService.ts b/src/vs/platform/menubar/electron-browser/menubarService.ts index 7a288b3d348..336330d1e6d 100644 --- a/src/vs/platform/menubar/electron-browser/menubarService.ts +++ b/src/vs/platform/menubar/electron-browser/menubarService.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar'; +import { IMenubarService, IMenubarData } from 'vs/platform/menubar/node/menubar'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class MenubarService implements IMenubarService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private channel: IChannel; diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 76185f93da8..ff9d06a636b 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -17,7 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { mnemonicMenuLabel as baseMnemonicLabel } from 'vs/base/common/labels'; import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService } from 'vs/platform/history/common/history'; -import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction, IMenubarMenu, isMenubarMenuItemUriAction } from 'vs/platform/menubar/common/menubar'; +import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction, IMenubarMenu, isMenubarMenuItemUriAction } from 'vs/platform/menubar/node/menubar'; import { URI } from 'vs/base/common/uri'; import { IStateService } from 'vs/platform/state/common/state'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; @@ -550,7 +550,7 @@ export class Menubar { })]; case StateType.CheckingForUpdates: - return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })]; + return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking for Updates..."), enabled: false })]; case StateType.AvailableForDownload: return [new MenuItem({ diff --git a/src/vs/platform/menubar/electron-main/menubarService.ts b/src/vs/platform/menubar/electron-main/menubarService.ts index b4a8ed44ef4..f7abf2ee9f2 100644 --- a/src/vs/platform/menubar/electron-main/menubarService.ts +++ b/src/vs/platform/menubar/electron-main/menubarService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar'; +import { IMenubarService, IMenubarData } from 'vs/platform/menubar/node/menubar'; import { Menubar } from 'vs/platform/menubar/electron-main/menubar'; import { ILogService } from 'vs/platform/log/common/log'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/platform/menubar/common/menubar.ts b/src/vs/platform/menubar/node/menubar.ts similarity index 100% rename from src/vs/platform/menubar/common/menubar.ts rename to src/vs/platform/menubar/node/menubar.ts diff --git a/src/vs/platform/menubar/node/menubarIpc.ts b/src/vs/platform/menubar/node/menubarIpc.ts index 58f22e631b4..850a2542dd0 100644 --- a/src/vs/platform/menubar/node/menubarIpc.ts +++ b/src/vs/platform/menubar/node/menubarIpc.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IMenubarService } from 'vs/platform/menubar/common/menubar'; +import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { Event } from 'vs/base/common/event'; export class MenubarChannel implements IServerChannel { diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 173b575822e..23b14135edb 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -29,6 +29,45 @@ export interface INotificationProperties { * catch some attention. */ silent?: boolean; + + /** + * Adds an action to never show the notification again. The choice will be persisted + * such as future requests will not cause the notification to show again. + */ + neverShowAgain?: INeverShowAgainOptions; +} + +export enum NeverShowAgainScope { + + /** + * Will never show this notification on the current workspace again. + */ + WORKSPACE, + + /** + * Will never show this notification on any workspace again. + */ + GLOBAL +} + +export interface INeverShowAgainOptions { + + /** + * The id is used to persist the selection of not showing the notification again. + */ + id: string; + + /** + * By default the action will show up as primary action. Setting this to true will + * make it a secondary action instead. + */ + isSecondary?: boolean; + + /** + * Wether to persist the choice in the current workspace or for all workspaces. By + * default it will be persisted for all workspaces. + */ + scope?: NeverShowAgainScope; } export interface INotification extends INotificationProperties { @@ -270,4 +309,4 @@ export class NoOpProgress implements INotificationProgress { done(): void { } total(value: number): void { } worked(value: number): void { } -} \ No newline at end of file +} diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 8c0f9fee5df..17cea7f5b3a 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -9,15 +9,18 @@ import { IDisposable } from 'vs/base/common/lifecycle'; export const IOpenerService = createDecorator('openerService'); - export interface IOpener { open(resource: URI, options?: { openToSide?: boolean }): Promise; + open(resource: URI, options?: { openExternal?: boolean }): Promise; } export interface IOpenerService { _serviceBrand: any; + /** + * Register a participant that can handle the open() call. + */ registerOpener(opener: IOpener): IDisposable; /** @@ -27,10 +30,11 @@ export interface IOpenerService { * @return A promise that resolves when the opening is done. */ open(resource: URI, options?: { openToSide?: boolean }): Promise; + open(resource: URI, options?: { openExternal?: boolean }): Promise; } export const NullOpenerService: IOpenerService = Object.freeze({ _serviceBrand: undefined, registerOpener() { return { dispose() { } }; }, - open() { return Promise.resolve(false); } + open() { return Promise.resolve(false); }, }); diff --git a/src/vs/platform/product/browser/productService.ts b/src/vs/platform/product/browser/productService.ts deleted file mode 100644 index fbdf03fd3e4..00000000000 --- a/src/vs/platform/product/browser/productService.ts +++ /dev/null @@ -1,49 +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 { IProductService, IProductConfiguration } from 'vs/platform/product/common/product'; -import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; - -export class ProductService implements IProductService { - - private readonly productConfiguration: IProductConfiguration | null; - - constructor() { - const element = document.getElementById('vscode-remote-product-configuration'); - this.productConfiguration = element ? JSON.parse(element.getAttribute('data-settings')!) : null; - } - - _serviceBrand: ServiceIdentifier; - - get version(): string { return '1.35.0'; } - - get commit(): string | undefined { return this.productConfiguration ? this.productConfiguration.commit : undefined; } - - get nameLong(): string { return ''; } - - get urlProtocol(): string { return ''; } - - get extensionAllowedProposedApi(): readonly string[] { return this.productConfiguration ? this.productConfiguration.extensionAllowedProposedApi : []; } - - get uiExtensions(): readonly string[] | undefined { return this.productConfiguration ? this.productConfiguration.uiExtensions : undefined; } - - get enableTelemetry(): boolean { return false; } - - get sendASmile(): { reportIssueUrl: string, requestFeatureUrl: string } | undefined { return this.productConfiguration ? this.productConfiguration.sendASmile : undefined; } - - get extensionsGallery() { return this.productConfiguration ? this.productConfiguration.extensionsGallery : undefined; } - - get settingsSearchBuildId(): number | undefined { return this.productConfiguration ? this.productConfiguration.settingsSearchBuildId : undefined; } - - get settingsSearchUrl(): string | undefined { return this.productConfiguration ? this.productConfiguration.settingsSearchUrl : undefined; } - - get experimentsUrl(): string | undefined { return this.productConfiguration ? this.productConfiguration.experimentsUrl : undefined; } - - get extensionKeywords(): { [extension: string]: readonly string[]; } | undefined { return this.productConfiguration ? this.productConfiguration.extensionKeywords : undefined; } - - get extensionAllowedBadgeProviders(): readonly string[] | undefined { return this.productConfiguration ? this.productConfiguration.extensionAllowedBadgeProviders : undefined; } - - get aiConfig() { return this.productConfiguration ? this.productConfiguration.aiConfig : undefined; } -} \ No newline at end of file diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index d39dd65c08f..0a3b1f75c89 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -7,43 +7,14 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' export const IProductService = createDecorator('productService'); -export interface IProductService { - _serviceBrand: any; +export interface IProductService extends Readonly { - readonly version: string; - readonly commit?: string; + _serviceBrand: undefined; - readonly nameLong: string; - readonly urlProtocol: string; - readonly extensionAllowedProposedApi: readonly string[]; - readonly uiExtensions?: readonly string[]; - - readonly enableTelemetry: boolean; - readonly extensionsGallery?: { - readonly serviceUrl: string; - readonly itemUrl: string; - readonly controlUrl: string; - readonly recommendationsUrl: string; - }; - - readonly sendASmile?: { - readonly reportIssueUrl: string; - readonly requestFeatureUrl: string; - }; - - readonly settingsSearchBuildId?: number; - readonly settingsSearchUrl?: string; - - readonly experimentsUrl?: string; - readonly extensionKeywords?: { [extension: string]: readonly string[]; }; - readonly extensionAllowedBadgeProviders?: readonly string[]; - - readonly aiConfig?: { - readonly asimovKey: string; - }; } export interface IProductConfiguration { + version: string; nameShort: string; nameLong: string; readonly applicationName: string; @@ -71,11 +42,10 @@ export interface IProductConfiguration { readonly controlUrl: string; readonly recommendationsUrl: string; }; - extensionTips: { [id: string]: string; }; - extensionImportantTips: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; }; + readonly extensionTips: { [id: string]: string; }; + readonly extensionImportantTips: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; }; readonly exeBasedExtensionTips: { [id: string]: IExeBasedExtensionTip; }; readonly extensionKeywords: { [extension: string]: readonly string[]; }; - readonly extensionAllowedBadgeProviders: readonly string[]; readonly extensionAllowedProposedApi: readonly string[]; readonly keymapExtensionTips: readonly string[]; readonly crashReporter: { @@ -134,4 +104,4 @@ export interface ISurveyData { languageId: string; editCount: number; userProbability: number; -} \ No newline at end of file +} diff --git a/src/vs/platform/product/node/product.ts b/src/vs/platform/product/node/product.ts index 08610d71a1a..3d13f26c576 100644 --- a/src/vs/platform/product/node/product.ts +++ b/src/vs/platform/product/node/product.ts @@ -6,6 +6,7 @@ import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { IProductConfiguration } from 'vs/platform/product/common/product'; +import pkg from 'vs/platform/product/node/package'; const rootPath = path.dirname(getPathFromAmdModule(require, '')); const productJsonPath = path.join(rootPath, 'product.json'); @@ -17,4 +18,6 @@ if (process.env['VSCODE_DEV']) { product.dataFolderName += '-dev'; } +product.version = pkg.version; + export default product; diff --git a/src/vs/platform/product/node/productService.ts b/src/vs/platform/product/node/productService.ts deleted file mode 100644 index 4b98ff65d0f..00000000000 --- a/src/vs/platform/product/node/productService.ts +++ /dev/null @@ -1,42 +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 { IProductService } from 'vs/platform/product/common/product'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; -import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; - -export class ProductService implements IProductService { - - _serviceBrand: ServiceIdentifier; - - get version(): string { return pkg.version; } - - get commit(): string | undefined { return product.commit; } - - get nameLong(): string { return product.nameLong; } - - get urlProtocol(): string { return product.urlProtocol; } - - get extensionAllowedProposedApi(): readonly string[] { return product.extensionAllowedProposedApi; } - - get uiExtensions(): readonly string[] | undefined { return product.uiExtensions; } - - get enableTelemetry(): boolean { return product.enableTelemetry; } - - get sendASmile(): { reportIssueUrl: string, requestFeatureUrl: string } { return product.sendASmile; } - - get extensionsGallery() { return product.extensionsGallery; } - - get settingsSearchBuildId(): number | undefined { return product.settingsSearchBuildId; } - - get settingsSearchUrl(): string | undefined { return product.settingsSearchUrl; } - - get experimentsUrl(): string | undefined { return product.experimentsUrl; } - - get extensionKeywords(): { [extension: string]: readonly string[]; } | undefined { return product.extensionKeywords; } - - get extensionAllowedBadgeProviders(): readonly string[] | undefined { return product.extensionAllowedBadgeProviders; } -} \ No newline at end of file diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 634b38b1929..4308c72ce89 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -90,13 +90,13 @@ export interface IProgress { export class Progress implements IProgress { private _callback: (data: T) => void; - private _value: T; + private _value?: T; constructor(callback: (data: T) => void) { this._callback = callback; } - get value() { + get value(): T | undefined { return this._value; } @@ -120,7 +120,7 @@ export interface IOperation { export class LongRunningOperation extends Disposable { private currentOperationId = 0; private readonly currentOperationDisposables = this._register(new DisposableStore()); - private currentProgressRunner: IProgressRunner; + private currentProgressRunner: IProgressRunner | undefined; private currentProgressTimeout: any; constructor( diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index ad95b6751a7..5653a6912b2 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -35,7 +35,6 @@ export interface ConnectionTypeRequest { signedData?: string; desiredConnectionType?: ConnectionType; args?: any; - isBuilt: boolean; } export interface ErrorMessage { @@ -51,7 +50,6 @@ export type HandshakeMessage = AuthRequest | SignRequest | ConnectionTypeRequest interface ISimpleConnectionOptions { - isBuilt: boolean; commit: string | undefined; host: string; port: number; @@ -110,8 +108,7 @@ async function connectToRemoteExtensionHostAgent(options: ISimpleConnectionOptio type: 'connectionType', commit: options.commit, signedData: signed, - desiredConnectionType: connectionType, - isBuilt: options.isBuilt + desiredConnectionType: connectionType }; if (args) { connTypeRequest.args = args; @@ -200,7 +197,6 @@ async function doConnectRemoteAgentTunnel(options: ISimpleConnectionOptions, sta } export interface IConnectionOptions { - isBuilt: boolean; commit: string | undefined; socketFactory: ISocketFactory; addressProvider: IAddressProvider; @@ -210,7 +206,6 @@ export interface IConnectionOptions { async function resolveConnectionOptions(options: IConnectionOptions, reconnectionToken: string, reconnectionProtocol: PersistentProtocol | null): Promise { const { host, port } = await options.addressProvider.getAddress(); return { - isBuilt: options.isBuilt, commit: options.commit, host: host, port: port, diff --git a/src/vs/platform/remote/common/remoteAgentEnvironment.ts b/src/vs/platform/remote/common/remoteAgentEnvironment.ts index c1815d801f3..839a6cb5c19 100644 --- a/src/vs/platform/remote/common/remoteAgentEnvironment.ts +++ b/src/vs/platform/remote/common/remoteAgentEnvironment.ts @@ -9,6 +9,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' export interface IRemoteAgentEnvironment { pid: number; + connectionToken: string; appRoot: URI; appSettingsHome: URI; settingsPath: URI; @@ -24,4 +25,4 @@ export interface IRemoteAgentEnvironment { export interface RemoteAgentConnectionContext { remoteAuthority: string; clientId: string; -} \ No newline at end of file +} diff --git a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts index cefb3518ba0..1f8b5fee33c 100644 --- a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts +++ b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts @@ -33,7 +33,7 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF private readonly _onDidChangeCapabilities = this._register(new Emitter()); readonly onDidChangeCapabilities: Event = this._onDidChangeCapabilities.event; - private _capabilities: FileSystemProviderCapabilities; + private _capabilities!: FileSystemProviderCapabilities; get capabilities(): FileSystemProviderCapabilities { return this._capabilities; } constructor(private readonly channel: IChannel, environment: Promise) { diff --git a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts index 3cd1da3e018..45ad072f2eb 100644 --- a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { ResolvedAuthority, IRemoteAuthorityResolverService, ResolverResult, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { ipcRenderer as ipc } from 'electron'; import * as errors from 'vs/base/common/errors'; +import { RemoteAuthorities } from 'vs/base/common/network'; class PendingResolveAuthorityRequest { constructor( @@ -49,7 +49,7 @@ export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverS setResolvedAuthority(resolvedAuthority: ResolvedAuthority, options?: ResolvedOptions) { if (this._resolveAuthorityRequests[resolvedAuthority.authority]) { let request = this._resolveAuthorityRequests[resolvedAuthority.authority]; - ipc.send('vscode:remoteAuthorityResolved', resolvedAuthority); + RemoteAuthorities.set(resolvedAuthority.authority, resolvedAuthority.host, resolvedAuthority.port); request.resolve({ authority: resolvedAuthority, options }); } } diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index e0c572277d3..8ca875f6fa9 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -38,7 +38,7 @@ export class RequestService extends Disposable implements IRequestService { _serviceBrand: any; private proxyUrl?: string; - private strictSSL: boolean; + private strictSSL: boolean | undefined; private authorization?: string; constructor( @@ -143,4 +143,4 @@ export class RequestService extends Disposable implements IRequestService { } -} \ No newline at end of file +} diff --git a/src/vs/platform/sign/browser/signService.ts b/src/vs/platform/sign/browser/signService.ts index 164dfaef813..75951fab3dc 100644 --- a/src/vs/platform/sign/browser/signService.ts +++ b/src/vs/platform/sign/browser/signService.ts @@ -8,27 +8,12 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class SignService implements ISignService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly _tkn: string | null; constructor(token: string | undefined) { - if (typeof token !== 'undefined') { - this._tkn = token; - } else { - this._tkn = SignService._readTokenFromURL(); - } - } - - private static _readTokenFromURL(): string | null { - if (!document.location.hash) { - return null; - } - const m = document.location.hash.match(/[#&]tkn=([^&]+)/); - if (!m) { - return null; - } - return m[1]; + this._tkn = token || null; } async sign(value: string): Promise { diff --git a/src/vs/platform/sign/node/signService.ts b/src/vs/platform/sign/node/signService.ts index b33e5e25be1..cf5a0703ea8 100644 --- a/src/vs/platform/sign/node/signService.ts +++ b/src/vs/platform/sign/node/signService.ts @@ -4,14 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { ISignService } from 'vs/platform/sign/common/sign'; -import { memoize } from 'vs/base/common/decorators'; export class SignService implements ISignService { _serviceBrand: any; - // Cache the 'vsda' import, because when the same missing module is imported multiple times, - // the ones after the first will not throw an error. And this will break the contract of the sign method. - @memoize private vsda(): Promise { return import('vsda'); } @@ -29,4 +25,4 @@ export class SignService implements ISignService { return value; } -} \ No newline at end of file +} diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index 7e18f2ba09d..5648772339f 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -5,19 +5,21 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage, FileStorageDatabase } from 'vs/platform/storage/common/storage'; +import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IStorage, Storage } from 'vs/base/parts/storage/common/storage'; +import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; +import { IStorage, Storage, IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; -import { runWhenIdle } from 'vs/base/common/async'; +import { runWhenIdle, RunOnceScheduler } from 'vs/base/common/async'; +import { serializableToMap, mapToSerializable } from 'vs/base/common/map'; +import { VSBuffer } from 'vs/base/common/buffer'; export class BrowserStorageService extends Disposable implements IStorageService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); readonly onDidChangeStorage: Event = this._onDidChangeStorage.event; @@ -28,10 +30,18 @@ export class BrowserStorageService extends Disposable implements IStorageService private globalStorage: IStorage; private workspaceStorage: IStorage; + private globalStorageDatabase: FileStorageDatabase; + private workspaceStorageDatabase: FileStorageDatabase; + private globalStorageFile: URI; private workspaceStorageFile: URI; private initializePromise: Promise; + private periodicSaveScheduler = this._register(new RunOnceScheduler(() => this.collectState(), 5000)); + + get hasPendingUpdate(): boolean { + return this.globalStorageDatabase.hasPendingUpdate || this.workspaceStorageDatabase.hasPendingUpdate; + } constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService, @@ -44,20 +54,28 @@ export class BrowserStorageService extends Disposable implements IStorageService // long running operation. // Instead, periodically ask customers to save save. The library will be clever enough // to only save state that has actually changed. - this.saveStatePeriodically(); + this.periodicSaveScheduler.schedule(); } - private saveStatePeriodically(): void { - setTimeout(() => { - runWhenIdle(() => { + private collectState(): void { + runWhenIdle(() => { - // this event will potentially cause new state to be stored + // this event will potentially cause new state to be stored + // since new state will only be created while the document + // has focus, one optimization is to not run this when the + // document has no focus, assuming that state has not changed + // + // another optimization is to not collect more state if we + // have a pending update already running which indicates + // that the connection is either slow or disconnected and + // thus unhealthy. + if (document.hasFocus() && !this.hasPendingUpdate) { this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE }); + } - // repeat - this.saveStatePeriodically(); - }); - }, 5000); + // repeat + this.periodicSaveScheduler.schedule(); + }); } initialize(payload: IWorkspaceInitializationPayload): Promise { @@ -76,12 +94,14 @@ export class BrowserStorageService extends Disposable implements IStorageService // Workspace Storage this.workspaceStorageFile = joinPath(stateRoot, `${payload.id}.json`); - this.workspaceStorage = new Storage(this._register(new FileStorageDatabase(this.workspaceStorageFile, this.fileService))); + this.workspaceStorageDatabase = this._register(new FileStorageDatabase(this.workspaceStorageFile, false /* do not watch for external changes */, this.fileService)); + this.workspaceStorage = this._register(new Storage(this.workspaceStorageDatabase)); this._register(this.workspaceStorage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key, scope: StorageScope.WORKSPACE }))); // Global Storage this.globalStorageFile = joinPath(stateRoot, 'global.json'); - this.globalStorage = new Storage(this._register(new FileStorageDatabase(this.globalStorageFile, this.fileService))); + this.globalStorageDatabase = this._register(new FileStorageDatabase(this.globalStorageFile, true /* watch for external changes */, this.fileService)); + this.globalStorage = this._register(new Storage(this.globalStorageDatabase)); this._register(this.globalStorage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key, scope: StorageScope.GLOBAL }))); // Init both @@ -131,8 +151,127 @@ export class BrowserStorageService extends Disposable implements IStorageService } close(): void { - - // Signal as event so that clients can still store data - this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); + // We explicitly do not close our DBs because writing data onBeforeUnload() + // can result in unexpected results. Namely, it seems that - even though this + // operation is async - sometimes it is being triggered on unload and + // succeeds. Often though, the DBs turn out to be empty because the write + // never had a chance to complete. + // + // Instead we trigger dispose() to ensure that no timeouts or callbacks + // get triggered in this phase. + this.dispose(); + } +} + +export class FileStorageDatabase extends Disposable implements IStorageDatabase { + + private readonly _onDidChangeItemsExternal: Emitter = this._register(new Emitter()); + readonly onDidChangeItemsExternal: Event = this._onDidChangeItemsExternal.event; + + private cache: Map | undefined; + + private pendingUpdate: Promise = Promise.resolve(); + + private _hasPendingUpdate = false; + get hasPendingUpdate(): boolean { + return this._hasPendingUpdate; + } + + private isWatching = false; + + constructor( + private readonly file: URI, + private readonly watchForExternalChanges: boolean, + @IFileService private readonly fileService: IFileService + ) { + super(); + } + + private async ensureWatching(): Promise { + if (this.isWatching || !this.watchForExternalChanges) { + return; + } + + const exists = await this.fileService.exists(this.file); + if (this.isWatching || !exists) { + return; // file must exist to be watched + } + + this.isWatching = true; + + this._register(this.fileService.watch(this.file)); + this._register(this.fileService.onFileChanges(e => { + if (document.hasFocus()) { + return; // optimization: ignore changes from ourselves by checking for focus + } + + if (!e.contains(this.file, FileChangeType.UPDATED)) { + return; // not our file + } + + this.onDidStorageChangeExternal(); + })); + } + + private async onDidStorageChangeExternal(): Promise { + const items = await this.doGetItemsFromFile(); + + this.cache = items; + + this._onDidChangeItemsExternal.fire({ items }); + } + + async getItems(): Promise> { + if (!this.cache) { + try { + this.cache = await this.doGetItemsFromFile(); + } catch (error) { + this.cache = new Map(); + } + } + + return this.cache; + } + + private async doGetItemsFromFile(): Promise> { + await this.pendingUpdate; + + const itemsRaw = await this.fileService.readFile(this.file); + + this.ensureWatching(); // now that the file must exist, ensure we watch it for changes + + return serializableToMap(JSON.parse(itemsRaw.value.toString())); + } + + async updateItems(request: IUpdateRequest): Promise { + const items = await this.getItems(); + + if (request.insert) { + request.insert.forEach((value, key) => items.set(key, value)); + } + + if (request.delete) { + request.delete.forEach(key => items.delete(key)); + } + + await this.pendingUpdate; + + this.pendingUpdate = (async () => { + try { + this._hasPendingUpdate = true; + + await this.fileService.writeFile(this.file, VSBuffer.fromString(JSON.stringify(mapToSerializable(items)))); + + this.ensureWatching(); // now that the file must exist, ensure we watch it for changes + } finally { + this._hasPendingUpdate = false; + } + })(); + + return this.pendingUpdate; + } + + close(): Promise { + return this.pendingUpdate; } } diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 5f6e2b6673f..8860535a54a 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -7,11 +7,6 @@ import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/co import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { IUpdateRequest, IStorageDatabase } from 'vs/base/parts/storage/common/storage'; -import { serializableToMap, mapToSerializable } from 'vs/base/common/map'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { URI } from 'vs/base/common/uri'; -import { IFileService } from 'vs/platform/files/common/files'; export const IStorageService = createDecorator('storageService'); @@ -212,64 +207,6 @@ export class InMemoryStorageService extends Disposable implements IStorageServic } } -export class FileStorageDatabase extends Disposable implements IStorageDatabase { - - readonly onDidChangeItemsExternal = Event.None; // TODO@Ben implement global UI storage events - - private cache: Map | undefined; - - private pendingUpdate: Promise = Promise.resolve(); - - constructor( - private readonly file: URI, - private readonly fileService: IFileService - ) { - super(); - } - - async getItems(): Promise> { - if (!this.cache) { - try { - this.cache = await this.doGetItemsFromFile(); - } catch (error) { - this.cache = new Map(); - } - } - - return this.cache; - } - - private async doGetItemsFromFile(): Promise> { - await this.pendingUpdate; - - const itemsRaw = await this.fileService.readFile(this.file); - - return serializableToMap(JSON.parse(itemsRaw.value.toString())); - } - - async updateItems(request: IUpdateRequest): Promise { - const items = await this.getItems(); - - if (request.insert) { - request.insert.forEach((value, key) => items.set(key, value)); - } - - if (request.delete) { - request.delete.forEach(key => items.delete(key)); - } - - await this.pendingUpdate; - - this.pendingUpdate = this.fileService.writeFile(this.file, VSBuffer.fromString(JSON.stringify(mapToSerializable(items)))).then(); - - return this.pendingUpdate; - } - - close(): Promise { - return this.pendingUpdate; - } -} - export async function logStorage(global: Map, workspace: Map, globalPath: string, workspacePath: string): Promise { const safeParse = (value: string) => { try { @@ -312,4 +249,4 @@ export async function logStorage(global: Map, workspace: Map; + /** + * Access to all cached items of this storage service. + */ + readonly items: Map; + + /** + * Required call to ensure the service can be used. + */ + initialize(): Promise; + /** * Retrieve an element stored with the given key from storage. Use * the provided defaultValue if the element is null or undefined. @@ -75,7 +85,7 @@ export interface IStorageChangeEvent { export class StorageMainService extends Disposable implements IStorageMainService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static STORAGE_NAME = 'state.vscdb'; diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index caff11a3483..ca09cbc9cc9 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -19,7 +19,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class StorageService extends Disposable implements IStorageService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static WORKSPACE_STORAGE_NAME = 'state.vscdb'; private static WORKSPACE_META_NAME = 'workspace.json'; @@ -81,7 +81,7 @@ export class StorageService extends Disposable implements IStorageService { const useInMemoryStorage = !!this.environmentService.extensionTestsLocationURI; // no storage during extension tests! - // Create workspace storage and initalize + // Create workspace storage and initialize mark('willInitWorkspaceStorage'); try { await this.createWorkspaceStorage(useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, StorageService.WORKSPACE_STORAGE_NAME), result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined).init(); diff --git a/src/vs/platform/storage/test/node/storage.test.ts b/src/vs/platform/storage/test/electron-browser/storage.test.ts similarity index 92% rename from src/vs/platform/storage/test/node/storage.test.ts rename to src/vs/platform/storage/test/electron-browser/storage.test.ts index 7c77b9e1c08..4e5e10a4e13 100644 --- a/src/vs/platform/storage/test/node/storage.test.ts +++ b/src/vs/platform/storage/test/electron-browser/storage.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { equal } from 'assert'; -import { FileStorageDatabase } from 'vs/platform/storage/common/storage'; +import { FileStorageDatabase } from 'vs/platform/storage/browser/storageService'; import { generateUuid } from 'vs/base/common/uuid'; import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; @@ -49,7 +49,7 @@ suite('Storage', () => { }); test('File Based Storage', async () => { - let storage = new Storage(new FileStorageDatabase(URI.file(join(testDir, 'storage.json')), fileService)); + let storage = new Storage(new FileStorageDatabase(URI.file(join(testDir, 'storage.json')), false, fileService)); await storage.init(); @@ -63,7 +63,7 @@ suite('Storage', () => { await storage.close(); - storage = new Storage(new FileStorageDatabase(URI.file(join(testDir, 'storage.json')), fileService)); + storage = new Storage(new FileStorageDatabase(URI.file(join(testDir, 'storage.json')), false, fileService)); await storage.init(); @@ -81,7 +81,7 @@ suite('Storage', () => { await storage.close(); - storage = new Storage(new FileStorageDatabase(URI.file(join(testDir, 'storage.json')), fileService)); + storage = new Storage(new FileStorageDatabase(URI.file(join(testDir, 'storage.json')), false, fileService)); await storage.init(); @@ -89,4 +89,4 @@ suite('Storage', () => { equal(storage.get('barNumber', 'undefinedNumber'), 'undefinedNumber'); equal(storage.get('barBoolean', 'undefinedBoolean'), 'undefinedBoolean'); }); -}); \ No newline at end of file +}); diff --git a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts index 3e6c88bd451..ac0da4361f7 100644 --- a/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/browser/workbenchCommonProperties.ts @@ -12,21 +12,23 @@ export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; import * as Platform from 'vs/base/common/platform'; import * as uuid from 'vs/base/common/uuid'; +import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; export async function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, remoteAuthority?: string): Promise<{ [name: string]: string | undefined }> { const result: { [name: string]: string | undefined; } = Object.create(null); - const instanceId = storageService.get(instanceStorageKey, StorageScope.GLOBAL)!; const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; + /** + * Note: In the web, session date information is fetched from browser storage, so these dates are tied to a specific + * browser and not the machine overall. + */ // __GDPR__COMMON__ "common.firstSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } result['common.firstSessionDate'] = firstSessionDate; // __GDPR__COMMON__ "common.lastSessionDate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } result['common.lastSessionDate'] = lastSessionDate || ''; // __GDPR__COMMON__ "common.isNewSession" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } result['common.isNewSession'] = !lastSessionDate ? '1' : '0'; - // __GDPR__COMMON__ "common.instanceId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - result['common.instanceId'] = instanceId; // __GDPR__COMMON__ "common.remoteAuthority" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } result['common.remoteAuthority'] = cleanRemoteAuthority(remoteAuthority); @@ -69,18 +71,3 @@ export async function resolveWorkbenchCommonProperties(storageService: IStorageS return result; } -function cleanRemoteAuthority(remoteAuthority?: string): string { - if (!remoteAuthority) { - return 'none'; - } - - let ret = 'other'; - // Whitelisted remote authorities - ['ssh-remote', 'dev-container', 'wsl'].forEach((res: string) => { - if (remoteAuthority!.indexOf(`${res}+`) === 0) { - ret = res; - } - }); - - return ret; -} diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index f8990d64a32..d98abe9e156 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -288,6 +288,22 @@ export function validateTelemetryData(data?: any): { properties: Properties, mea }; } +export function cleanRemoteAuthority(remoteAuthority?: string): string { + if (!remoteAuthority) { + return 'none'; + } + + let ret = 'other'; + // Whitelisted remote authorities + ['ssh-remote', 'dev-container', 'attached-container', 'wsl'].forEach((res: string) => { + if (remoteAuthority!.indexOf(`${res}+`) === 0) { + ret = res; + } + }); + + return ret; +} + function flatten(obj: any, result: { [key: string]: any }, order: number = 0, prefix?: string): void { if (!obj) { return; diff --git a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts index d1b83a14146..81d6a4d3d22 100644 --- a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts @@ -6,6 +6,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; export async function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, installSourcePath: string, remoteAuthority?: string): Promise<{ [name: string]: string | undefined }> { const result = await resolveCommonProperties(commit, version, machineId, installSourcePath); @@ -30,19 +31,3 @@ export async function resolveWorkbenchCommonProperties(storageService: IStorageS return result; } - -function cleanRemoteAuthority(remoteAuthority?: string): string { - if (!remoteAuthority) { - return 'none'; - } - - let ret = 'other'; - // Whitelisted remote authorities - ['ssh-remote', 'dev-container', 'wsl'].forEach((res: string) => { - if (remoteAuthority!.indexOf(`${res}+`) === 0) { - ret = res; - } - }); - - return ret; -} diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 6f356bfe66b..47b428fbb84 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -222,6 +222,10 @@ export const selectListBackground = registerColor('dropdown.listBackground', { d export const selectForeground = registerColor('dropdown.foreground', { dark: '#F0F0F0', light: null, hc: Color.white }, nls.localize('dropdownForeground', "Dropdown foreground.")); export const selectBorder = registerColor('dropdown.border', { dark: selectBackground, light: '#CECECE', hc: contrastBorder }, nls.localize('dropdownBorder', "Dropdown border.")); +export const simpleCheckboxBackground = registerColor('checkbox.background', { dark: selectBackground, light: selectBackground, hc: selectBackground }, nls.localize('checkbox.background', "Background color of checkbox widget.")); +export const simpleCheckboxForeground = registerColor('checkbox.foreground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, nls.localize('checkbox.foreground', "Foreground color of checkbox widget.")); +export const simpleCheckboxBorder = registerColor('checkbox.border', { dark: selectBorder, light: selectBorder, hc: selectBorder }, nls.localize('checkbox.border', "Border color of checkbox widget.")); + export const listFocusBackground = registerColor('list.focusBackground', { dark: '#062F4A', light: '#D6EBFF', hc: null }, nls.localize('listFocusBackground', "List/Tree background color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listFocusForeground = registerColor('list.focusForeground', { dark: null, light: null, hc: null }, nls.localize('listFocusForeground', "List/Tree foreground color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#094771', light: '#0074E8', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index c1732ca1735..40de3567375 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; +import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -341,6 +341,9 @@ export interface IDialogStyleOverrides extends IButtonStyleOverrides { dialogBackground?: ColorIdentifier; dialogShadow?: ColorIdentifier; dialogBorder?: ColorIdentifier; + checkboxBorder?: ColorIdentifier; + checkboxBackground?: ColorIdentifier; + checkboxForeground?: ColorIdentifier; } export const defaultDialogStyles = { @@ -351,7 +354,10 @@ export const defaultDialogStyles = { buttonForeground: buttonForeground, buttonBackground: buttonBackground, buttonHoverBackground: buttonHoverBackground, - buttonBorder: contrastBorder + buttonBorder: contrastBorder, + checkboxBorder: simpleCheckboxBorder, + checkboxBackground: simpleCheckboxBackground, + checkboxForeground: simpleCheckboxForeground }; diff --git a/src/vs/platform/update/electron-browser/updateService.ts b/src/vs/platform/update/electron-browser/updateService.ts index 952c39cdbed..bd8fa6cc185 100644 --- a/src/vs/platform/update/electron-browser/updateService.ts +++ b/src/vs/platform/update/electron-browser/updateService.ts @@ -11,7 +11,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class UpdateService implements IUpdateService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private _onStateChange = new Emitter(); readonly onStateChange: Event = this._onStateChange.event; diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 2a1240b1eb9..d789bf4e09b 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -81,8 +81,15 @@ export abstract class AbstractUpdateService implements IUpdateService { return; } - // Start checking for updates after 30 seconds - this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err)); + if (updateMode === 'start') { + this.logService.info('update#ctor - startup checks only; automatic updates are disabled by user preference'); + + // Check for updates only once after 30 seconds + setTimeout(() => this.checkForUpdates(null), 30 * 1000); + } else { + // Start checking for updates after 30 seconds + this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err)); + } } private getProductQuality(updateMode: string): string | undefined { diff --git a/src/vs/platform/update/node/update.config.contribution.ts b/src/vs/platform/update/node/update.config.contribution.ts index b6ef43b29fa..fe7b3cba8b2 100644 --- a/src/vs/platform/update/node/update.config.contribution.ts +++ b/src/vs/platform/update/node/update.config.contribution.ts @@ -17,7 +17,7 @@ configurationRegistry.registerConfiguration({ properties: { 'update.mode': { type: 'string', - enum: ['none', 'manual', 'default'], + enum: ['none', 'manual', 'start', 'default'], default: 'default', scope: ConfigurationScope.APPLICATION, description: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."), @@ -25,6 +25,7 @@ configurationRegistry.registerConfiguration({ enumDescriptions: [ localize('none', "Disable updates."), localize('manual', "Disable automatic background update checks. Updates will be available if you manually check for updates."), + localize('start', "Check for updates only on startup. Disable automatic background update checks."), localize('default', "Enable automatic update checks. Code will check for updates automatically and periodically.") ] }, diff --git a/src/vs/platform/url/common/urlService.ts b/src/vs/platform/url/common/urlService.ts index afcfff01be4..66d4abd6722 100644 --- a/src/vs/platform/url/common/urlService.ts +++ b/src/vs/platform/url/common/urlService.ts @@ -12,7 +12,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class URLService implements IURLService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private handlers = new Set(); diff --git a/src/vs/platform/url/electron-browser/urlService.ts b/src/vs/platform/url/electron-browser/urlService.ts index 06b5d7c31f0..1b1c88721b7 100644 --- a/src/vs/platform/url/electron-browser/urlService.ts +++ b/src/vs/platform/url/electron-browser/urlService.ts @@ -27,12 +27,16 @@ export class RelayURLService extends URLService implements IURLHandler { openerService.registerOpener(this); } - async open(uri: URI): Promise { - if (uri.scheme !== product.urlProtocol) { + async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { + if (options && options.openExternal) { return false; } - return await this.urlService.open(uri); + if (resource.scheme !== product.urlProtocol) { + return false; + } + + return await this.urlService.open(resource); } handleURL(uri: URI): Promise { diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 0339be71c36..bca1181e591 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -96,12 +96,12 @@ export interface IWindowsService { _serviceBrand: any; - onWindowOpen: Event; - onWindowFocus: Event; - onWindowBlur: Event; - onWindowMaximize: Event; - onWindowUnmaximize: Event; - onRecentlyOpenedChange: Event; + readonly onWindowOpen: Event; + readonly onWindowFocus: Event; + readonly onWindowBlur: Event; + readonly onWindowMaximize: Event; + readonly onWindowUnmaximize: Event; + readonly onRecentlyOpenedChange: Event; // Dialogs pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise; @@ -240,6 +240,8 @@ export interface IWindowService { closeWorkspace(): Promise; updateTouchBar(items: ISerializableCommandAction[][]): Promise; enterWorkspace(path: URI): Promise; + // rationale: will eventually move to electron-browser + // tslint:disable-next-line: no-dom-globals toggleFullScreen(target?: HTMLElement): Promise; setRepresentedFilename(fileName: string): Promise; getRecentlyOpened(): Promise; @@ -303,7 +305,7 @@ export function getTitleBarStyle(configurationService: IConfigurationService, en return 'native'; // native tabs on sierra do not work with custom title style } - const useSimpleFullScreen = false; //isMacintosh && configuration.nativeFullScreen === false; + const useSimpleFullScreen = isMacintosh && configuration.nativeFullScreen === false; if (useSimpleFullScreen) { return 'native'; // simple fullscreen does not work well with custom title style (https://github.com/Microsoft/vscode/issues/63291) } @@ -426,6 +428,7 @@ export interface IWindowConfiguration extends ParsedArgs { folderUri?: ISingleFolderWorkspaceIdentifier; remoteAuthority?: string; + connectionToken?: string; zoomLevel?: number; fullscreen?: boolean; diff --git a/src/vs/platform/windows/node/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts similarity index 94% rename from src/vs/platform/windows/node/windowsIpc.ts rename to src/vs/platform/windows/common/windowsIpc.ts index 6fbad27a549..3cdf71e2f4a 100644 --- a/src/vs/platform/windows/node/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -12,14 +12,14 @@ import { IRecent, isRecentFile, isRecentFolder } from 'vs/platform/history/commo export class WindowsChannel implements IServerChannel { - private onWindowOpen: Event; - private onWindowFocus: Event; - private onWindowBlur: Event; - private onWindowMaximize: Event; - private onWindowUnmaximize: Event; - private onRecentlyOpenedChange: Event; + private readonly onWindowOpen: Event; + private readonly onWindowFocus: Event; + private readonly onWindowBlur: Event; + private readonly onWindowMaximize: Event; + private readonly onWindowUnmaximize: Event; + private readonly onRecentlyOpenedChange: Event; - constructor(private service: IWindowsService) { + constructor(private readonly service: IWindowsService) { this.onWindowOpen = Event.buffer(service.onWindowOpen, true); this.onWindowFocus = Event.buffer(service.onWindowFocus, true); this.onWindowBlur = Event.buffer(service.onWindowBlur, true); @@ -120,4 +120,4 @@ export class WindowsChannel implements IServerChannel { throw new Error(`Call not found: ${command}`); } -} \ No newline at end of file +} diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index 228dfd98ba2..57bd96143af 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -13,17 +13,14 @@ import { URI } from 'vs/base/common/uri'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { IProcessEnvironment } from 'vs/base/common/platform'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class WindowsService implements IWindowsService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; private channel: IChannel; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - this.channel = mainProcessService.getChannel('windows'); - } - get onWindowOpen(): Event { return this.channel.listen('onWindowOpen'); } get onWindowFocus(): Event { return this.channel.listen('onWindowFocus'); } get onWindowBlur(): Event { return this.channel.listen('onWindowBlur'); } @@ -31,6 +28,10 @@ export class WindowsService implements IWindowsService { get onWindowUnmaximize(): Event { return this.channel.listen('onWindowUnmaximize'); } get onRecentlyOpenedChange(): Event { return this.channel.listen('onRecentlyOpenedChange'); } + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + this.channel = mainProcessService.getChannel('windows'); + } + pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise { return this.channel.call('pickFileFolderAndOpen', options); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index c33bbb42b8a..00af7fe6715 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -27,7 +27,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class WindowsService extends Disposable implements IWindowsService, IURLHandler { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly disposables = this._register(new DisposableStore()); 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 6d3f1024f3e..16b3f1d60d7 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts @@ -17,6 +17,7 @@ import { URI } from 'vs/base/common/uri'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { isWindows } from 'vs/base/common/platform'; import { normalizeDriveLetter } from 'vs/base/common/labels'; +import { dirname, joinPath } from 'vs/base/common/resources'; suite('WorkspacesMainService', () => { const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'workspacesservice'); @@ -51,13 +52,13 @@ suite('WorkspacesMainService', () => { let service: TestWorkspacesMainService; - setup(() => { + setup(async () => { service = new TestWorkspacesMainService(environmentService, logService); // Delete any existing backups completely and then re-create it. - return pfs.rimraf(untitledWorkspacesHomePath, pfs.RimRafMode.MOVE).then(() => { - return pfs.mkdirp(untitledWorkspacesHomePath); - }); + await pfs.rimraf(untitledWorkspacesHomePath, pfs.RimRafMode.MOVE); + + return pfs.mkdirp(untitledWorkspacesHomePath); }); teardown(() => { @@ -77,57 +78,50 @@ suite('WorkspacesMainService', () => { assert.equal(u1.toString(), u2.toString()); } - test('createWorkspace (folders)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath.fsPath)); - assert.ok(service.isUntitledWorkspace(workspace)); + test('createWorkspace (folders)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + assert.ok(workspace); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); + assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 2); // - assertPathEquals((ws.folders[0]).path, process.cwd()); - assertPathEquals((ws.folders[1]).path, os.tmpdir()); - - assert.ok(!(ws.folders[0]).name); - assert.ok(!(ws.folders[1]).name); - }); + const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); + assert.equal(ws.folders.length, 2); + assertPathEquals((ws.folders[0]).path, process.cwd()); + assertPathEquals((ws.folders[1]).path, os.tmpdir()); + assert.ok(!(ws.folders[0]).name); + assert.ok(!(ws.folders[1]).name); }); - test('createWorkspace (folders with name)', () => { - return createWorkspace([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']).then(workspace => { - assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath.fsPath)); - assert.ok(service.isUntitledWorkspace(workspace)); + test('createWorkspace (folders with name)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']); + assert.ok(workspace); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); + assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 2); // - assertPathEquals((ws.folders[0]).path, process.cwd()); - assertPathEquals((ws.folders[1]).path, os.tmpdir()); - - assert.equal((ws.folders[0]).name, 'currentworkingdirectory'); - assert.equal((ws.folders[1]).name, 'tempdir'); - }); + const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); + assert.equal(ws.folders.length, 2); + assertPathEquals((ws.folders[0]).path, process.cwd()); + assertPathEquals((ws.folders[1]).path, os.tmpdir()); + assert.equal((ws.folders[0]).name, 'currentworkingdirectory'); + assert.equal((ws.folders[1]).name, 'tempdir'); }); - test('createUntitledWorkspace (folders as other resource URIs)', () => { + test('createUntitledWorkspace (folders as other resource URIs)', async () => { const folder1URI = URI.parse('myscheme://server/work/p/f1'); const folder2URI = URI.parse('myscheme://server/work/o/f3'); - return service.createUntitledWorkspace([{ uri: folder1URI }, { uri: folder2URI }], 'server').then(workspace => { - assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath.fsPath)); - assert.ok(service.isUntitledWorkspace(workspace)); + const workspace = await service.createUntitledWorkspace([{ uri: folder1URI }, { uri: folder2URI }], 'server'); + assert.ok(workspace); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); + assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 2); - assert.equal((ws.folders[0]).uri, folder1URI.toString(true)); - assert.equal((ws.folders[1]).uri, folder2URI.toString(true)); - - assert.ok(!(ws.folders[0]).name); - assert.ok(!(ws.folders[1]).name); - - assert.equal(ws.remoteAuthority, 'server'); - }); + const ws = (JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace); + assert.equal(ws.folders.length, 2); + assert.equal((ws.folders[0]).uri, folder1URI.toString(true)); + assert.equal((ws.folders[1]).uri, folder2URI.toString(true)); + assert.ok(!(ws.folders[0]).name); + assert.ok(!(ws.folders[1]).name); + assert.equal(ws.remoteAuthority, 'server'); }); test('createWorkspaceSync (folders)', () => { @@ -178,145 +172,130 @@ suite('WorkspacesMainService', () => { assert.ok(!(ws.folders[1]).name); }); - test('resolveWorkspaceSync', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - assert.ok(service.resolveLocalWorkspaceSync(workspace.configPath)); + test('resolveWorkspaceSync', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + 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); + // 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.resolveLocalWorkspaceSync(workspace.configPath); - assert.equal(2, resolved!.folders.length); - assertEqualURI(resolved!.configPath, workspace.configPath); - assert.ok(resolved!.id); + 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 - fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ something: 'something' })); // invalid workspace - const resolvedInvalid = service.resolveLocalWorkspaceSync(workspace.configPath); - assert.ok(!resolvedInvalid); - }); + const resolvedInvalid = service.resolveLocalWorkspaceSync(workspace.configPath); + assert.ok(!resolvedInvalid); }); - test('resolveWorkspaceSync (support relative paths)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] })); + test('resolveWorkspaceSync (support relative paths)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] })); - const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); - assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); - }); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); - test('resolveWorkspaceSync (support relative paths #2)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] })); + test('resolveWorkspaceSync (support relative paths #2)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] })); - const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); - assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'other'))); - }); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'other'))); }); - test('resolveWorkspaceSync (support relative paths #3)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] })); + test('resolveWorkspaceSync (support relative paths #3)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] })); - const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); - assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); - }); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); - test('resolveWorkspaceSync (support invalid JSON via fault tolerant parsing)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath.fsPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma + test('resolveWorkspaceSync (support invalid JSON via fault tolerant parsing)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + fs.writeFileSync(workspace.configPath.fsPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma - const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); - assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); - }); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); - test('rewriteWorkspaceFileForNewLocation', () => { + test('rewriteWorkspaceFileForNewLocation', async () => { const folder1 = process.cwd(); // absolute path because outside of tmpDir const tmpDir = os.tmpdir(); const tmpInsideDir = path.join(os.tmpdir(), 'inside'); - return createWorkspace([folder1, tmpInsideDir, path.join(tmpInsideDir, 'somefolder')]).then(workspace => { - const origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + const workspace = await createWorkspace([folder1, tmpInsideDir, path.join(tmpInsideDir, 'somefolder')]); + const origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); - let origConfigPath = workspace.configPath; - let workspaceConfigPath = URI.file(path.join(tmpDir, 'inside', 'myworkspace1.code-workspace')); - let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, workspaceConfigPath); + let origConfigPath = workspace.configPath; + let workspaceConfigPath = URI.file(path.join(tmpDir, 'inside', 'myworkspace1.code-workspace')); + let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, workspaceConfigPath); + let ws = (JSON.parse(newContent) as IStoredWorkspace); + assert.equal(ws.folders.length, 3); + assertPathEquals((ws.folders[0]).path, folder1); // absolute path because outside of tmpdir + assertPathEquals((ws.folders[1]).path, '.'); + assertPathEquals((ws.folders[2]).path, 'somefolder'); - let ws = JSON.parse(newContent) as IStoredWorkspace; - assert.equal(ws.folders.length, 3); - assertPathEquals((ws.folders[0]).path, folder1); // absolute path because outside of tmpdir - assertPathEquals((ws.folders[1]).path, '.'); - assertPathEquals((ws.folders[2]).path, 'somefolder'); + origConfigPath = workspaceConfigPath; + workspaceConfigPath = URI.file(path.join(tmpDir, 'myworkspace2.code-workspace')); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + ws = (JSON.parse(newContent) as IStoredWorkspace); + assert.equal(ws.folders.length, 3); + assertPathEquals((ws.folders[0]).path, folder1); + assertPathEquals((ws.folders[1]).path, 'inside'); + assertPathEquals((ws.folders[2]).path, isWindows ? 'inside\\somefolder' : 'inside/somefolder'); - origConfigPath = workspaceConfigPath; - workspaceConfigPath = URI.file(path.join(tmpDir, 'myworkspace2.code-workspace')); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + origConfigPath = workspaceConfigPath; + workspaceConfigPath = URI.file(path.join(tmpDir, 'other', 'myworkspace2.code-workspace')); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + ws = (JSON.parse(newContent) as IStoredWorkspace); + assert.equal(ws.folders.length, 3); + assertPathEquals((ws.folders[0]).path, folder1); + assertPathEquals((ws.folders[1]).path, tmpInsideDir); + assertPathEquals((ws.folders[2]).path, path.join(tmpInsideDir, 'somefolder')); - ws = JSON.parse(newContent) as IStoredWorkspace; - assert.equal(ws.folders.length, 3); - assertPathEquals((ws.folders[0]).path, folder1); - assertPathEquals((ws.folders[1]).path, 'inside'); - assertPathEquals((ws.folders[2]).path, isWindows ? 'inside\\somefolder' : 'inside/somefolder'); + origConfigPath = workspaceConfigPath; + workspaceConfigPath = URI.parse('foo://foo/bar/myworkspace2.code-workspace'); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + ws = (JSON.parse(newContent) as IStoredWorkspace); + assert.equal(ws.folders.length, 3); + assert.equal((ws.folders[0]).uri, URI.file(folder1).toString(true)); + assert.equal((ws.folders[1]).uri, URI.file(tmpInsideDir).toString(true)); + assert.equal((ws.folders[2]).uri, URI.file(path.join(tmpInsideDir, 'somefolder')).toString(true)); - origConfigPath = workspaceConfigPath; - workspaceConfigPath = URI.file(path.join(tmpDir, 'other', 'myworkspace2.code-workspace')); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); - - ws = JSON.parse(newContent) as IStoredWorkspace; - assert.equal(ws.folders.length, 3); - assertPathEquals((ws.folders[0]).path, folder1); - assertPathEquals((ws.folders[1]).path, tmpInsideDir); - assertPathEquals((ws.folders[2]).path, path.join(tmpInsideDir, 'somefolder')); - - origConfigPath = workspaceConfigPath; - workspaceConfigPath = URI.parse('foo://foo/bar/myworkspace2.code-workspace'); - newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); - - ws = JSON.parse(newContent) as IStoredWorkspace; - assert.equal(ws.folders.length, 3); - assert.equal((ws.folders[0]).uri, URI.file(folder1).toString(true)); - assert.equal((ws.folders[1]).uri, URI.file(tmpInsideDir).toString(true)); - assert.equal((ws.folders[2]).uri, URI.file(path.join(tmpInsideDir, 'somefolder')).toString(true)); - - service.deleteUntitledWorkspaceSync(workspace); - }); + service.deleteUntitledWorkspaceSync(workspace); }); - test('rewriteWorkspaceFileForNewLocation (preserves comments)', () => { - return createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]).then(workspace => { - const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); + test('rewriteWorkspaceFileForNewLocation (preserves comments)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]); + const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); - let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); - origContent = `// this is a comment\n${origContent}`; + let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + origContent = `// this is a comment\n${origContent}`; - let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); - - assert.equal(0, newContent.indexOf('// this is a comment')); - - service.deleteUntitledWorkspaceSync(workspace); - }); + let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + assert.equal(0, newContent.indexOf('// this is a comment')); + service.deleteUntitledWorkspaceSync(workspace); }); - test('rewriteWorkspaceFileForNewLocation (preserves forward slashes)', () => { - return createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]).then(workspace => { - const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); - let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); - origContent = origContent.replace(/[\\]/g, '/'); // convert backslash to slash + test('rewriteWorkspaceFileForNewLocation (preserves forward slashes)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]); + const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); - const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + origContent = origContent.replace(/[\\]/g, '/'); // convert backslash to slash - const ws = JSON.parse(newContent) as IStoredWorkspace; - assert.ok(ws.folders.every(f => (f).path.indexOf('\\') < 0)); - - service.deleteUntitledWorkspaceSync(workspace); - }); + const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + const ws = (JSON.parse(newContent) as IStoredWorkspace); + assert.ok(ws.folders.every(f => (f).path.indexOf('\\') < 0)); + service.deleteUntitledWorkspaceSync(workspace); }); - test('rewriteWorkspaceFileForNewLocation (unc paths)', () => { + test('rewriteWorkspaceFileForNewLocation (unc paths)', async () => { if (!isWindows) { return Promise.resolve(); } @@ -326,68 +305,59 @@ suite('WorkspacesMainService', () => { const folder2Location = '\\\\server\\share2\\some\\path'; const folder3Location = path.join(os.tmpdir(), 'wsloc', 'inner', 'more'); - return createWorkspace([folder1Location, folder2Location, folder3Location]).then(workspace => { - const workspaceConfigPath = URI.file(path.join(workspaceLocation, `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); - let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + const workspace = await createWorkspace([folder1Location, folder2Location, folder3Location]); + const workspaceConfigPath = URI.file(path.join(workspaceLocation, `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); + let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); + const ws = (JSON.parse(newContent) as IStoredWorkspace); + assertPathEquals((ws.folders[0]).path, folder1Location); + assertPathEquals((ws.folders[1]).path, folder2Location); + assertPathEquals((ws.folders[2]).path, 'inner\\more'); - const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); - - const ws = JSON.parse(newContent) as IStoredWorkspace; - assertPathEquals((ws.folders[0]).path, folder1Location); - assertPathEquals((ws.folders[1]).path, folder2Location); - assertPathEquals((ws.folders[2]).path, 'inner\\more'); - - service.deleteUntitledWorkspaceSync(workspace); - }); + service.deleteUntitledWorkspaceSync(workspace); }); - test('deleteUntitledWorkspaceSync (untitled)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - assert.ok(fs.existsSync(workspace.configPath.fsPath)); - - service.deleteUntitledWorkspaceSync(workspace); - - assert.ok(!fs.existsSync(workspace.configPath.fsPath)); - }); + test('deleteUntitledWorkspaceSync (untitled)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); + service.deleteUntitledWorkspaceSync(workspace); + assert.ok(!fs.existsSync(workspace.configPath.fsPath)); }); - test('deleteUntitledWorkspaceSync (saved)', () => { - return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - service.deleteUntitledWorkspaceSync(workspace); - }); + test('deleteUntitledWorkspaceSync (saved)', async () => { + const workspace = await createWorkspace([process.cwd(), os.tmpdir()]); + service.deleteUntitledWorkspaceSync(workspace); }); - test('getUntitledWorkspaceSync', () => { + test('getUntitledWorkspaceSync', async () => { let untitled = service.getUntitledWorkspacesSync(); assert.equal(untitled.length, 0); - return createWorkspace([process.cwd(), os.tmpdir()]).then(untitledOne => { - assert.ok(fs.existsSync(untitledOne.configPath.fsPath)); + const untitledOne = await createWorkspace([process.cwd(), os.tmpdir()]); + assert.ok(fs.existsSync(untitledOne.configPath.fsPath)); - untitled = service.getUntitledWorkspacesSync(); + untitled = service.getUntitledWorkspacesSync(); + assert.equal(1, untitled.length); + assert.equal(untitledOne.id, untitled[0].workspace.id); - assert.equal(1, untitled.length); - assert.equal(untitledOne.id, untitled[0].workspace.id); + const untitledTwo = await createWorkspace([os.tmpdir(), process.cwd()]); + assert.ok(fs.existsSync(untitledTwo.configPath.fsPath)); + assert.ok(fs.existsSync(untitledOne.configPath.fsPath), `Unexpected workspaces count of 1 (expected 2): ${untitledOne.configPath.fsPath} does not exist anymore?`); + const untitledHome = dirname(dirname(untitledTwo.configPath)); + const beforeGettingUntitledWorkspaces = fs.readdirSync(untitledHome.fsPath).map(name => fs.readFileSync(joinPath(untitledHome, name, 'workspace.json').fsPath, 'utf8')); + untitled = service.getUntitledWorkspacesSync(); + assert.ok(fs.existsSync(untitledOne.configPath.fsPath), `Unexpected workspaces count of 1 (expected 2): ${untitledOne.configPath.fsPath} does not exist anymore?`); + if (untitled.length === 1) { + assert.fail(`Unexpected workspaces count of 1 (expected 2), all workspaces:\n ${fs.readdirSync(untitledHome.fsPath).map(name => fs.readFileSync(joinPath(untitledHome, name, 'workspace.json').fsPath, 'utf8'))}, before getUntitledWorkspacesSync: ${beforeGettingUntitledWorkspaces}`); + } + assert.equal(2, untitled.length); - return createWorkspace([os.tmpdir(), process.cwd()]).then(untitledTwo => { - assert.ok(fs.existsSync(untitledTwo.configPath.fsPath)); + service.deleteUntitledWorkspaceSync(untitledOne); + untitled = service.getUntitledWorkspacesSync(); + assert.equal(1, untitled.length); - untitled = service.getUntitledWorkspacesSync(); - - if (untitled.length === 1) { - assert.fail('Unexpected workspaces count, contents:\n' + fs.readFileSync(untitledTwo.configPath.fsPath, 'utf8')); - } - - assert.equal(2, untitled.length); - - service.deleteUntitledWorkspaceSync(untitledOne); - untitled = service.getUntitledWorkspacesSync(); - assert.equal(1, untitled.length); - - service.deleteUntitledWorkspaceSync(untitledTwo); - untitled = service.getUntitledWorkspacesSync(); - assert.equal(0, untitled.length); - }); - }); + service.deleteUntitledWorkspaceSync(untitledTwo); + untitled = service.getUntitledWorkspacesSync(); + assert.equal(0, untitled.length); }); }); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index d1e2a6c8167..3611c2746ab 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2786,7 +2786,7 @@ declare module 'vscode' { * *Note* that the eol-sequence will be applied to the * whole document. */ - newEol: EndOfLine; + newEol?: EndOfLine; /** * Create a new TextEdit. @@ -5208,7 +5208,7 @@ declare module 'vscode' { */ export enum TaskScope { /** - * The task is a global task + * The task is a global task. Global tasks are currrently not supported. */ Global = 1, @@ -5237,7 +5237,7 @@ declare module 'vscode' { * Creates a new task. * * @param definition The task definition as defined in the taskDefinitions extension point. - * @param scope Specifies the task's scope. It is either a global or a workspace task or a task for a specific workspace folder. + * @param scope Specifies the task's scope. It is either a global or a workspace task or a task for a specific workspace folder. Global tasks are currently not supported. * @param name The task's name. Is presented in the user interface. * @param source The task's source (e.g. 'gulp', 'npm', ...). Is presented in the user interface. * @param execution The process or shell execution. @@ -5923,6 +5923,30 @@ declare module 'vscode' { * @param message Body of the message. */ postMessage(message: any): Thenable; + + /** + * Convert a uri for the local file system to one that can be used inside webviews. + * + * Webviews cannot directly load resoruces from the workspace or local file system using `file:` uris. The + * `asWebviewUri` function takes a local `file:` uri and converts it into a uri that can be used inside of + * a webview to load the same resource: + * + * ```ts + * webview.html = `` + * ``` + */ + asWebviewUri(localResource: Uri): Uri; + + /** + * Content security policy source for webview resources. + * + * This is the origin that should be used in a content security policy rule: + * + * ``` + * img-src https: ${webview.cspSource} ...; + * ``` + */ + readonly cspSource: string; } /** @@ -8092,9 +8116,14 @@ declare module 'vscode' { * result. A failing provider (rejected promise or exception) will not fail the whole * operation. * + * A completion item provider can be associated with a set of `triggerCharacters`. When trigger + * characters are being typed, completions are requested but only from providers that registered + * the typed character. Because of that trigger characters should be different than [word characters](#LanguageConfiguration.wordPattern), + * a common trigger character is `.` to trigger member completions. + * * @param selector A selector that defines the documents this provider is applicable to. * @param provider A completion provider. - * @param triggerCharacters Trigger completion when the user types one of the characters, like `.` or `:`. + * @param triggerCharacters Trigger completion when the user types one of the characters. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ export function registerCompletionItemProvider(selector: DocumentSelector, provider: CompletionItemProvider, ...triggerCharacters: string[]): Disposable; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index a5b0357aa86..1628e28fb27 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -565,22 +565,6 @@ declare module 'vscode' { //#endregion - //#region Joh: onDidExecuteCommand - - export interface CommandExecutionEvent { - command: string; - arguments: any[]; - } - - export namespace commands { - /** - * An event that is emitted when a [command](#Command) is executed. - */ - export const onDidExecuteCommand: Event; - } - - //#endregion - //#region Joh: decorations //todo@joh -> make class @@ -752,11 +736,29 @@ declare module 'vscode' { readonly dimensions: TerminalDimensions; } + export interface TerminalDataWriteEvent { + /** + * The [terminal](#Terminal) for which the data was written. + */ + readonly terminal: Terminal; + /** + * The data being written. + */ + readonly data: string; + } + namespace window { /** * An event which fires when the [dimensions](#Terminal.dimensions) of the terminal change. */ export const onDidChangeTerminalDimensions: Event; + + /** + * An event which fires when the terminal's pty slave pseudo-device is written to. In other + * words, this provides access to the raw data stream from the process running within the + * terminal, including VT sequences. + */ + export const onDidWriteTerminalData: Event; } export interface Terminal { @@ -771,22 +773,12 @@ declare module 'vscode' { * Fires when the terminal's pty slave pseudo-device is written to. In other words, this * provides access to the raw data stream from the process running within the terminal, * including VT sequences. + * + * @deprecated Use [window.onDidWriteTerminalData](#onDidWriteTerminalData). */ readonly onDidWriteData: Event; } - - export interface TerminalOptions { - /** - * When enabled the terminal will run the process as normal but not be surfaced to the user - * until `Terminal.show` is called. The typical usage for this is when you need to run - * something that may need interactivity but only want to tell the user about it when - * interaction is needed. Note that the terminals will still be exposed to all extensions - * as normal. - */ - runInBackground?: boolean; - } - /** * Represents the dimensions of a terminal. */ @@ -802,134 +794,13 @@ declare module 'vscode' { readonly rows: number; } - /** - * Represents a terminal without a process where all interaction and output in the terminal is - * controlled by an extension. This is similar to an output window but has the same VT sequence - * compatibility as the regular terminal. - * - * Note that an instance of [Terminal](#Terminal) will be created when a TerminalRenderer is - * created with all its APIs available for use by extensions. When using the Terminal object - * of a TerminalRenderer it acts just like normal only the extension that created the - * TerminalRenderer essentially acts as a process. For example when an - * [Terminal.onDidWriteData](#Terminal.onDidWriteData) listener is registered, that will fire - * when [TerminalRenderer.write](#TerminalRenderer.write) is called. Similarly when - * [Terminal.sendText](#Terminal.sendText) is triggered that will fire the - * [TerminalRenderer.onDidAcceptInput](#TerminalRenderer.onDidAcceptInput) event. - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - * - * **Example:** Create a terminal renderer, show it and write hello world in red - * ```typescript - * const renderer = window.createTerminalRenderer('foo'); - * renderer.terminal.then(t => t.show()); - * renderer.write('\x1b[31mHello world\x1b[0m'); - * ``` - */ - export interface TerminalRenderer { - /** - * The name of the terminal, this will appear in the terminal selector. - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - */ - name: string; - - /** - * The dimensions of the terminal, the rows and columns of the terminal can only be set to - * a value smaller than the maximum value, if this is undefined the terminal will auto fit - * to the maximum value [maximumDimensions](TerminalRenderer.maximumDimensions). - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - * - * **Example:** Override the dimensions of a TerminalRenderer to 20 columns and 10 rows - * ```typescript - * terminalRenderer.dimensions = { - * cols: 20, - * rows: 10 - * }; - * ``` - */ - dimensions: TerminalDimensions | undefined; - - /** - * The maximum dimensions of the terminal, this will be undefined immediately after a - * terminal renderer is created and also until the terminal becomes visible in the UI. - * Listen to [onDidChangeMaximumDimensions](TerminalRenderer.onDidChangeMaximumDimensions) - * to get notified when this value changes. - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - */ - readonly maximumDimensions: TerminalDimensions | undefined; - - /** - * The corresponding [Terminal](#Terminal) for this TerminalRenderer. - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - */ - readonly terminal: Terminal; - - /** - * Write text to the terminal. Unlike [Terminal.sendText](#Terminal.sendText) which sends - * text to the underlying _process_, this will write the text to the terminal itself. - * - * @param text The text to write. - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - * - * **Example:** Write red text to the terminal - * ```typescript - * terminalRenderer.write('\x1b[31mHello world\x1b[0m'); - * ``` - * - * **Example:** Move the cursor to the 10th row and 20th column and write an asterisk - * ```typescript - * terminalRenderer.write('\x1b[10;20H*'); - * ``` - */ - write(text: string): void; - - /** - * An event which fires on keystrokes in the terminal or when an extension calls - * [Terminal.sendText](#Terminal.sendText). Keystrokes are converted into their - * corresponding VT sequence representation. - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - * - * **Example:** Simulate interaction with the terminal from an outside extension or a - * workbench command such as `workbench.action.terminal.runSelectedText` - * ```typescript - * const terminalRenderer = window.createTerminalRenderer('test'); - * terminalRenderer.onDidAcceptInput(data => { - * console.log(data); // 'Hello world' - * }); - * terminalRenderer.terminal.sendText('Hello world'); - * ``` - */ - readonly onDidAcceptInput: Event; - - /** - * An event which fires when the [maximum dimensions](#TerminalRenderer.maximumDimensions) of - * the terminal renderer change. - * - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - */ - readonly onDidChangeMaximumDimensions: Event; - } - - export namespace window { - /** - * Create a [TerminalRenderer](#TerminalRenderer). - * - * @param name The name of the terminal renderer, this shows up in the terminal selector. - * @deprecated Use [ExtensionTerminalOptions](#ExtensionTerminalOptions) instead. - */ - export function createTerminalRenderer(name: string): TerminalRenderer; - } - //#endregion //#region Extension terminals export namespace window { /** - * Creates a [Terminal](#Terminal) where an extension controls the teerminal. + * Creates a [Terminal](#Terminal) where an extension controls the terminal. * * @param options An [ExtensionTerminalOptions](#ExtensionTerminalOptions) object describing * the characteristics of the new terminal. @@ -983,19 +854,19 @@ declare module 'vscode' { /** * An event that when fired allows overriding the [dimensions](#Terminal.dimensions) of the - * terminal. Note that when set the overridden dimensions will only take effect when they + * terminal. Note that when set, the overridden dimensions will only take effect when they * are lower than the actual dimensions of the terminal (ie. there will never be a scroll * bar). Set to `undefined` for the terminal to go back to the regular dimensions (fit to * the size of the panel). * * **Example:** Override the dimensions of a terminal to 20 columns and 10 rows * ```typescript - * const dimensionsEmitter = new vscode.EventEmitter(); + * const dimensionsEmitter = new vscode.EventEmitter(); * const pty: vscode.Pseudoterminal = { * onDidWrite: writeEmitter.event, * onDidOverrideDimensions: dimensionsEmitter.event, * open: () => { - * dimensionsEmitter.fire({ + * dimensionsEmitter.fire({ * columns: 20, * rows: 10 * }); @@ -1013,17 +884,17 @@ declare module 'vscode' { * **Example:** Exit the terminal when "y" is pressed, otherwise show a notification. * ```typescript * const writeEmitter = new vscode.EventEmitter(); - * const closeEmitter = new vscode.EventEmitter(); + * const closeEmitter = new vscode.EventEmitter(); * const pty: vscode.Pseudoterminal = { * onDidWrite: writeEmitter.event, * onDidClose: closeEmitter.event, * open: () => writeEmitter.fire('Press y to exit successfully'), * close: () => {} - * handleInput: { + * handleInput: data => { * if (data !== 'y') { * vscode.window.showInformationMessage('Something went wrong'); * } - * data => closeEmitter.fire(); + * closeEmitter.fire(); * } * }; * vscode.window.createTerminal({ name: 'Exit example', pty }); @@ -1071,6 +942,11 @@ declare module 'vscode' { * state of a terminal's dimensions should be treated as `undefined` until this is triggered * as the size of a terminal isn't know until it shows up in the user interface. * + * When dimensions are overridden by + * [onDidOverrideDimensions](#Pseudoterminal.onDidOverrideDimensions), `setDimensions` will + * continue to be called with the regular panel dimensions, allowing the extension continue + * to react dimension changes. + * * @param dimensions The new dimensions. */ setDimensions?(dimensions: TerminalDimensions): void; @@ -1120,7 +996,7 @@ declare module 'vscode' { /** * An optional human-readable message that will be rendered in the view. */ - message?: string | MarkdownString; + message?: string; } @@ -1154,33 +1030,24 @@ declare module 'vscode' { */ constructor(label: TreeItemLabel, collapsibleState?: TreeItemCollapsibleState); } + + export interface TreeViewOptions2 extends TreeViewOptions { + /** + * Whether the tree supports multi-select. When the tree supports multi-select and a command is executed from the tree, + * the first argument to the command is the tree item that the command was executed on and the second argument is an + * array containing the other selected tree items. + */ + canSelectMany?: boolean; + } //#endregion //#region CustomExecution - /** - * Class used to execute an extension callback as a task. - */ - export class CustomExecution { - /** - * @param callback The callback that will be called when the extension callback task is executed. - */ - constructor(callback: (terminalRenderer: TerminalRenderer, cancellationToken: CancellationToken, thisArg?: any) => Thenable); - - /** - * The callback used to execute the task. - * @param terminalRenderer Used by the task to render output and receive input. - * @param cancellationToken Cancellation used to signal a cancel request to the executing task. - * @returns The callback should return '0' for success and a non-zero value for failure. - */ - callback: (terminalRenderer: TerminalRenderer, cancellationToken: CancellationToken, thisArg?: any) => Thenable; - } - /** * Class used to execute an extension callback as a task. */ export class CustomExecution2 { /** - * @param process The [Pseudotrminal](#Pseudoterminal) to be used by the task to display output. + * @param process The [Pseudoterminal](#Pseudoterminal) to be used by the task to display output. * @param callback The callback that will be called when the task is started by a user. */ constructor(callback: (thisArg?: any) => Thenable); @@ -1209,12 +1076,12 @@ declare module 'vscode' { * or '$eslint'. Problem matchers can be contributed by an extension using * the `problemMatchers` extension point. */ - constructor(taskDefinition: TaskDefinition, scope: WorkspaceFolder | TaskScope.Global | TaskScope.Workspace, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2, problemMatchers?: string | string[]); + constructor(taskDefinition: TaskDefinition, scope: WorkspaceFolder | TaskScope.Global | TaskScope.Workspace, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution2, problemMatchers?: string | string[]); /** * The task's execution engine */ - execution2?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2; + execution2?: ProcessExecution | ShellExecution | CustomExecution2; } //#endregion @@ -1275,36 +1142,6 @@ declare module 'vscode' { //#endregion - //#region Webview Resource Roots - - export interface Webview { - /** - * Convert a uri for the local file system to one that can be used inside webviews. - * - * Webviews cannot directly load resoruces from the workspace or local file system using `file:` uris. The - * `toWebviewResource` function takes a local `file:` uri and converts it into a uri that can be used inside of - * a webview to load the same resource: - * - * ```ts - * webview.html = `` - * ``` - */ - toWebviewResource(localResource: Uri): Uri; - - /** - * Content security policy source for webview resources. - * - * This is origin used in a content security policy rule: - * - * ``` - * img-src https: ${webview.cspSource} ...; - * ```` - */ - readonly cspSource: string; - } - - //#endregion - //#region Deprecated support export interface CompletionItem { diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index 48938ad1f5b..053217cda52 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -8,7 +8,7 @@ import * as modes from 'vs/editor/common/modes'; import { MainContext, MainThreadEditorInsetsShape, IExtHostContext, ExtHostEditorInsetsShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from '../common/extHostCustomers'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/common/webview'; +import { IWebviewService, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -21,7 +21,7 @@ class EditorWebviewZone implements IViewZone { readonly afterColumn: number; readonly heightInLines: number; - private _id: number; + private _id?: string; // suppressMouseDown?: boolean | undefined; // heightInPx?: number | undefined; // minWidthInPx?: number | undefined; @@ -46,7 +46,7 @@ class EditorWebviewZone implements IViewZone { } dispose(): void { - this.editor.changeViewZones(accessor => accessor.removeZone(this._id)); + this.editor.changeViewZones(accessor => this._id && accessor.removeZone(this._id)); } } @@ -90,7 +90,6 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { const webview = this._webviewService.createWebview('' + handle, { enableFindWidget: false, - allowSvgs: false, extension: { id: extensionId, location: URI.revive(extensionLocation) } }, { allowScripts: options.enableScripts, diff --git a/src/vs/workbench/api/browser/mainThreadCommands.ts b/src/vs/workbench/api/browser/mainThreadCommands.ts index 25dc1138055..2f4d043f048 100644 --- a/src/vs/workbench/api/browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCommands.ts @@ -15,7 +15,6 @@ export class MainThreadCommands implements MainThreadCommandsShape { private readonly _commandRegistrations = new Map(); private readonly _generateCommandsDocumentationRegistration: IDisposable; private readonly _proxy: ExtHostCommandsShape; - private _onDidExecuteCommandListener?: IDisposable; constructor( extHostContext: IExtHostContext, @@ -78,19 +77,6 @@ export class MainThreadCommands implements MainThreadCommandsShape { return this._commandService.executeCommand(id, ...args); } - $registerCommandListener() { - if (!this._onDidExecuteCommandListener) { - this._onDidExecuteCommandListener = this._commandService.onDidExecuteCommand(command => this._proxy.$handleDidExecuteCommand(command)); - } - } - - $unregisterCommandListener() { - if (this._onDidExecuteCommandListener) { - this._onDidExecuteCommandListener.dispose(); - this._onDidExecuteCommandListener = undefined; - } - } - $getCommands(): Promise { return Promise.resolve([...CommandsRegistry.getCommands().keys()]); } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 2580f438473..bbd29b585a2 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -16,9 +16,10 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'vs/workbench/browser/panel'; import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; -import { CommentsPanel, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsPanel'; +import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape } from '../common/extHost.protocol'; +import { COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; export class MainThreadCommentThread implements modes.CommentThread { @@ -35,13 +36,13 @@ export class MainThreadCommentThread implements modes.CommentThread { private _onDidChangeInput = new Emitter(); get onDidChangeInput(): Event { return this._onDidChangeInput.event; } - private _label: string; + private _label: string | undefined; - get label(): string { + get label(): string | undefined { return this._label; } - set label(label: string) { + set label(label: string | undefined) { this._label = label; this._onDidChangeLabel.fire(this._label); } @@ -56,8 +57,8 @@ export class MainThreadCommentThread implements modes.CommentThread { this._contextValue = context; } - private _onDidChangeLabel = new Emitter(); - readonly onDidChangeLabel: Event = this._onDidChangeLabel.event; + private _onDidChangeLabel = new Emitter(); + readonly onDidChangeLabel: Event = this._onDidChangeLabel.event; private _comments: modes.Comment[] | undefined; @@ -348,7 +349,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments private _activeCommentThread?: MainThreadCommentThread; private readonly _activeCommentThreadDisposables = this._register(new DisposableStore()); - private _openPanelListener: IDisposable | null; + private _openPanelListener: IDisposable | null = null; constructor( diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 85039369466..2f0f5283bfd 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -5,10 +5,10 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI as uri } from 'vs/base/common/uri'; -import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, - IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto + IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import severity from 'vs/base/common/severity'; @@ -110,15 +110,16 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb // send all breakpoints const bps = this.debugService.getModel().getBreakpoints(); const fbps = this.debugService.getModel().getFunctionBreakpoints(); + const dbps = this.debugService.getModel().getDataBreakpoints(); if (bps.length > 0 || fbps.length > 0) { this._proxy.$acceptBreakpointsDelta({ - added: this.convertToDto(bps).concat(this.convertToDto(fbps)) + added: this.convertToDto(bps).concat(this.convertToDto(fbps)).concat(this.convertToDto(dbps)) }); } } } - public $registerBreakpoints(DTOs: Array): Promise { + public $registerBreakpoints(DTOs: Array): Promise { for (let dto of DTOs) { if (dto.type === 'sourceMulti') { @@ -136,14 +137,17 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb this.debugService.addBreakpoints(uri.revive(dto.uri), rawbps, 'extension'); } else if (dto.type === 'function') { this.debugService.addFunctionBreakpoint(dto.functionName, dto.id); + } else if (dto.type === 'data') { + this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist); } } return Promise.resolve(); } - public $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): Promise { + public $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[], dataBreakpointIds: string[]): Promise { breakpointIds.forEach(id => this.debugService.removeBreakpoints(id)); functionBreakpointIds.forEach(id => this.debugService.removeFunctionBreakpoints(id)); + dataBreakpointIds.forEach(id => this.debugService.removeDataBreakpoints(id)); return Promise.resolve(); } @@ -153,13 +157,13 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb type: debugType }; if (hasProvide) { - provider.provideDebugConfigurations = (folder) => { - return this._proxy.$provideDebugConfigurations(handle, folder); + provider.provideDebugConfigurations = (folder, token) => { + return this._proxy.$provideDebugConfigurations(handle, folder, token); }; } if (hasResolve) { - provider.resolveDebugConfiguration = (folder, config) => { - return this._proxy.$resolveDebugConfiguration(handle, folder, config); + provider.resolveDebugConfiguration = (folder, config, token) => { + return this._proxy.$resolveDebugConfiguration(handle, folder, config, token); }; } if (hasProvideDebugAdapter) { @@ -294,7 +298,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return undefined; } - private convertToDto(bps: (ReadonlyArray)): Array { + private convertToDto(bps: (ReadonlyArray)): Array { return bps.map(bp => { if ('name' in bp) { const fbp = bp; @@ -307,6 +311,19 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb logMessage: fbp.logMessage, functionName: fbp.name }; + } else if ('dataId' in bp) { + const dbp = bp; + return { + type: 'data', + id: dbp.getId(), + dataId: dbp.dataId, + enabled: dbp.enabled, + condition: dbp.condition, + hitCondition: dbp.hitCondition, + logMessage: dbp.logMessage, + label: dbp.label, + canPersist: dbp.canPersist + }; } else { const sbp = bp; return { diff --git a/src/vs/workbench/api/browser/mainThreadDiagnostics.ts b/src/vs/workbench/api/browser/mainThreadDiagnostics.ts index 801a17d89a7..eb26b62da3a 100644 --- a/src/vs/workbench/api/browser/mainThreadDiagnostics.ts +++ b/src/vs/workbench/api/browser/mainThreadDiagnostics.ts @@ -5,24 +5,43 @@ import { IMarkerService, IMarkerData } from 'vs/platform/markers/common/markers'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { MainThreadDiagnosticsShape, MainContext, IExtHostContext } from '../common/extHost.protocol'; +import { MainThreadDiagnosticsShape, MainContext, IExtHostContext, ExtHostDiagnosticsShape, ExtHostContext } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { IDisposable } from 'vs/base/common/lifecycle'; @extHostNamedCustomer(MainContext.MainThreadDiagnostics) export class MainThreadDiagnostics implements MainThreadDiagnosticsShape { private readonly _activeOwners = new Set(); + + private readonly _proxy: ExtHostDiagnosticsShape; private readonly _markerService: IMarkerService; + private readonly _markerListener: IDisposable; constructor( extHostContext: IExtHostContext, @IMarkerService markerService: IMarkerService ) { + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDiagnostics); this._markerService = markerService; + this._markerListener = this._markerService.onMarkerChanged(this._forwardMarkers, this); } dispose(): void { + this._markerListener.dispose(); this._activeOwners.forEach(owner => this._markerService.changeAll(owner, [])); + this._activeOwners.clear(); + } + + private _forwardMarkers(resources: URI[]): void { + const data: [UriComponents, IMarkerData[]][] = []; + for (const resource of resources) { + data.push([ + resource, + this._markerService.read({ resource }).filter(marker => !this._activeOwners.has(marker.owner)) + ]); + } + this._proxy.$acceptMarkersChange(data); } $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index 804349ee484..10037f6c21a 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -109,7 +109,7 @@ class DocumentAndEditorStateDelta { class DocumentAndEditorState { - static compute(before: DocumentAndEditorState, after: DocumentAndEditorState): DocumentAndEditorStateDelta { + static compute(before: DocumentAndEditorState | undefined, after: DocumentAndEditorState): DocumentAndEditorStateDelta { if (!before) { return new DocumentAndEditorStateDelta( [], values(after.documents), @@ -146,7 +146,7 @@ class MainThreadDocumentAndEditorStateComputer { private readonly _toDispose = new DisposableStore(); private _toDisposeOnEditorRemove = new Map(); - private _currentState: DocumentAndEditorState; + private _currentState?: DocumentAndEditorState; private _activeEditorOrder: ActiveEditorOrder = ActiveEditorOrder.Editor; constructor( diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 669ff5063fa..ff8e1cd5138 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -178,7 +178,7 @@ export class MainThreadTextEditor { private readonly _focusTracker: IFocusTracker; private readonly _codeEditorListeners = new DisposableStore(); - private _properties: MainThreadTextEditorProperties; + private _properties: MainThreadTextEditorProperties | null; private readonly _onPropertiesChanged: Emitter; constructor( @@ -191,6 +191,7 @@ export class MainThreadTextEditor { this._id = id; this._model = model; this._codeEditor = null; + this._properties = null; this._focusTracker = focusTracker; this._modelService = modelService; @@ -289,7 +290,7 @@ export class MainThreadTextEditor { } public getProperties(): MainThreadTextEditorProperties { - return this._properties; + return this._properties!; } public get onPropertiesChanged(): Event { @@ -304,7 +305,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/browser/mainThreadEditors.ts b/src/vs/workbench/api/browser/mainThreadEditors.ts index 8601055aeeb..76334b577b1 100644 --- a/src/vs/workbench/api/browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadEditors.ts @@ -20,7 +20,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/browser/mainThreadDocumentsAndEditors'; import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor'; -import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContext, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType, WorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContext, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType, IWorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { EditorViewColumn, editorGroupToViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -212,7 +212,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { return Promise.resolve(editor.applyEdits(modelVersionId, edits, opts)); } - $tryApplyWorkspaceEdit(dto: WorkspaceEditDto): Promise { + $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise { const { edits } = reviveWorkspaceEditDto(dto); return this._bulkEditService.apply({ edits }, undefined).then(() => true, err => false); } diff --git a/src/vs/workbench/api/browser/mainThreadKeytar.ts b/src/vs/workbench/api/browser/mainThreadKeytar.ts index fff0a902e2c..f07b7688801 100644 --- a/src/vs/workbench/api/browser/mainThreadKeytar.ts +++ b/src/vs/workbench/api/browser/mainThreadKeytar.ts @@ -5,49 +5,33 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadKeytarShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; -import { optional } from 'vs/platform/instantiation/common/instantiation'; +import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; @extHostNamedCustomer(MainContext.MainThreadKeytar) export class MainThreadKeytar implements MainThreadKeytarShape { - private readonly _credentialsService?: ICredentialsService; - constructor( _extHostContext: IExtHostContext, - @optional(ICredentialsService) credentialsService: ICredentialsService, - ) { - this._credentialsService = credentialsService; + @ICredentialsService private readonly _credentialsService: ICredentialsService, + ) { } + + async $getPassword(service: string, account: string): Promise { + return this._credentialsService.getPassword(service, account); + } + + async $setPassword(service: string, account: string, password: string): Promise { + return this._credentialsService.setPassword(service, account, password); + } + + async $deletePassword(service: string, account: string): Promise { + return this._credentialsService.deletePassword(service, account); + } + + async $findPassword(service: string): Promise { + return this._credentialsService.findPassword(service); } dispose(): void { // } - - async $getPassword(service: string, account: string): Promise { - if (this._credentialsService) { - return this._credentialsService.getPassword(service, account); - } - return null; - } - - async $setPassword(service: string, account: string, password: string): Promise { - if (this._credentialsService) { - return this._credentialsService.setPassword(service, account, password); - } - } - - async $deletePassword(service: string, account: string): Promise { - if (this._credentialsService) { - return this._credentialsService.deletePassword(service, account); - } - return false; - } - - async $findPassword(service: string): Promise { - if (this._credentialsService) { - return this._credentialsService.findPassword(service); - } - return null; - } } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 3541ba2eef0..2093d2e34ce 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/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, IRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CallHierarchyDto, SuggestDataDto, CodeActionDto } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyDto, ISuggestDataDto, ICodeActionDto } from '../common/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -54,9 +54,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha //#region --- revive functions - private static _reviveLocationDto(data: LocationDto): modes.Location; - private static _reviveLocationDto(data: LocationDto[]): modes.Location[]; - private static _reviveLocationDto(data: LocationDto | LocationDto[]): modes.Location | modes.Location[] { + private static _reviveLocationDto(data: ILocationDto): modes.Location; + private static _reviveLocationDto(data: ILocationDto[]): modes.Location[]; + private static _reviveLocationDto(data: ILocationDto | ILocationDto[]): modes.Location | modes.Location[] { if (!data) { return data; } else if (Array.isArray(data)) { @@ -68,9 +68,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } - private static _reviveLocationLinkDto(data: DefinitionLinkDto): modes.LocationLink; - private static _reviveLocationLinkDto(data: DefinitionLinkDto[]): modes.LocationLink[]; - private static _reviveLocationLinkDto(data: DefinitionLinkDto | DefinitionLinkDto[]): modes.LocationLink | modes.LocationLink[] { + private static _reviveLocationLinkDto(data: IDefinitionLinkDto): modes.LocationLink; + private static _reviveLocationLinkDto(data: IDefinitionLinkDto[]): modes.LocationLink[]; + private static _reviveLocationLinkDto(data: IDefinitionLinkDto | IDefinitionLinkDto[]): modes.LocationLink | modes.LocationLink[] { if (!data) { return data; } else if (Array.isArray(data)) { @@ -82,10 +82,10 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } - private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto): search.IWorkspaceSymbol; - private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto[]): search.IWorkspaceSymbol[]; + private static _reviveWorkspaceSymbolDto(data: IWorkspaceSymbolDto): search.IWorkspaceSymbol; + private static _reviveWorkspaceSymbolDto(data: IWorkspaceSymbolDto[]): search.IWorkspaceSymbol[]; private static _reviveWorkspaceSymbolDto(data: undefined): undefined; - private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto | WorkspaceSymbolDto[] | undefined): search.IWorkspaceSymbol | search.IWorkspaceSymbol[] | undefined { + private static _reviveWorkspaceSymbolDto(data: IWorkspaceSymbolDto | IWorkspaceSymbolDto[] | undefined): search.IWorkspaceSymbol | search.IWorkspaceSymbol[] | undefined { if (!data) { return data; } else if (Array.isArray(data)) { @@ -97,21 +97,21 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } - private static _reviveCodeActionDto(data: ReadonlyArray): modes.CodeAction[] { + private static _reviveCodeActionDto(data: ReadonlyArray): modes.CodeAction[] { if (data) { data.forEach(code => reviveWorkspaceEditDto(code.edit)); } return data; } - private static _reviveLinkDTO(data: LinkDto): modes.ILink { + private static _reviveLinkDTO(data: ILinkDto): modes.ILink { if (data.url && typeof data.url !== 'string') { data.url = URI.revive(data.url); } return data; } - private static _reviveCallHierarchyItemDto(data: CallHierarchyDto | undefined): callh.CallHierarchyItem { + private static _reviveCallHierarchyItemDto(data: ICallHierarchyDto | undefined): callh.CallHierarchyItem { if (data) { data.uri = URI.revive(data.uri); } @@ -122,7 +122,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- outline - $registerDocumentSymbolProvider(handle: number, selector: ISerializedDocumentFilter[], displayName: string): void { + $registerDocumentSymbolProvider(handle: number, selector: IDocumentFilterDto[], displayName: string): void { this._registrations.set(handle, modes.DocumentSymbolProviderRegistry.register(selector, { displayName, provideDocumentSymbols: (model: ITextModel, token: CancellationToken): Promise => { @@ -133,7 +133,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- code lens - $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void { + $registerCodeLensSupport(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void { const provider = { provideCodeLenses: (model: ITextModel, token: CancellationToken): Promise => { @@ -170,7 +170,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- declaration - $registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.DefinitionProviderRegistry.register(selector, { provideDefinition: (model, position, token): Promise => { return this._proxy.$provideDefinition(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); @@ -178,7 +178,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - $registerDeclarationSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerDeclarationSupport(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.DeclarationProviderRegistry.register(selector, { provideDeclaration: (model, position, token) => { return this._proxy.$provideDeclaration(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); @@ -186,7 +186,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - $registerImplementationSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerImplementationSupport(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.ImplementationProviderRegistry.register(selector, { provideImplementation: (model, position, token): Promise => { return this._proxy.$provideImplementation(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); @@ -194,7 +194,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - $registerTypeDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.TypeDefinitionProviderRegistry.register(selector, { provideTypeDefinition: (model, position, token): Promise => { return this._proxy.$provideTypeDefinition(handle, model.uri, position, token).then(MainThreadLanguageFeatures._reviveLocationLinkDto); @@ -204,7 +204,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- extra info - $registerHoverProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.HoverProviderRegistry.register(selector, { provideHover: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { return this._proxy.$provideHover(handle, model.uri, position, token); @@ -214,7 +214,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- occurrences - $registerDocumentHighlightProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.DocumentHighlightProviderRegistry.register(selector, { provideDocumentHighlights: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { return this._proxy.$provideDocumentHighlights(handle, model.uri, position, token); @@ -224,7 +224,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- references - $registerReferenceSupport(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.ReferenceProviderRegistry.register(selector, { provideReferences: (model: ITextModel, position: EditorPosition, context: modes.ReferenceContext, token: CancellationToken): Promise => { return this._proxy.$provideReferences(handle, model.uri, position, context, token).then(MainThreadLanguageFeatures._reviveLocationDto); @@ -234,7 +234,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- quick fix - $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], providedCodeActionKinds?: string[]): void { + $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], providedCodeActionKinds?: string[]): void { this._registrations.set(handle, modes.CodeActionProviderRegistry.register(selector, { provideCodeActions: async (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise => { const listDto = await this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token); @@ -256,7 +256,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- formatting - $registerDocumentFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier, displayName: string): void { + $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void { this._registrations.set(handle, modes.DocumentFormattingEditProviderRegistry.register(selector, { extensionId, displayName, @@ -266,7 +266,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier, displayName: string): void { + $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void { this._registrations.set(handle, modes.DocumentRangeFormattingEditProviderRegistry.register(selector, { extensionId, displayName, @@ -276,7 +276,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } - $registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void { + $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void { this._registrations.set(handle, modes.OnTypeFormattingEditProviderRegistry.register(selector, { extensionId, autoFormatTriggerCharacters, @@ -313,7 +313,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- rename - $registerRenameSupport(handle: number, selector: ISerializedDocumentFilter[], supportResolveLocation: boolean): void { + $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportResolveLocation: boolean): void { this._registrations.set(handle, modes.RenameProviderRegistry.register(selector, { provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken): Promise => { return this._proxy.$provideRenameEdits(handle, model.uri, position, newName, token).then(reviveWorkspaceEditDto); @@ -326,7 +326,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- suggest - private static _inflateSuggestDto(defaultRange: IRange, data: SuggestDataDto): modes.CompletionItem { + private static _inflateSuggestDto(defaultRange: IRange, data: ISuggestDataDto): modes.CompletionItem { return { label: data.a, kind: data.b, @@ -347,7 +347,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; } - $registerSuggestSupport(handle: number, selector: ISerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void { + $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void { const provider: modes.CompletionItemProvider = { triggerCharacters, _debugDisplayName: extensionId.value, @@ -380,7 +380,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- parameter hints - $registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], metadata: ISerializedSignatureHelpProviderMetadata): void { + $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void { this._registrations.set(handle, modes.SignatureHelpProviderRegistry.register(selector, { signatureHelpTriggerCharacters: metadata.triggerCharacters, @@ -403,7 +403,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- links - $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[], supportsResolve: boolean): void { + $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void { const provider: modes.LinkProvider = { provideLinks: (model, token) => { return this._proxy.$provideDocumentLinks(handle, model.uri, token).then(dto => { @@ -423,7 +423,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; if (supportsResolve) { provider.resolveLink = (link, token) => { - const dto: LinkDto = link; + const dto: ILinkDto = link; if (!dto.cacheId) { return link; } @@ -437,7 +437,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- colors - $registerDocumentColorProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerDocumentColorProvider(handle: number, selector: IDocumentFilterDto[]): void { const proxy = this._proxy; this._registrations.set(handle, modes.ColorProviderRegistry.register(selector, { provideDocumentColors: (model, token) => { @@ -471,7 +471,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- folding - $registerFoldingRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void { const proxy = this._proxy; this._registrations.set(handle, modes.FoldingRangeProviderRegistry.register(selector, { provideFoldingRanges: (model, context, token) => { @@ -482,7 +482,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // -- smart select - $registerSelectionRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, modes.SelectionRangeRegistry.register(selector, { provideSelectionRanges: (model, positions, token) => { return this._proxy.$provideSelectionRanges(handle, model.uri, positions, token); @@ -492,7 +492,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- call hierarchy - $registerCallHierarchyProvider(handle: number, selector: ISerializedDocumentFilter[]): void { + $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void { this._registrations.set(handle, callh.CallHierarchyProviderRegistry.register(selector, { provideCallHierarchyItem: (document, position, token) => { return this._proxy.$provideCallHierarchyItem(handle, document.uri, position, token).then(MainThreadLanguageFeatures._reviveCallHierarchyItemDto); @@ -516,11 +516,11 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- configuration - private static _reviveRegExp(regExp: ISerializedRegExp): RegExp { + private static _reviveRegExp(regExp: IRegExpDto): RegExp { return new RegExp(regExp.pattern, regExp.flags); } - private static _reviveIndentationRule(indentationRule: ISerializedIndentationRule): IndentationRule { + private static _reviveIndentationRule(indentationRule: IIndentationRuleDto): IndentationRule { return { decreaseIndentPattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.decreaseIndentPattern), increaseIndentPattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.increaseIndentPattern), @@ -529,7 +529,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; } - private static _reviveOnEnterRule(onEnterRule: ISerializedOnEnterRule): OnEnterRule { + private static _reviveOnEnterRule(onEnterRule: IOnEnterRuleDto): OnEnterRule { return { beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText), afterText: onEnterRule.afterText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText) : undefined, @@ -538,11 +538,11 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; } - private static _reviveOnEnterRules(onEnterRules: ISerializedOnEnterRule[]): OnEnterRule[] { + private static _reviveOnEnterRules(onEnterRules: IOnEnterRuleDto[]): OnEnterRule[] { return onEnterRules.map(MainThreadLanguageFeatures._reviveOnEnterRule); } - $setLanguageConfiguration(handle: number, languageId: string, _configuration: ISerializedLanguageConfiguration): void { + $setLanguageConfiguration(handle: number, languageId: string, _configuration: ILanguageConfigurationDto): void { const configuration: LanguageConfiguration = { comments: _configuration.comments, diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index dbc3da9fafd..7934efb088b 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -29,7 +29,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, MainThreadTaskShape, ExtHostTaskShape, MainContext, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { TaskDefinitionDTO, TaskExecutionDTO, ProcessExecutionOptionsDTO, TaskPresentationOptionsDTO, - ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, CustomExecutionDTO, CustomExecution2DTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, + ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, CustomExecution2DTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, RunOptionsDTO } from 'vs/workbench/api/common/shared/tasks'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; @@ -131,7 +131,7 @@ namespace ProcessExecutionOptionsDTO { } namespace ProcessExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is ProcessExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO): value is ProcessExecutionDTO { const candidate = value as ProcessExecutionDTO; return candidate && !!candidate.process; } @@ -199,7 +199,7 @@ namespace ShellExecutionOptionsDTO { } namespace ShellExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is ShellExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO): value is ShellExecutionDTO { const candidate = value as ShellExecutionDTO; return candidate && (!!candidate.commandLine || !!candidate.command); } @@ -230,28 +230,8 @@ namespace ShellExecutionDTO { } } -namespace CustomExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is CustomExecutionDTO { - const candidate = value as CustomExecutionDTO; - return candidate && candidate.customExecution === 'customExecution'; - } - - export function from(value: CommandConfiguration): CustomExecutionDTO { - return { - customExecution: 'customExecution' - }; - } - - export function to(value: CustomExecutionDTO): CommandConfiguration { - return { - runtime: RuntimeType.CustomExecution, - presentation: undefined - }; - } -} - namespace CustomExecution2DTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is CustomExecution2DTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO): value is CustomExecution2DTO { const candidate = value as CustomExecution2DTO; return candidate && candidate.customExecution === 'customExecution2'; } @@ -371,8 +351,6 @@ namespace TaskDTO { command = ShellExecutionDTO.to(task.execution); } else if (ProcessExecutionDTO.is(task.execution)) { command = ProcessExecutionDTO.to(task.execution); - } else if (CustomExecutionDTO.is(task.execution)) { - command = CustomExecutionDTO.to(task.execution); } else if (CustomExecution2DTO.is(task.execution)) { command = CustomExecution2DTO.to(task.execution); } @@ -420,7 +398,7 @@ namespace TaskFilterDTO { @extHostNamedCustomer(MainContext.MainThreadTask) export class MainThreadTask implements MainThreadTaskShape { - private readonly _extHostContext: IExtHostContext; + private readonly _extHostContext: IExtHostContext | undefined; private readonly _proxy: ExtHostTaskShape; private readonly _providers: Map; diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index f23b625d140..6c6acac7a80 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; -import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, IShellLaunchConfigDto, TerminalLaunchConfig, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { StopWatch } from 'vs/base/common/stopwatch'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -21,13 +22,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape private readonly _terminalProcesses = new Map>(); private readonly _terminalProcessesReady = new Map void>(); private readonly _terminalOnDidWriteDataListeners = new Map(); - private readonly _terminalOnDidAcceptInputListeners = new Map(); + private _dataEventTracker: TerminalDataEventTracker | undefined; constructor( extHostContext: IExtHostContext, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalInstanceService readonly terminalInstanceService: ITerminalInstanceService, - @IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService + @IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); this._remoteAuthority = extHostContext.remoteAuthority; @@ -100,11 +102,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape }); } - public $createTerminalRenderer(name: string): Promise { - const instance = this._terminalService.createTerminalRenderer(name); - return Promise.resolve(instance.id); - } - public $show(terminalId: number, preserveFocus: boolean): void { const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance) { @@ -127,44 +124,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } - public $terminalRendererWrite(terminalId: number, text: string): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); - if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { - terminalInstance.write(text); - } - } - - public $terminalRendererSetName(terminalId: number, name: string): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); - if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { - terminalInstance.setTitle(name, false); - } - } - - public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); - if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) { - terminalInstance.setDimensions(dimensions); - } - } - - public $terminalRendererRegisterOnInputListener(terminalId: number): void { - const terminalInstance = this._terminalService.getInstanceFromId(terminalId); - if (!terminalInstance) { - return; - } - - // Listener already registered - if (this._terminalOnDidAcceptInputListeners.has(terminalId)) { - return; - } - - // Register - const listener = terminalInstance.onRendererInput(data => this._onTerminalRendererInput(terminalId, data)); - this._terminalOnDidAcceptInputListeners.set(terminalId, listener); - terminalInstance.addDisposable(listener); - } - public $sendText(terminalId: number, text: string, addNewLine: boolean): void { const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (terminalInstance) { @@ -172,6 +131,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } + /** @deprecated */ public $registerOnDataListener(terminalId: number): void { const terminalInstance = this._terminalService.getInstanceFromId(terminalId); if (!terminalInstance) { @@ -191,14 +151,34 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape terminalInstance.addDisposable(listener); } + public $startSendingDataEvents(): void { + if (!this._dataEventTracker) { + this._dataEventTracker = this._instantiationService.createInstance(TerminalDataEventTracker, (id, data) => { + this._onTerminalData2(id, data); + }); + } + } + + public $stopSendingDataEvents(): void { + if (this._dataEventTracker) { + this._dataEventTracker.dispose(); + this._dataEventTracker = undefined; + } + } + private _onActiveTerminalChanged(terminalId: number | null): void { this._proxy.$acceptActiveTerminalChanged(terminalId); } + /** @deprecated */ private _onTerminalData(terminalId: number, data: string): void { this._proxy.$acceptTerminalProcessData(terminalId, data); } + private _onTerminalData2(terminalId: number, data: string): void { + this._proxy.$acceptTerminalProcessData2(terminalId, data); + } + private _onTitleChanged(terminalId: number, name: string): void { this._proxy.$acceptTerminalTitleChange(terminalId, name); } @@ -207,10 +187,6 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._proxy.$acceptWorkspacePermissionsChanged(isAllowed); } - private _onTerminalRendererInput(terminalId: number, data: string): void { - this._proxy.$acceptTerminalRendererInput(terminalId, data); - } - private _onTerminalDisposed(terminalInstance: ITerminalInstance): void { this._proxy.$acceptTerminalClosed(terminalInstance.id); } @@ -254,7 +230,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } else { this._terminalProcesses.set(proxy.terminalId, Promise.resolve(proxy)); } - const shellLaunchConfigDto: ShellLaunchConfigDto = { + const shellLaunchConfigDto: IShellLaunchConfigDto = { name: request.shellLaunchConfig.name, executable: request.shellLaunchConfig.executable, args: request.shellLaunchConfig.args, @@ -359,7 +335,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape private _onRequestDefaultShellAndArgs(request: IDefaultShellAndArgsRequest): void { if (this._isPrimaryExtHost()) { - this._proxy.$requestDefaultShellAndArgs().then(e => request(e.shell, e.args)); + this._proxy.$requestDefaultShellAndArgs(request.useAutomationShell).then(e => request.callback(e.shell, e.args)); } } @@ -371,3 +347,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape return terminal; } } + +/** + * Encapsulates temporary tracking of data events from terminal instances, once disposed all + * listeners are removed. + */ +class TerminalDataEventTracker extends Disposable { + constructor( + private readonly _callback: (id: number, data: string) => void, + @ITerminalService private readonly _terminalService: ITerminalService + ) { + super(); + this._terminalService.terminalInstances.forEach(instance => this._registerInstance(instance)); + this._register(this._terminalService.onInstanceCreated(instance => this._registerInstance(instance))); + } + + private _registerInstance(instance: ITerminalInstance): void { + this._register(instance.onData(e => this._callback(instance.id, e))); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index f7024269742..3c2e1c70801 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -10,8 +10,8 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { distinct } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { isUndefinedOrNull, isNumber } from 'vs/base/common/types'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @extHostNamedCustomer(MainContext.MainThreadTreeViews) export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape { @@ -22,24 +22,28 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie constructor( extHostContext: IExtHostContext, @IViewsService private readonly viewsService: IViewsService, - @INotificationService private readonly notificationService: INotificationService + @INotificationService private readonly notificationService: INotificationService, + @IExtensionService private readonly extensionService: IExtensionService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews); } - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void { - const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService); - this._dataProviders.set(treeViewId, dataProvider); - const viewer = this.getTreeView(treeViewId); - if (viewer) { - viewer.dataProvider = dataProvider; - viewer.showCollapseAllAction = !!options.showCollapseAll; - this.registerListeners(treeViewId, viewer); - this._proxy.$setVisible(treeViewId, viewer.visible); - } else { - this.notificationService.error('No view is registered with id: ' + treeViewId); - } + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void { + this.extensionService.whenInstalledExtensionsRegistered().then(() => { + const dataProvider = new TreeViewDataProvider(treeViewId, this._proxy, this.notificationService); + this._dataProviders.set(treeViewId, dataProvider); + const viewer = this.getTreeView(treeViewId); + if (viewer) { + viewer.dataProvider = dataProvider; + viewer.showCollapseAllAction = !!options.showCollapseAll; + viewer.canSelectMany = !!options.canSelectMany; + this.registerListeners(treeViewId, viewer); + this._proxy.$setVisible(treeViewId, viewer.visible); + } else { + this.notificationService.error('No view is registered with id: ' + treeViewId); + } + }); } $reveal(treeViewId: string, item: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise { @@ -63,7 +67,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie return Promise.resolve(); } - $setMessage(treeViewId: string, message: string | IMarkdownString): void { + $setMessage(treeViewId: string, message: string): void { const viewer = this.getTreeView(treeViewId); if (viewer) { viewer.message = message; @@ -125,7 +129,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this._dataProviders.forEach((dataProvider, treeViewId) => { const treeView = this.getTreeView(treeViewId); if (treeView) { - treeView.dataProvider = null; + treeView.dataProvider = undefined; } }); this._dataProviders.clear(); diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 90bdeb5ea74..4c5b97e060d 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -5,30 +5,62 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import * as map from 'vs/base/common/map'; +import { startsWith } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProductService } from 'vs/platform/product/common/product'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions, WebviewPanelViewStateData } from 'vs/workbench/api/common/extHost.protocol'; import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; +import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { extHostNamedCustomer } from '../common/extHostCustomers'; -import { IProductService } from 'vs/platform/product/common/product'; -import { startsWith } from 'vs/base/common/strings'; -import { Webview } from 'vs/workbench/contrib/webview/common/webview'; interface OldMainThreadWebviewState { readonly viewType: string; state: any; } +/** + * Bi-directional map between webview handles and inputs. + */ +class WebviewHandleStore { + private readonly _handlesToInputs = new Map(); + private readonly _inputsToHandles = new Map(); + + public add(handle: string, input: WebviewEditorInput): void { + this._handlesToInputs.set(handle, input); + this._inputsToHandles.set(input, handle); + } + + public getHandleForInput(input: WebviewEditorInput): string | undefined { + return this._inputsToHandles.get(input); + } + + public getInputForHandle(handle: string): WebviewEditorInput | undefined { + return this._handlesToInputs.get(handle); + } + + public delete(handle: string): void { + const input = this.getInputForHandle(handle); + this._handlesToInputs.delete(handle); + if (input) { + this._inputsToHandles.delete(input); + } + } + + public get size(): number { + return this._handlesToInputs.size; + } +} + @extHostNamedCustomer(MainContext.MainThreadWebviews) export class MainThreadWebviews extends Disposable implements MainThreadWebviewsShape { @@ -43,12 +75,9 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews private static revivalPool = 0; private readonly _proxy: ExtHostWebviewsShape; - private readonly _webviewEditorInputs = new Map(); - private readonly _webviews = new Map(); + private readonly _webviewEditorInputs = new WebviewHandleStore(); private readonly _revivers = new Map(); - private _activeWebview: WebviewPanelHandle | undefined = undefined; - constructor( context: IExtHostContext, @IExtensionService extensionService: IExtensionService, @@ -62,8 +91,8 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews super(); this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews); - this._register(_editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this)); - this._register(_editorService.onDidVisibleEditorsChange(this.onVisibleEditorsChanged, this)); + this._register(_editorService.onDidActiveEditorChange(this.updateWebviewViewStates, this)); + this._register(_editorService.onDidVisibleEditorsChange(this.updateWebviewViewStates, this)); // This reviver's only job is to activate webview extensions // This should trigger the real reviver to be registered from the extension host side. @@ -104,8 +133,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews }); this.hookupWebviewEventDelegate(handle, webview); - this._webviewEditorInputs.set(handle, webview); - this._webviews.set(handle, webview.webview); + this._webviewEditorInputs.add(handle, webview); /* __GDPR__ "webviews:createWebviewPanel" : { @@ -175,8 +203,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } const handle = `revival-${MainThreadWebviews.revivalPool++}`; - this._webviewEditorInputs.set(handle, webviewEditorInput); - this._webviews.set(handle, webviewEditorInput.webview); + this._webviewEditorInputs.add(handle, webviewEditorInput); this.hookupWebviewEventDelegate(handle, webviewEditorInput); let state = undefined; @@ -234,7 +261,6 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews input.onDispose(() => { this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviewEditorInputs.delete(handle); - this._webviews.delete(handle); }); }); input.webview.onDidUpdateState((newState: any) => { @@ -246,70 +272,35 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews }); } - private onActiveEditorChanged() { - const activeEditor = this._editorService.activeControl; - let newActiveWebview: { input: WebviewEditorInput, handle: WebviewPanelHandle } | undefined = undefined; - if (activeEditor && activeEditor.input instanceof WebviewEditorInput) { - for (const handle of map.keys(this._webviewEditorInputs)) { - const input = this._webviewEditorInputs.get(handle)!; - if (input.matches(activeEditor.input)) { - newActiveWebview = { input, handle }; - break; - } - } - } - - if (newActiveWebview && newActiveWebview.handle === this._activeWebview) { - // Webview itself unchanged but position may have changed - this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, { - active: true, - visible: true, - position: editorGroupToViewColumn(this._editorGroupService, newActiveWebview.input.group || 0) - }); + private updateWebviewViewStates() { + if (!this._webviewEditorInputs.size) { return; } - // Broadcast view state update for currently active - if (typeof this._activeWebview !== 'undefined') { - const oldActiveWebview = this._webviewEditorInputs.get(this._activeWebview); - if (oldActiveWebview) { - this._proxy.$onDidChangeWebviewPanelViewState(this._activeWebview, { - active: false, - visible: this._editorService.visibleControls.some(editor => !!editor.input && editor.input.matches(oldActiveWebview)), - position: editorGroupToViewColumn(this._editorGroupService, oldActiveWebview.group || 0), - }); - } - } + const activeInput = this._editorService.activeControl && this._editorService.activeControl.input; + const viewStates: WebviewPanelViewStateData = {}; + for (const group of this._editorGroupService.groups) { + for (const input of group.editors) { + if (!(input instanceof WebviewEditorInput)) { + continue; + } - // Then for newly active - if (newActiveWebview) { - this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, { - active: true, - visible: true, - position: editorGroupToViewColumn(this._editorGroupService, activeEditor ? activeEditor.group : ACTIVE_GROUP), - }); - this._activeWebview = newActiveWebview.handle; - } else { - this._activeWebview = undefined; - } - } + input.updateGroup(group.id); - private onVisibleEditorsChanged(): void { - this._webviewEditorInputs.forEach((input, handle) => { - for (const workbenchEditor of this._editorService.visibleControls) { - if (workbenchEditor.input && workbenchEditor.input.matches(input)) { - const editorPosition = editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group!); - - input.updateGroup(workbenchEditor.group!.id); - this._proxy.$onDidChangeWebviewPanelViewState(handle, { - active: handle === this._activeWebview, - visible: true, - position: editorPosition - }); - break; + const handle = this._webviewEditorInputs.getHandleForInput(input); + if (handle) { + viewStates[handle] = { + visible: input === group.activeEditor, + active: input === activeInput, + position: editorGroupToViewColumn(this._editorGroupService, group.id || 0), + }; } } - }); + } + + if (Object.keys(viewStates).length) { + this._proxy.$onDidChangeWebviewPanelViewStates(viewStates); + } } private onDidClickLink(handle: WebviewPanelHandle, link: URI): void { @@ -342,28 +333,19 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } private tryGetWebviewEditorInput(handle: WebviewPanelHandle): WebviewEditorInput | undefined { - return this._webviewEditorInputs.get(handle); + return this._webviewEditorInputs.getInputForHandle(handle); } private getWebview(handle: WebviewPanelHandle): Webview { - const webview = this.tryGetWebview(handle); - if (!webview) { - throw new Error('Unknown webview handle:' + handle); - } - return webview; - } - - private tryGetWebview(handle: WebviewPanelHandle): Webview | undefined { - return this._webviews.get(handle); + return this.getWebviewEditorInput(handle).webview; } private static getDeserializationFailedContents(viewType: string) { return ` - - + ${localize('errorMessage', "An error occurred while restoring view:{0}", viewType)} `; diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index 410bb4ec66d..df059758f65 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -6,12 +6,13 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, ExtHostWindowShape, IExtHostContext, MainContext, MainThreadWindowShape, IOpenUriOptions } from '../common/extHost.protocol'; import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { extractLocalHostUriMetaDataForPortMapping } from 'vs/workbench/contrib/webview/common/portMapping'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; @extHostNamedCustomer(MainContext.MainThreadWindow) export class MainThreadWindow implements MainThreadWindowShape { @@ -23,7 +24,7 @@ export class MainThreadWindow implements MainThreadWindowShape { constructor( extHostContext: IExtHostContext, @IWindowService private readonly windowService: IWindowService, - @IWindowsService private readonly windowsService: IWindowsService, + @IOpenerService private readonly openerService: IOpenerService, @ITunnelService private readonly tunnelService: ITunnelService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { @@ -58,7 +59,7 @@ export class MainThreadWindow implements MainThreadWindowShape { } } - return this.windowsService.openExternal(encodeURI(uri.toString(true))); + return this.openerService.open(uri, { openExternal: true }); } private getOrCreateTunnel(remotePort: number): Promise | undefined { diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 149f1f4cebe..e6621fc362c 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -19,6 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files'; import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm'; import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; +import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/common/remote.contribution'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; @@ -36,7 +37,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { createCSSRule, asDomUri } from 'vs/base/browser/dom'; +import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -79,6 +80,7 @@ interface IUserFriendlyViewDescriptor { id: string; name: string; when?: string; + group?: string; } const viewDescriptor: IJSONSchema = { @@ -99,6 +101,27 @@ const viewDescriptor: IJSONSchema = { } }; +const nestableViewDescriptor: IJSONSchema = { + type: 'object', + properties: { + id: { + description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'), + type: 'string' + }, + name: { + description: localize('vscode.extension.contributes.view.name', 'The human-readable name of the view. Will be shown'), + type: 'string' + }, + when: { + description: localize('vscode.extension.contributes.view.when', 'Condition which must be true to show this view'), + type: 'string' + }, + group: { + description: localize('vscode.extension.contributes.view.group', 'Nested group in the viewlet'), + type: 'string' + } + } +}; const viewsContribution: IJSONSchema = { description: localize('vscode.extension.contributes.views', "Contributes views to the editor"), type: 'object', @@ -126,6 +149,12 @@ const viewsContribution: IJSONSchema = { type: 'array', items: viewDescriptor, default: [] + }, + 'remote': { + description: localize('views.remote', "Contributes views to Remote container in the Activity bar. To contribute to this container, enableProposedApi needs to be turned on"), + type: 'array', + items: nestableViewDescriptor, + default: [] } }, additionalProperties: { @@ -181,7 +210,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { }); } - private addCustomViewContainers(extensionPoints: IExtensionPointUser[], existingViewContainers: ViewContainer[]): void { + private addCustomViewContainers(extensionPoints: readonly IExtensionPointUser[], existingViewContainers: ViewContainer[]): void { const viewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); let order = TEST_VIEW_CONTAINER_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId).length + 1; for (let { value, collector, description } of extensionPoints) { @@ -198,7 +227,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } } - private removeCustomViewContainers(extensionPoints: IExtensionPointUser[]): void { + private removeCustomViewContainers(extensionPoints: readonly IExtensionPointUser[]): void { const viewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const removedExtensions: Set = extensionPoints.reduce((result, e) => { result.add(ExtensionIdentifier.toKey(e.description.identifier)); return result; }, new Set()); for (const viewContainer of viewContainersRegistry.all) { @@ -327,7 +356,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { // Generate CSS to show the icon in the activity bar const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`; - createCSSRule(iconClass, `-webkit-mask: url('${asDomUri(icon)}') no-repeat 50% 50%; -webkit-mask-size: 24px;`); + createCSSRule(iconClass, `-webkit-mask: ${asCSSUrl(icon)} no-repeat 50% 50%; -webkit-mask-size: 24px;`); } return viewContainer; @@ -349,7 +378,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { }); } - private addViews(extensions: IExtensionPointUser[]): void { + private addViews(extensions: readonly IExtensionPointUser[]): void { for (const extension of extensions) { const { value, collector } = extension; @@ -358,6 +387,11 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return; } + if (entry.key === 'remote' && !extension.description.enableProposedApi) { + collector.warn(localize('ViewContainerRequiresProposedAPI', "View container '{0}' requires 'enableProposedApi' turned on to be added to 'Remote'.", entry.key)); + return; + } + const viewContainer = this.getViewContainer(entry.key); if (!viewContainer) { collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key)); @@ -376,6 +410,12 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return null; } + const order = ExtensionIdentifier.equals(extension.description.identifier, container.extensionId) + ? index + 1 + : container.orderDelegate + ? container.orderDelegate.getOrder(item.group) + : undefined; + const viewDescriptor = { id: item.id, name: item.name, @@ -384,9 +424,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution { canToggleVisibility: true, collapsed: this.showCollapsed(container), treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name, container), - order: ExtensionIdentifier.equals(extension.description.identifier, container.extensionId) ? index + 1 : undefined, + order: order, extensionId: extension.description.identifier, - originalContainerId: entry.key + originalContainerId: entry.key, + group: item.group }; viewIds.push(viewDescriptor.id); @@ -401,7 +442,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return this.viewContainersRegistry.get(EXPLORER)!; } - private removeViews(extensions: IExtensionPointUser[]): void { + private removeViews(extensions: readonly IExtensionPointUser[]): void { const removedExtensions: Set = extensions.reduce((result, e) => { result.add(ExtensionIdentifier.toKey(e.description.identifier)); return result; }, new Set()); for (const viewContainer of this.viewContainersRegistry.all) { const removedViews = this.viewsRegistry.getViews(viewContainer).filter((v: ICustomViewDescriptor) => v.extensionId && removedExtensions.has(ExtensionIdentifier.toKey(v.extensionId))); @@ -440,6 +481,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { case 'explorer': return this.viewContainersRegistry.get(EXPLORER); case 'debug': return this.viewContainersRegistry.get(DEBUG); case 'scm': return this.viewContainersRegistry.get(SCM); + case 'remote': return this.viewContainersRegistry.get(REMOTE); default: return this.viewContainersRegistry.get(`workbench.view.extension.${value}`); } } diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index 0c5b098cd6a..28bbc68d9bf 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -175,6 +175,11 @@ CommandsRegistry.registerCommand('_workbench.addToRecentlyOpened', async functio return windowService.addRecentlyOpened([recent]); }); +CommandsRegistry.registerCommand('_workbench.getRecentlyOpened', async function (accessor: ServicesAccessor) { + const windowService = accessor.get(IWindowService); + return windowService.getRecentlyOpened(); +}); + export class SetEditorLayoutAPICommand { public static ID = 'vscode.setEditorLayout'; public static execute(executor: ICommandsExecutor, layout: EditorGroupLayout): Promise { @@ -205,4 +210,4 @@ CommandsRegistry.registerCommand({ } }] } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index aa05f7e1187..d36d498330d 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -39,13 +39,14 @@ const configurationEntrySchema: IJSONSchema = { }, scope: { type: 'string', - enum: ['application', 'machine', 'window', 'resource'], + enum: ['application', 'machine', 'window', 'resource', 'machine-overridable'], default: 'window', enumDescriptions: [ - nls.localize('scope.application.description', "Application specific configuration, which can be configured only in the user settings."), - nls.localize('scope.machine.description', "Machine specific configuration, which can be configured only in the user settings when the extension is running locally, or only in the remote settings when the extension is running remotely."), - nls.localize('scope.window.description', "Window specific configuration, which can be configured in the user, remote or workspace settings."), - nls.localize('scope.resource.description', "Resource specific configuration, which can be configured in the user, remote, workspace or folder settings.") + nls.localize('scope.application.description', "Configuration that can be configured only in the user settings."), + nls.localize('scope.machine.description', "Configuration that can be configured only in the user settings when the extension is running locally, or only in the remote settings when the extension is running remotely."), + nls.localize('scope.window.description', "Configuration that can be configured in the user, remote or workspace settings."), + nls.localize('scope.resource.description', "Configuration that can be configured in the user, remote, workspace or folder settings."), + nls.localize('scope.machine-overridable.description', "Machine configuration that can be configured also in workspace or folder settings.") ], description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window` and `resource`.") }, @@ -215,6 +216,8 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten propertyConfiguration.scope = ConfigurationScope.MACHINE; } else if (propertyConfiguration.scope.toString() === 'resource') { propertyConfiguration.scope = ConfigurationScope.RESOURCE; + } else if (propertyConfiguration.scope.toString() === 'machine-overridable') { + propertyConfiguration.scope = ConfigurationScope.MACHINE_OVERRIDABLE; } else { propertyConfiguration.scope = ConfigurationScope.WINDOW; } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts similarity index 90% rename from src/vs/workbench/api/node/extHost.api.impl.ts rename to src/vs/workbench/api/common/extHost.api.impl.ts index f4df88c11ba..5370f6a01ba 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -15,63 +15,59 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as files from 'vs/platform/files/common/files'; -import { ExtHostContext, IInitData, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, MainContext, ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostComments } from 'vs/workbench/api/common/extHostComments'; -import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; -import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; -import { ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; +import { ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { ExtHostDialogs } from 'vs/workbench/api/common/extHostDialogs'; import { ExtHostDocumentContentProvider } from 'vs/workbench/api/common/extHostDocumentContentProviders'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { ExtensionActivatedByAPI } from 'vs/workbench/api/common/extHostExtensionActivator'; -import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages'; -import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; import { ExtHostMessageService } from 'vs/workbench/api/common/extHostMessageService'; -import { ExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; -import { LogOutputChannelFactory } from 'vs/workbench/api/node/extHostOutputService'; +import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; import { ExtHostProgress } from 'vs/workbench/api/common/extHostProgress'; import { ExtHostQuickOpen } from 'vs/workbench/api/common/extHostQuickOpen'; import { ExtHostSCM } from 'vs/workbench/api/common/extHostSCM'; -import { ExtHostSearch, registerEHSearchProviders } from 'vs/workbench/api/node/extHostSearch'; import { ExtHostStatusBar } from 'vs/workbench/api/common/extHostStatusBar'; -import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; -import { ExtHostTask } from 'vs/workbench/api/node/extHostTask'; -import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; +import { IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; import { ExtHostTreeViews } from 'vs/workbench/api/common/extHostTreeViews'; -import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadService'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls'; import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; import { ExtHostWindow } from 'vs/workbench/api/common/extHostWindow'; -import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { throwProposedApiError, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { originalFSPath } from 'vs/base/common/resources'; -import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; import { values } from 'vs/base/common/collections'; -import { Schemas } from 'vs/base/common/network'; -import { IURITransformer } from 'vs/base/common/uriIpc'; import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; +import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; +import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -88,68 +84,55 @@ function proposedApiFunction(extension: IExtensionDescription, fn: T): T { /** * This method instantiates and returns the extension API surface */ -export function createApiFactory( - initData: IInitData, - rpcProtocol: IMainContext, - extHostWorkspace: ExtHostWorkspace, - extHostConfiguration: ExtHostConfiguration, - extensionService: ExtHostExtensionService, - extHostLogService: ExtHostLogService, - extHostStorage: ExtHostStorage, - uriTransformer: IURITransformer | null -): IExtensionApiFactory { +export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): IExtensionApiFactory { - // bootstrap services - const services = new ServiceCollection(...getSingletonServiceDescriptors()); - const instaService = new InstantiationService(services); + // services + const initData = accessor.get(IExtHostInitDataService); + const extensionService = accessor.get(IExtHostExtensionService); + const extHostWorkspace = accessor.get(IExtHostWorkspace); + const extHostConfiguration = accessor.get(IExtHostConfiguration); + const uriTransformer = accessor.get(IURITransformerService); + const rpcProtocol = accessor.get(IExtHostRpcService); + const extHostStorage = accessor.get(IExtHostStorage); + const extHostLogService = accessor.get(ILogService); - // Addressable instances - rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); - const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, new ExtHostDecorations(rpcProtocol)); + // register addressable instances + rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); + rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); + rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); + rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); + rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage); + + // automatically create and register addressable instances + const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations)); + const extHostDocumentsAndEditors = rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, accessor.get(IExtHostDocumentsAndEditors)); + const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, accessor.get(IExtHostCommands)); + const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, accessor.get(IExtHostTerminalService)); + const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, accessor.get(IExtHostDebugService)); + const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, accessor.get(IExtHostSearch)); + const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, accessor.get(IExtHostTask)); + const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); + + // manually create and register addressable instances const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment)); const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); - const extHostDocumentsAndEditors = rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new ExtHostDocumentsAndEditors(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadTextEditors))); const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); - const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(rpcProtocol, extHostLogService)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); - rpcProtocol.set(ExtHostContext.ExtHostDownloadService, new ExtHostDownloadService(rpcProtocol.getProxy(MainContext.MainThreadDownloadService), extHostCommands)); - rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); - rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, initData.environment)); const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol)); const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, uriTransformer, extHostDocuments, extHostCommands, extHostDiagnostics, extHostLogService)); const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); - const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostWorkspace, extHostDocumentsAndEditors, extHostLogService)); - const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, instaService.createInstance(ExtHostDebugService, rpcProtocol, extHostWorkspace, extensionService, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); - const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol, uriTransformer, extHostLogService)); - const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); - rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); - const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, new ExtHostOutputService(LogOutputChannelFactory, initData.logsLocation, rpcProtocol)); - rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage); const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol)); - if (initData.remote.isRemote && initData.remote.authority) { - extHostTask.registerTaskSystem(Schemas.vscodeRemote, { - scheme: Schemas.vscodeRemote, - authority: initData.remote.authority, - platform: process.platform - }); - - registerEHSearchProviders(extHostSearch, extHostLogService); - - const cliServer = new CLIServer(extHostCommands); - process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath; - } - // Check that no named customers are missing const expected: ProxyIdentifier[] = values(ExtHostContext); rpcProtocol.assertRegistered(expected); @@ -161,10 +144,6 @@ export function createApiFactory( const extHostStatusBar = new ExtHostStatusBar(rpcProtocol); const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments); - // Register an output channel for exthost log - const outputChannelName = initData.remote.isRemote ? nls.localize('remote extension host Log', "Remote Extension Host") : nls.localize('extension host Log', "Extension Host"); - extHostOutputService.createOutputChannelFromLogFile(outputChannelName, extHostLogService.logFile); - // Register API-ish commands ExtHostApiCommands.register(extHostCommands); @@ -244,11 +223,7 @@ export function createApiFactory( }, getCommands(filterInternal: boolean = false): Thenable { return extHostCommands.getCommands(filterInternal); - }, - onDidExecuteCommand: proposedApiFunction(extension, (listener, thisArgs?, disposables?) => { - checkProposedApiEnabled(extension); - return extHostCommands.onDidExecuteCommand(listener, thisArgs, disposables); - }), + } }; // namespace: env @@ -271,7 +246,7 @@ export function createApiFactory( return extHostClipboard; }, get shell() { - return extHostTerminalService.getDefaultShell(configProvider); + return extHostTerminalService.getDefaultShell(false, configProvider); }, openExternal(uri: URI) { return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.isRemote }); @@ -457,8 +432,13 @@ export function createApiFactory( return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables); }, onDidChangeTerminalDimensions(listener, thisArg?, disposables?) { + checkProposedApiEnabled(extension); return extHostTerminalService.onDidChangeTerminalDimensions(listener, thisArg, disposables); }, + onDidWriteTerminalData(listener, thisArg?, disposables?) { + checkProposedApiEnabled(extension); + return extHostTerminalService.onDidWriteTerminalData(listener, thisArg, disposables); + }, get state() { return extHostWindow.state; }, @@ -532,16 +512,11 @@ export function createApiFactory( if (typeof nameOrOptions === 'object') { if ('pty' in nameOrOptions) { return extHostTerminalService.createExtensionTerminal(nameOrOptions); - } else { - nameOrOptions.hideFromUser = nameOrOptions.hideFromUser || (nameOrOptions.runInBackground && extension.enableProposedApi); - return extHostTerminalService.createTerminalFromOptions(nameOrOptions); } + return extHostTerminalService.createTerminalFromOptions(nameOrOptions); } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); }, - createTerminalRenderer(name: string): vscode.TerminalRenderer { - return extHostTerminalService.createTerminalRenderer(name); - }, registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension); }, @@ -847,7 +822,6 @@ export function createApiFactory( EventEmitter: Emitter, ExtensionExecutionContext: extHostTypes.ExtensionExecutionContext, ExtensionKind: extHostTypes.ExtensionKind, - CustomExecution: extHostTypes.CustomExecution, CustomExecution2: extHostTypes.CustomExecution2, FileChangeType: extHostTypes.FileChangeType, FileSystemError: extHostTypes.FileSystemError, @@ -912,7 +886,7 @@ export function createApiFactory( class Extension implements vscode.Extension { - private _extensionService: ExtHostExtensionService; + private _extensionService: IExtHostExtensionService; private _identifier: ExtensionIdentifier; readonly id: string; @@ -920,7 +894,7 @@ class Extension implements vscode.Extension { readonly packageJSON: IExtensionDescription; readonly extensionKind: vscode.ExtensionKind; - constructor(extensionService: ExtHostExtensionService, description: IExtensionDescription, kind: extHostTypes.ExtensionKind) { + constructor(extensionService: IExtHostExtensionService, description: IExtensionDescription, kind: extHostTypes.ExtensionKind) { this._extensionService = extensionService; this._identifier = description.identifier; this.id = description.identifier.value; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b8f3ff3a9df..f433ba27025 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -21,7 +21,7 @@ import { EndOfLineSequence, ISingleEditOperation } from 'vs/editor/common/model' import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import * as modes from 'vs/editor/common/modes'; import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/modes/languageConfiguration'; -import { ICommandHandlerDescription, ICommandEvent } from 'vs/platform/commands/common/commands'; +import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationData, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -115,8 +115,6 @@ export interface MainThreadClipboardShape extends IDisposable { export interface MainThreadCommandsShape extends IDisposable { $registerCommand(id: string): void; - $registerCommandListener(): void; - $unregisterCommandListener(): void; $unregisterCommand(id: string): void; $executeCommand(id: string, args: any[]): Promise; $getCommands(): Promise; @@ -132,7 +130,7 @@ export interface MainThreadCommentsShape extends IDisposable { $unregisterCommentController(handle: number): void; $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, extensionId: ExtensionIdentifier): modes.CommentThread | undefined; - $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string, contextValue: string | undefined, comments: modes.Comment[], collapseState: modes.CommentThreadCollapsibleState): void; + $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string | undefined, contextValue: string | undefined, comments: modes.Comment[], collapseState: modes.CommentThreadCollapsibleState): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent): void; } @@ -236,16 +234,16 @@ export interface MainThreadTextEditorsShape extends IDisposable { $tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): Promise; $trySetSelections(id: string, selections: ISelection[]): Promise; $tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts: IApplyEditsOptions): Promise; - $tryApplyWorkspaceEdit(workspaceEditDto: WorkspaceEditDto): Promise; + $tryApplyWorkspaceEdit(workspaceEditDto: IWorkspaceEditDto): Promise; $tryInsertSnippet(id: string, template: string, selections: readonly IRange[], opts: IUndoStopOptions): Promise; $getDiffInformation(id: string): Promise; } export interface MainThreadTreeViewsShape extends IDisposable { - $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean }): void; + $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean, canSelectMany: boolean }): void; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise; $reveal(treeViewId: string, treeItem: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise; - $setMessage(treeViewId: string, message: string | IMarkdownString): void; + $setMessage(treeViewId: string, message: string): void; } export interface MainThreadDownloadServiceShape extends IDisposable { @@ -267,28 +265,28 @@ export interface MainThreadKeytarShape extends IDisposable { $findPassword(service: string): Promise; } -export interface ISerializedRegExp { +export interface IRegExpDto { pattern: string; flags?: string; } -export interface ISerializedIndentationRule { - decreaseIndentPattern: ISerializedRegExp; - increaseIndentPattern: ISerializedRegExp; - indentNextLinePattern?: ISerializedRegExp; - unIndentedLinePattern?: ISerializedRegExp; +export interface IIndentationRuleDto { + decreaseIndentPattern: IRegExpDto; + increaseIndentPattern: IRegExpDto; + indentNextLinePattern?: IRegExpDto; + unIndentedLinePattern?: IRegExpDto; } -export interface ISerializedOnEnterRule { - beforeText: ISerializedRegExp; - afterText?: ISerializedRegExp; - oneLineAboveText?: ISerializedRegExp; +export interface IOnEnterRuleDto { + beforeText: IRegExpDto; + afterText?: IRegExpDto; + oneLineAboveText?: IRegExpDto; action: EnterAction; } -export interface ISerializedLanguageConfiguration { +export interface ILanguageConfigurationDto { comments?: CommentRule; brackets?: CharacterPair[]; - wordPattern?: ISerializedRegExp; - indentationRules?: ISerializedIndentationRule; - onEnterRules?: ISerializedOnEnterRule[]; + wordPattern?: IRegExpDto; + indentationRules?: IIndentationRuleDto; + onEnterRules?: IOnEnterRuleDto[]; __electricCharacterSupport?: { brackets?: any; docComment?: { @@ -309,7 +307,7 @@ export interface ISerializedLanguageConfiguration { export type GlobPattern = string | { base: string; pattern: string }; -export interface ISerializedDocumentFilter { +export interface IDocumentFilterDto { $serialized: true; language?: string; scheme?: string; @@ -317,37 +315,37 @@ export interface ISerializedDocumentFilter { exclusive?: boolean; } -export interface ISerializedSignatureHelpProviderMetadata { +export interface ISignatureHelpProviderMetadataDto { readonly triggerCharacters: readonly string[]; readonly retriggerCharacters: readonly string[]; } export interface MainThreadLanguageFeaturesShape extends IDisposable { $unregister(handle: number): void; - $registerDocumentSymbolProvider(handle: number, selector: ISerializedDocumentFilter[], label: string): void; - $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void; + $registerDocumentSymbolProvider(handle: number, selector: IDocumentFilterDto[], label: string): void; + $registerCodeLensSupport(handle: number, selector: IDocumentFilterDto[], eventHandle: number | undefined): void; $emitCodeLensEvent(eventHandle: number, event?: any): void; - $registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerDeclarationSupport(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerImplementationSupport(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerTypeDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerHoverProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $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[], extensionId: ExtensionIdentifier, displayName: string): void; - $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier, displayName: string): void; - $registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; + $registerDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void; + $registerDeclarationSupport(handle: number, selector: IDocumentFilterDto[]): void; + $registerImplementationSupport(handle: number, selector: IDocumentFilterDto[]): void; + $registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void; + $registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; + $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], supportedKinds?: string[]): void; + $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; + $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; + $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], 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, extensionId: ExtensionIdentifier): void; - $registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], metadata: ISerializedSignatureHelpProviderMetadata): void; - $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[], supportsResolve: boolean): void; - $registerDocumentColorProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerFoldingRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerSelectionRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $registerCallHierarchyProvider(handle: number, selector: ISerializedDocumentFilter[]): void; - $setLanguageConfiguration(handle: number, languageId: string, configuration: ISerializedLanguageConfiguration): void; + $registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportsResolveInitialValues: boolean): void; + $registerSuggestSupport(handle: number, selector: IDocumentFilterDto[], triggerCharacters: string[], supportsResolveDetails: boolean, extensionId: ExtensionIdentifier): void; + $registerSignatureHelpProvider(handle: number, selector: IDocumentFilterDto[], metadata: ISignatureHelpProviderMetadataDto): void; + $registerDocumentLinkProvider(handle: number, selector: IDocumentFilterDto[], supportsResolve: boolean): void; + $registerDocumentColorProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerFoldingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerSelectionRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; + $registerCallHierarchyProvider(handle: number, selector: IDocumentFilterDto[]): void; + $setLanguageConfiguration(handle: number, languageId: string, configuration: ILanguageConfigurationDto): void; } export interface MainThreadLanguagesShape extends IDisposable { @@ -395,12 +393,14 @@ export interface TerminalLaunchConfig { export interface MainThreadTerminalServiceShape extends IDisposable { $createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string }>; - $createTerminalRenderer(name: string): Promise; $dispose(terminalId: number): void; $hide(terminalId: number): void; $sendText(terminalId: number, text: string, addNewLine: boolean): void; $show(terminalId: number, preserveFocus: boolean): void; + /** @deprecated */ $registerOnDataListener(terminalId: number): void; + $startSendingDataEvents(): void; + $stopSendingDataEvents(): void; // Process $sendProcessTitle(terminalId: number, title: string): void; @@ -411,12 +411,6 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $sendProcessCwd(terminalId: number, initialCwd: string): void; $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void; $sendResolvedLaunchConfig(terminalId: number, shellLaunchConfig: IShellLaunchConfig): void; - - // Renderer - $terminalRendererSetName(terminalId: number, name: string): void; - $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void; - $terminalRendererWrite(terminalId: number, text: string): void; - $terminalRendererRegisterOnInputListener(terminalId: number): void; } export interface TransferQuickPickItems extends quickInput.IQuickPickItem { @@ -555,15 +549,17 @@ export interface MainThreadWebviewsShape extends IDisposable { $unregisterSerializer(viewType: string): void; } -export interface WebviewPanelViewState { - readonly active: boolean; - readonly visible: boolean; - readonly position: EditorViewColumn; +export interface WebviewPanelViewStateData { + [handle: string]: { + readonly active: boolean; + readonly visible: boolean; + readonly position: EditorViewColumn; + }; } export interface ExtHostWebviewsShape { $onMessage(handle: WebviewPanelHandle, message: any): void; - $onDidChangeWebviewPanelViewState(handle: WebviewPanelHandle, newState: WebviewPanelViewState): void; + $onDidChangeWebviewPanelViewStates(newState: WebviewPanelViewStateData): void; $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise; $deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; } @@ -649,7 +645,7 @@ export interface SCMProviderFeatures { count?: number; commitTemplate?: string; acceptInputCommand?: modes.Command; - statusBarCommands?: CommandDto[]; + statusBarCommands?: ICommandDto[]; } export interface SCMGroupFeatures { @@ -721,8 +717,8 @@ export interface MainThreadDebugServiceShape extends IDisposable { $customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise; $appendDebugConsole(value: string): void; $startBreakpointEvents(): void; - $registerBreakpoints(breakpoints: Array): Promise; - $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): Promise; + $registerBreakpoints(breakpoints: Array): Promise; + $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[], dataBreakpointIds: string[]): Promise; } export interface IOpenUriOptions { @@ -739,7 +735,6 @@ export interface MainThreadWindowShape extends IDisposable { export interface ExtHostCommandsShape { $executeContributedCommand(id: string, ...args: any[]): Promise; $getContributedCommandHandlerDescriptions(): Promise<{ [id: string]: string | ICommandHandlerDescription }>; - $handleDidExecuteCommand(command: ICommandEvent): void; } export interface ExtHostConfigurationShape { @@ -748,7 +743,7 @@ export interface ExtHostConfigurationShape { } export interface ExtHostDiagnosticsShape { - + $acceptMarkersChange(data: [UriComponents, IMarkerData[]][]): void; } export interface ExtHostDocumentContentProvidersShape { @@ -819,9 +814,6 @@ export interface ExtHostTreeViewsShape { $setVisible(treeViewId: string, visible: boolean): void; } -export interface ExtHostDownloadServiceShape { -} - export interface ExtHostWorkspaceShape { $initializeWorkspace(workspace: IWorkspaceData | null): void; $acceptWorkspaceData(workspace: IWorkspaceData | null): void; @@ -876,6 +868,7 @@ export interface ExtHostExtensionServiceShape { $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise; $activateByEvent(activationEvent: string): Promise; $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise; + $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise; @@ -927,7 +920,7 @@ export class IdObject { } } -export interface SuggestDataDto { +export interface ISuggestDataDto { a/* label */: string; b/* kind */: modes.CompletionItemKind; c/* detail */?: string; @@ -946,51 +939,51 @@ export interface SuggestDataDto { x?: ChainedCacheId; } -export interface SuggestResultDto { +export interface ISuggestResultDto { x?: number; a: IRange; - b: SuggestDataDto[]; + b: ISuggestDataDto[]; c?: boolean; } -export interface SignatureHelpDto { +export interface ISignatureHelpDto { id: CacheId; signatures: modes.SignatureInformation[]; activeSignature: number; activeParameter: number; } -export interface SignatureHelpContextDto { +export interface ISignatureHelpContextDto { readonly triggerKind: modes.SignatureHelpTriggerKind; readonly triggerCharacter?: string; readonly isRetrigger: boolean; - readonly activeSignatureHelp?: SignatureHelpDto; + readonly activeSignatureHelp?: ISignatureHelpDto; } -export interface LocationDto { +export interface ILocationDto { uri: UriComponents; range: IRange; } -export interface DefinitionLinkDto { +export interface IDefinitionLinkDto { originSelectionRange?: IRange; uri: UriComponents; range: IRange; targetSelectionRange?: IRange; } -export interface WorkspaceSymbolDto extends IdObject { +export interface IWorkspaceSymbolDto extends IdObject { name: string; containerName?: string; kind: modes.SymbolKind; - location: LocationDto; + location: ILocationDto; } -export interface WorkspaceSymbolsDto extends IdObject { - symbols: WorkspaceSymbolDto[]; +export interface IWorkspaceSymbolsDto extends IdObject { + symbols: IWorkspaceSymbolDto[]; } -export interface ResourceFileEditDto { +export interface IResourceFileEditDto { oldUri?: UriComponents; newUri?: UriComponents; options?: { @@ -1001,76 +994,76 @@ export interface ResourceFileEditDto { }; } -export interface ResourceTextEditDto { +export interface IResourceTextEditDto { resource: UriComponents; modelVersionId?: number; edits: modes.TextEdit[]; } -export interface WorkspaceEditDto { - edits: Array; +export interface IWorkspaceEditDto { + edits: Array; // todo@joh reject should go into rename rejectReason?: string; } -export function reviveWorkspaceEditDto(data: WorkspaceEditDto | undefined): modes.WorkspaceEdit { +export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): modes.WorkspaceEdit { if (data && data.edits) { for (const edit of data.edits) { - if (typeof (edit).resource === 'object') { - (edit).resource = URI.revive((edit).resource); + if (typeof (edit).resource === 'object') { + (edit).resource = URI.revive((edit).resource); } else { - (edit).newUri = URI.revive((edit).newUri); - (edit).oldUri = URI.revive((edit).oldUri); + (edit).newUri = URI.revive((edit).newUri); + (edit).oldUri = URI.revive((edit).oldUri); } } } return data; } -export type CommandDto = ObjectIdentifier & modes.Command; +export type ICommandDto = ObjectIdentifier & modes.Command; -export interface CodeActionDto { +export interface ICodeActionDto { title: string; - edit?: WorkspaceEditDto; + edit?: IWorkspaceEditDto; diagnostics?: IMarkerData[]; - command?: CommandDto; + command?: ICommandDto; kind?: string; isPreferred?: boolean; } -export interface CodeActionListDto { +export interface ICodeActionListDto { cacheId: number; - actions: ReadonlyArray; + actions: ReadonlyArray; } export type CacheId = number; export type ChainedCacheId = [CacheId, CacheId]; -export interface LinksListDto { +export interface ILinksListDto { id?: CacheId; - links: LinkDto[]; + links: ILinkDto[]; } -export interface LinkDto { +export interface ILinkDto { cacheId?: ChainedCacheId; range: IRange; url?: string | UriComponents; tooltip?: string; } -export interface CodeLensListDto { +export interface ICodeLensListDto { cacheId?: number; - lenses: CodeLensDto[]; + lenses: ICodeLensDto[]; } -export interface CodeLensDto { +export interface ICodeLensDto { cacheId?: ChainedCacheId; range: IRange; - command?: CommandDto; + command?: ICommandDto; } -export interface CallHierarchyDto { +export interface ICallHierarchyDto { _id: number; kind: modes.SymbolKind; name: string; @@ -1082,40 +1075,40 @@ export interface CallHierarchyDto { export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $resolveCodeLens(handle: number, symbol: CodeLensDto, token: CancellationToken): Promise; + $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $resolveCodeLens(handle: number, symbol: ICodeLensDto, token: CancellationToken): Promise; $releaseCodeLenses(handle: number, id: number): void; - $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; + $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; + $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; $releaseCodeActions(handle: number, cacheId: number): void; $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; + $provideWorkspaceSymbols(handle: number, search: string, token: CancellationToken): Promise; + $resolveWorkspaceSymbol(handle: number, symbol: IWorkspaceSymbolDto, token: CancellationToken): Promise; $releaseWorkspaceSymbols(handle: number, id: number): void; - $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, 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, id: ChainedCacheId, token: CancellationToken): Promise; + $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; + $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; - $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; $releaseSignatureHelp(handle: number, id: number): void; - $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; + $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise; $releaseDocumentLinks(handle: number, id: number): void; $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, positions: IPosition[], token: CancellationToken): Promise; - $provideCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $resolveCallHierarchyItem(handle: number, item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[CallHierarchyDto, modes.Location[]][]>; + $provideCallHierarchyItem(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $resolveCallHierarchyItem(handle: number, item: callHierarchy.CallHierarchyItem, direction: callHierarchy.CallHierarchyDirection, token: CancellationToken): Promise<[ICallHierarchyDto, modes.Location[]][]>; } export interface ExtHostQuickOpenShape { @@ -1129,7 +1122,7 @@ export interface ExtHostQuickOpenShape { $onDidHide(sessionId: number): void; } -export interface ShellLaunchConfigDto { +export interface IShellLaunchConfigDto { name?: string; executable?: string; args?: string[] | string; @@ -1157,12 +1150,13 @@ export interface ExtHostTerminalServiceShape { $acceptTerminalOpened(id: number, name: string): void; $acceptActiveTerminalChanged(id: number | null): void; $acceptTerminalProcessId(id: number, processId: number): void; + /** @deprecated */ $acceptTerminalProcessData(id: number, data: string): void; - $acceptTerminalRendererInput(id: number, data: string): void; + $acceptTerminalProcessData2(id: number, data: string): void; $acceptTerminalTitleChange(id: number, name: string): void; $acceptTerminalDimensions(id: number, cols: number, rows: number): void; $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void; - $spawnExtHostProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; + $spawnExtHostProcess(id: number, shellLaunchConfig: IShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): void; $acceptProcessInput(id: number, data: string): void; $acceptProcessResize(id: number, cols: number, rows: number): void; @@ -1172,7 +1166,7 @@ export interface ExtHostTerminalServiceShape { $acceptProcessRequestLatency(id: number): number; $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; $requestAvailableShells(): Promise; - $requestDefaultShellAndArgs(): Promise; + $requestDefaultShellAndArgs(useAutomationShell: boolean): Promise; } export interface ExtHostSCMShape { @@ -1207,6 +1201,13 @@ export interface IFunctionBreakpointDto extends IBreakpointDto { functionName: string; } +export interface IDataBreakpointDto extends IBreakpointDto { + type: 'data'; + dataId: string; + canPersist: boolean; + label: string; +} + export interface ISourceBreakpointDto extends IBreakpointDto { type: 'source'; uri: UriComponents; @@ -1215,9 +1216,9 @@ export interface ISourceBreakpointDto extends IBreakpointDto { } export interface IBreakpointsDeltaDto { - added?: Array; + added?: Array; removed?: string[]; - changed?: Array; + changed?: Array; } export interface ISourceMultiBreakpointDto { @@ -1250,8 +1251,8 @@ export interface ExtHostDebugServiceShape { $startDASession(handle: number, session: IDebugSessionDto): Promise; $stopDASession(handle: number): Promise; $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; - $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): Promise; - $provideDebugConfigurations(handle: number, folder: UriComponents | undefined): Promise; + $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; + $provideDebugConfigurations(handle: number, folder: UriComponents | undefined, token: CancellationToken): Promise; $legacyDebugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): Promise; // TODO@AW legacy $provideDebugAdapter(handle: number, session: IDebugSessionDto): Promise; $acceptDebugSessionStarted(session: IDebugSessionDto): void; @@ -1357,7 +1358,6 @@ export const ExtHostContext = { ExtHostDocumentSaveParticipant: createExtId('ExtHostDocumentSaveParticipant'), ExtHostEditors: createExtId('ExtHostEditors'), ExtHostTreeViews: createExtId('ExtHostTreeViews'), - ExtHostDownloadService: createExtId('ExtHostDownloadService'), ExtHostFileSystem: createExtId('ExtHostFileSystem'), ExtHostFileSystemEventService: createExtId('ExtHostFileSystemEventService'), ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures'), diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index f6aa2e727a4..d84da000c3f 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -8,7 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { IRawColorInfo, WorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IRawColorInfo, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; @@ -135,7 +135,7 @@ 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: 'rangeOrSelection', description: 'Range in a text document. Some refactoring provider requires Selection object.', constraint: types.Range }, { name: 'kind', description: '(optional) Code action kind to return code actions for', constraint: (value: any) => !value || typeof value.value === 'string' }, ], returns: 'A promise that resolves to an array of Command-instances.' @@ -362,7 +362,7 @@ export class ExtHostApiCommands { position: position && typeConverters.Position.from(position), newName }; - return this._commands.executeCommand('_executeDocumentRenameProvider', args).then(value => { + return this._commands.executeCommand('_executeDocumentRenameProvider', args).then(value => { if (!value) { return undefined; } @@ -470,20 +470,22 @@ export class ExtHostApiCommands { return res; } - detail: string; - range: vscode.Range; - selectionRange: vscode.Range; - children: vscode.DocumentSymbol[]; - containerName: string; + detail!: string; + range!: vscode.Range; + selectionRange!: vscode.Range; + children!: vscode.DocumentSymbol[]; + containerName!: string; } return value.map(MergedInfo.to); }); } - private _executeCodeActionProvider(resource: URI, range: types.Range, kind?: string): Promise<(vscode.CodeAction | vscode.Command | undefined)[] | undefined> { + private _executeCodeActionProvider(resource: URI, rangeOrSelection: types.Range | types.Selection, kind?: string): Promise<(vscode.CodeAction | vscode.Command | undefined)[] | undefined> { const args = { resource, - range: typeConverters.Range.from(range), + rangeOrSelection: types.Selection.isSelection(rangeOrSelection) + ? typeConverters.Selection.from(rangeOrSelection) + : typeConverters.Range.from(rangeOrSelection), kind }; return this._commands.executeCommand('_executeCodeActionProvider', args) @@ -504,6 +506,7 @@ export class ExtHostApiCommands { if (codeAction.command) { ret.command = this._commands.converter.fromInternal(codeAction.command); } + ret.isPreferred = codeAction.isPreferred; return ret; } })); diff --git a/src/vs/workbench/api/common/extHostCodeInsets.ts b/src/vs/workbench/api/common/extHostCodeInsets.ts index cf7f22b692a..769e4de2a7a 100644 --- a/src/vs/workbench/api/common/extHostCodeInsets.ts +++ b/src/vs/workbench/api/common/extHostCodeInsets.ts @@ -10,7 +10,7 @@ import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; import * as vscode from 'vscode'; import { ExtHostEditorInsetsShape, MainThreadEditorInsetsShape } from './extHost.protocol'; -import { toWebviewResource, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; +import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import { generateUuid } from 'vs/base/common/uuid'; export class ExtHostEditorInsets implements ExtHostEditorInsetsShape { @@ -63,10 +63,10 @@ export class ExtHostEditorInsets implements ExtHostEditorInsetsShape { private readonly _uuid = generateUuid(); private _html: string = ''; - private _options: vscode.WebviewOptions; + private _options: vscode.WebviewOptions = Object.create(null); - toWebviewResource(resource: vscode.Uri): vscode.Uri { - return toWebviewResource(that._initData, this._uuid, resource); + asWebviewUri(resource: vscode.Uri): vscode.Uri { + return asWebviewUri(that._initData, this._uuid, resource); } get cspSource(): string { diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 19e4d5655f1..8111a7687cf 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { validateConstraint } from 'vs/base/common/types'; -import { ICommandHandlerDescription, ICommandEvent } from 'vs/platform/commands/common/commands'; +import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters'; import { cloneAndChange } from 'vs/base/common/objects'; -import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier, IMainContext, CommandDto } from './extHost.protocol'; +import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier, ICommandDto } from './extHost.protocol'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import * as modes from 'vs/editor/common/modes'; import * as vscode from 'vscode'; @@ -17,8 +17,9 @@ import { revive } from 'vs/base/common/marshalling'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { URI } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; interface CommandHandler { callback: Function; @@ -32,8 +33,7 @@ export interface ArgumentProcessor { export class ExtHostCommands implements ExtHostCommandsShape { - private readonly _onDidExecuteCommand: Emitter; - readonly onDidExecuteCommand: Event; + readonly _serviceBrand: any; private readonly _commands = new Map(); private readonly _proxy: MainThreadCommandsShape; @@ -42,15 +42,10 @@ export class ExtHostCommands implements ExtHostCommandsShape { private readonly _argumentProcessors: ArgumentProcessor[]; constructor( - mainContext: IMainContext, - logService: ILogService + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @ILogService logService: ILogService ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadCommands); - this._onDidExecuteCommand = new Emitter({ - onFirstListenerDidAdd: () => this._proxy.$registerCommandListener(), - onLastListenerRemove: () => this._proxy.$unregisterCommandListener(), - }); - this.onDidExecuteCommand = Event.filter(this._onDidExecuteCommand.event, e => e.command[0] !== '_'); // filter 'private' commands + this._proxy = extHostRpc.getProxy(MainContext.MainThreadCommands); this._logService = logService; this._converter = new CommandsConverter(this); this._argumentProcessors = [ @@ -115,13 +110,6 @@ export class ExtHostCommands implements ExtHostCommandsShape { }); } - $handleDidExecuteCommand(command: ICommandEvent): void { - this._onDidExecuteCommand.fire({ - command: command.commandId, - arguments: command.args.map(arg => this._argumentProcessors.reduce((r, p) => p.processArgument(r), arg)) - }); - } - executeCommand(id: string, ...args: any[]): Promise { this._logService.trace('ExtHostCommands#executeCommand', id); @@ -170,7 +158,6 @@ export class ExtHostCommands implements ExtHostCommandsShape { try { const result = callback.apply(thisArg, args); - this._onDidExecuteCommand.fire({ command: id, arguments: args }); return Promise.resolve(result); } catch (err) { this._logService.error(err, id); @@ -227,13 +214,13 @@ export class CommandsConverter { this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } - toInternal(command: vscode.Command | undefined, disposables: DisposableStore): CommandDto | undefined { + toInternal(command: vscode.Command | undefined, disposables: DisposableStore): ICommandDto | undefined { if (!command) { return undefined; } - const result: CommandDto = { + const result: ICommandDto = { $ident: undefined, id: command.command, title: command.title, @@ -281,3 +268,6 @@ export class CommandsConverter { } } + +export interface IExtHostCommands extends ExtHostCommands { } +export const IExtHostCommands = createDecorator('IExtHostCommands'); diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index b71643ba369..b4057f98164 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -253,13 +253,13 @@ export class ExtHostCommentThread implements vscode.CommentThread { return this._range; } - private _label: string; + private _label: string | undefined; - get label(): string { + get label(): string | undefined { return this._label; } - set label(label: string) { + set label(label: string | undefined) { this._label = label; this._onDidUpdateCommentThread.fire(); } diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 4c8515502df..99402c46057 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -7,8 +7,8 @@ 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/common/extHostWorkspace'; -import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData, IConfigurationInitData } from './extHost.protocol'; +import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { ExtHostConfigurationShape, MainThreadConfigurationShape, IWorkspaceConfigurationChangeEventData, IConfigurationInitData, MainContext } from './extHost.protocol'; import { ConfigurationTarget as ExtHostConfigurationTarget } from './extHostTypes'; import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationChangeEvent, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; @@ -18,6 +18,8 @@ import { ConfigurationScope, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/confi import { isObject } from 'vs/base/common/types'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; function lookUp(tree: any, key: string) { if (key) { @@ -40,13 +42,18 @@ type ConfigurationInspect = { export class ExtHostConfiguration implements ExtHostConfigurationShape { + readonly _serviceBrand: any; + private readonly _proxy: MainThreadConfigurationShape; private readonly _extHostWorkspace: ExtHostWorkspace; private readonly _barrier: Barrier; private _actual: ExtHostConfigProvider | null; - constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace) { - this._proxy = proxy; + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadConfiguration); this._extHostWorkspace = extHostWorkspace; this._barrier = new Barrier(); this._actual = null; @@ -263,8 +270,8 @@ export class ExtHostConfigProvider { const defaultConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.defaults); const userConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.user); const workspaceConfiguration = ExtHostConfigProvider.parseConfigurationModel(data.workspace); - const folders: ResourceMap = Object.keys(data.folders).reduce((result, key) => { - result.set(URI.parse(key), ExtHostConfigProvider.parseConfigurationModel(data.folders[key])); + const folders: ResourceMap = data.folders.reduce((result, value) => { + result.set(URI.revive(value[0]), ExtHostConfigProvider.parseConfigurationModel(value[1])); return result; }, new ResourceMap()); return new Configuration(defaultConfiguration, userConfiguration, new ConfigurationModel(), workspaceConfiguration, folders, new ConfigurationModel(), new ResourceMap(), false); @@ -274,3 +281,6 @@ export class ExtHostConfigProvider { return new ConfigurationModel(model.contents, model.keys, model.overrides).freeze(); } } + +export const IExtHostConfiguration = createDecorator('IExtHostConfiguration'); +export interface IExtHostConfiguration extends ExtHostConfiguration { } diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts new file mode 100644 index 00000000000..02b3c423f87 --- /dev/null +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { ExtHostDebugServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as vscode from 'vscode'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtHostDebugService = createDecorator('IExtHostDebugService'); + +export interface IExtHostDebugService extends ExtHostDebugServiceShape { + + readonly _serviceBrand: any; + + onDidStartDebugSession: Event; + onDidTerminateDebugSession: Event; + onDidChangeActiveDebugSession: Event; + activeDebugSession: vscode.DebugSession | undefined; + activeDebugConsole: vscode.DebugConsole; + onDidReceiveDebugSessionCustomEvent: Event; + onDidChangeBreakpoints: Event; + breakpoints: vscode.Breakpoint[]; + + addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise; + removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise; + startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession): Promise; + registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable; + registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable; + registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable; +} + diff --git a/src/vs/workbench/api/common/extHostDecorations.ts b/src/vs/workbench/api/common/extHostDecorations.ts index 0c176afc362..b62fc15ee32 100644 --- a/src/vs/workbench/api/common/extHostDecorations.ts +++ b/src/vs/workbench/api/common/extHostDecorations.ts @@ -5,26 +5,31 @@ import * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; -import { MainContext, IMainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, DecorationData, DecorationRequest, DecorationReply } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, DecorationData, DecorationRequest, DecorationReply } from 'vs/workbench/api/common/extHost.protocol'; import { Disposable } from 'vs/workbench/api/common/extHostTypes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { asArray } from 'vs/base/common/arrays'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; interface ProviderData { provider: vscode.DecorationProvider; extensionId: ExtensionIdentifier; } -export class ExtHostDecorations implements ExtHostDecorationsShape { +export class ExtHostDecorations implements IExtHostDecorations { private static _handlePool = 0; + readonly _serviceBrand: undefined; private readonly _provider = new Map(); private readonly _proxy: MainThreadDecorationsShape; - constructor(mainContext: IMainContext) { - this._proxy = mainContext.getProxy(MainContext.MainThreadDecorations); + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadDecorations); } registerDecorationProvider(provider: vscode.DecorationProvider, extensionId: ExtensionIdentifier): vscode.Disposable { @@ -70,3 +75,6 @@ export class ExtHostDecorations implements ExtHostDecorationsShape { }); } } + +export const IExtHostDecorations = createDecorator('IExtHostDecorations'); +export interface IExtHostDecorations extends ExtHostDecorations, ExtHostDecorationsShape { } diff --git a/src/vs/workbench/api/common/extHostDiagnostics.ts b/src/vs/workbench/api/common/extHostDiagnostics.ts index 60dae259b05..d654408346b 100644 --- a/src/vs/workbench/api/common/extHostDiagnostics.ts +++ b/src/vs/workbench/api/common/extHostDiagnostics.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import * as vscode from 'vscode'; import { MainContext, MainThreadDiagnosticsShape, ExtHostDiagnosticsShape, IMainContext } from './extHost.protocol'; import { DiagnosticSeverity } from './extHostTypes'; @@ -20,12 +20,12 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { private readonly _owner: string; private readonly _maxDiagnosticsPerFile: number; private readonly _onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>; - private readonly _proxy: MainThreadDiagnosticsShape; + private readonly _proxy: MainThreadDiagnosticsShape | undefined; private _isDisposed = false; private _data = new Map(); - constructor(name: string, owner: string, maxDiagnosticsPerFile: number, proxy: MainThreadDiagnosticsShape, onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>) { + constructor(name: string, owner: string, maxDiagnosticsPerFile: number, proxy: MainThreadDiagnosticsShape | undefined, onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>) { this._name = name; this._owner = owner; this._maxDiagnosticsPerFile = maxDiagnosticsPerFile; @@ -36,7 +36,9 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { dispose(): void { if (!this._isDisposed) { this._onDidChangeDiagnostics.fire(keys(this._data)); - this._proxy.$clear(this._owner); + if (this._proxy) { + this._proxy.$clear(this._owner); + } this._data = undefined!; this._isDisposed = true; } @@ -112,6 +114,9 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { this._onDidChangeDiagnostics.fire(toSync); // compute change and send to main side + if (!this._proxy) { + return; + } const entries: [URI, IMarkerData[]][] = []; for (let uri of toSync) { let marker: IMarkerData[] = []; @@ -149,7 +154,6 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { entries.push([uri, marker]); } - this._proxy.$changeMany(this._owner, entries); } @@ -157,14 +161,18 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { this._checkDisposed(); this._onDidChangeDiagnostics.fire([uri]); this._data.delete(uri.toString()); - this._proxy.$changeMany(this._owner, [[uri, undefined]]); + if (this._proxy) { + this._proxy.$changeMany(this._owner, [[uri, undefined]]); + } } clear(): void { this._checkDisposed(); this._onDidChangeDiagnostics.fire(keys(this._data)); this._data.clear(); - this._proxy.$clear(this._owner); + if (this._proxy) { + this._proxy.$clear(this._owner); + } } forEach(callback: (uri: URI, diagnostics: ReadonlyArray, collection: DiagnosticCollection) => any, thisArg?: any): void { @@ -311,4 +319,20 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { }); return res; } + + private _mirrorCollection: vscode.DiagnosticCollection | undefined; + + $acceptMarkersChange(data: [UriComponents, IMarkerData[]][]): void { + + if (!this._mirrorCollection) { + const name = '_generated_mirror'; + const collection = new DiagnosticCollection(name, name, ExtHostDiagnostics._maxDiagnosticsPerFile, undefined, this._onDidChangeDiagnostics); + this._collections.set(name, collection); + this._mirrorCollection = collection; + } + + for (const [uri, markers] of data) { + this._mirrorCollection.set(URI.revive(uri), markers.map(converter.Diagnostic.to)); + } + } } diff --git a/src/vs/workbench/api/common/extHostDocumentContentProviders.ts b/src/vs/workbench/api/common/extHostDocumentContentProviders.ts index b6f46b8f759..56fda7f3a90 100644 --- a/src/vs/workbench/api/common/extHostDocumentContentProviders.ts +++ b/src/vs/workbench/api/common/extHostDocumentContentProviders.ts @@ -50,7 +50,7 @@ export class ExtHostDocumentContentProvider implements ExtHostDocumentContentPro } if (this._documentsAndEditors.getDocument(uri)) { this.$provideTextDocumentContent(handle, uri).then(value => { - if (!value) { + if (!value && typeof value !== 'string') { return; } diff --git a/src/vs/workbench/api/common/extHostDocumentData.ts b/src/vs/workbench/api/common/extHostDocumentData.ts index 96f70218b48..b44527a2b51 100644 --- a/src/vs/workbench/api/common/extHostDocumentData.ts +++ b/src/vs/workbench/api/common/extHostDocumentData.ts @@ -26,7 +26,7 @@ export class ExtHostDocumentData extends MirrorTextModel { private _proxy: MainThreadDocumentsShape; private _languageId: string; private _isDirty: boolean; - private _document: vscode.TextDocument; + private _document?: vscode.TextDocument; private _textLines: vscode.TextLine[] = []; private _isDisposed: boolean = false; diff --git a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts index 13dd34a001b..f55a89b3f30 100644 --- a/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { sequence } from 'vs/base/common/async'; import { illegalState } from 'vs/base/common/errors'; -import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, ResourceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IResourceTextEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { TextEdit } from 'vs/workbench/api/common/extHostTypes'; import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -142,7 +142,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic }).then(values => { - const resourceEdit: ResourceTextEditDto = { + const resourceEdit: IResourceTextEditDto = { resource: document.uri, edits: [] }; @@ -153,7 +153,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic resourceEdit.edits.push({ range: range && Range.from(range), text: newText, - eol: EndOfLine.from(newEol) + eol: newEol && EndOfLine.from(newEol) }); } } diff --git a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts index 5157bb71267..e80289ecb5f 100644 --- a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts @@ -7,17 +7,21 @@ import * as assert from 'vs/base/common/assert'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { Disposable } from 'vs/workbench/api/common/extHostTypes'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape { + readonly _serviceBrand: any; + private _disposables: Disposable[] = []; - private _activeEditorId: string | null; + private _activeEditorId: string | null = null; private readonly _editors = new Map(); private readonly _documents = new Map(); @@ -33,7 +37,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha readonly onDidChangeActiveTextEditor: Event = this._onDidChangeActiveTextEditor.event; constructor( - private readonly _mainContext: IMainContext, + @IExtHostRpcService private readonly _extHostRpc: IExtHostRpcService, ) { } dispose() { @@ -64,7 +68,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha assert.ok(!this._documents.has(resource.toString()), `document '${resource} already exists!'`); const documentData = new ExtHostDocumentData( - this._mainContext.getProxy(MainContext.MainThreadDocuments), + this._extHostRpc.getProxy(MainContext.MainThreadDocuments), resource, data.lines, data.EOL, @@ -95,7 +99,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha const documentData = this._documents.get(resource.toString())!; const editor = new ExtHostTextEditor( - this._mainContext.getProxy(MainContext.MainThreadTextEditors), + this._extHostRpc.getProxy(MainContext.MainThreadTextEditors), data.id, documentData, data.selections.map(typeConverters.Selection.to), @@ -159,3 +163,6 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha return result; } } + +export interface IExtHostDocumentsAndEditors extends ExtHostDocumentsAndEditors { } +export const IExtHostDocumentsAndEditors = createDecorator('IExtHostDocumentsAndEditors'); diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts new file mode 100644 index 00000000000..8c903a6027c --- /dev/null +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -0,0 +1,779 @@ +/*--------------------------------------------------------------------------------------------- + * 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 path from 'vs/base/common/path'; +import { originalFSPath, joinPath } from 'vs/base/common/resources'; +import { Barrier } from 'vs/base/common/async'; +import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { TernarySearchTree } from 'vs/base/common/map'; +import { URI } from 'vs/base/common/uri'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostExtensionServiceShape, IInitData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IResolveAuthorityResult } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; +import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; +import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import * as errors from 'vs/base/common/errors'; +import * as vscode from 'vscode'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { Schemas } from 'vs/base/common/network'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; +import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/workbench/api/common/extHostTypes'; +import { ResolvedAuthority, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; + +interface ITestRunner { + /** Old test runner API, as exported from `vscode/lib/testrunner` */ + run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; +} + +interface INewTestRunner { + /** New test runner API, as explained in the extension test doc */ + run(): Promise; +} + +export const IHostUtils = createDecorator('IHostUtils'); + +export interface IHostUtils { + _serviceBrand: undefined; + exit(code?: number): void; + exists(path: string): Promise; + realpath(path: string): Promise; +} + +type TelemetryActivationEventFragment = { + id: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + name: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + extensionVersion: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; + publisherDisplayName: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + activationEvents: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + isBuiltin: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + reason: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; + +export abstract class AbstractExtHostExtensionService implements ExtHostExtensionServiceShape { + + readonly _serviceBrand: any; + + private static readonly WORKSPACE_CONTAINS_TIMEOUT = 7000; + + protected readonly _hostUtils: IHostUtils; + protected readonly _initData: IInitData; + protected readonly _extHostContext: IExtHostRpcService; + protected readonly _instaService: IInstantiationService; + protected readonly _extHostWorkspace: ExtHostWorkspace; + protected readonly _extHostConfiguration: ExtHostConfiguration; + protected readonly _logService: ILogService; + + protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape; + protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; + protected readonly _mainThreadExtensionsProxy: MainThreadExtensionServiceShape; + + private readonly _almostReadyToRunExtensions: Barrier; + private readonly _readyToStartExtensionHost: Barrier; + private readonly _readyToRunExtensions: Barrier; + protected readonly _registry: ExtensionDescriptionRegistry; + private readonly _storage: ExtHostStorage; + private readonly _storagePath: IExtensionStoragePaths; + private readonly _activator: ExtensionsActivator; + private _extensionPathIndex: Promise> | null; + + private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver; }; + + private _started: boolean; + + private readonly _disposables: DisposableStore; + + constructor( + @IInstantiationService instaService: IInstantiationService, + @IHostUtils hostUtils: IHostUtils, + @IExtHostRpcService extHostContext: IExtHostRpcService, + @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace, + @IExtHostConfiguration extHostConfiguration: IExtHostConfiguration, + @ILogService logService: ILogService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtensionStoragePaths storagePath: IExtensionStoragePaths + ) { + this._hostUtils = hostUtils; + this._extHostContext = extHostContext; + this._initData = initData; + + this._extHostWorkspace = extHostWorkspace; + this._extHostConfiguration = extHostConfiguration; + this._logService = logService; + this._disposables = new DisposableStore(); + + this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); + this._mainThreadTelemetryProxy = this._extHostContext.getProxy(MainContext.MainThreadTelemetry); + this._mainThreadExtensionsProxy = this._extHostContext.getProxy(MainContext.MainThreadExtensionService); + + this._almostReadyToRunExtensions = new Barrier(); + this._readyToStartExtensionHost = new Barrier(); + this._readyToRunExtensions = new Barrier(); + this._registry = new ExtensionDescriptionRegistry(this._initData.extensions); + this._storage = new ExtHostStorage(this._extHostContext); + this._storagePath = storagePath; + + this._instaService = instaService.createChild(new ServiceCollection( + [IExtHostStorage, this._storage] + )); + + const hostExtensions = new Set(); + this._initData.hostExtensions.forEach((extensionId) => hostExtensions.add(ExtensionIdentifier.toKey(extensionId))); + + this._activator = new ExtensionsActivator(this._registry, this._initData.resolvedExtensions, this._initData.hostExtensions, { + onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { + this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); + }, + + actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { + if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { + const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); + await this._mainThreadExtensionsProxy.$activateExtension(extensionId, activationEvent); + return new HostExtension(); + } + const extensionDescription = this._registry.getExtensionDescription(extensionId)!; + return this._activateExtension(extensionDescription, reason); + } + }); + this._extensionPathIndex = null; + this._resolvers = Object.create(null); + this._started = false; + } + + public async initialize(): Promise { + try { + + await this._beforeAlmostReadyToRunExtensions(); + this._almostReadyToRunExtensions.open(); + + await this._extHostWorkspace.waitForInitializeCall(); + this._readyToStartExtensionHost.open(); + + if (this._initData.autoStart) { + this._startExtensionHost(); + } + } catch (err) { + errors.onUnexpectedError(err); + } + } + + protected abstract _beforeAlmostReadyToRunExtensions(): Promise; + + public async deactivateAll(): Promise { + let allPromises: Promise[] = []; + try { + const allExtensions = this._registry.getAllExtensionDescriptions(); + const allExtensionsIds = allExtensions.map(ext => ext.identifier); + const activatedExtensions = allExtensionsIds.filter(id => this.isActivated(id)); + + allPromises = activatedExtensions.map((extensionId) => { + return this._deactivate(extensionId); + }); + } catch (err) { + // TODO: write to log once we have one + } + await allPromises; + } + + public isActivated(extensionId: ExtensionIdentifier): boolean { + if (this._readyToRunExtensions.isOpen()) { + return this._activator.isActivated(extensionId); + } + return false; + } + + private _activateByEvent(activationEvent: string, startup: boolean): Promise { + const reason = new ExtensionActivatedByEvent(startup, activationEvent); + return this._activator.activateByEvent(activationEvent, reason); + } + + private _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { + return this._activator.activateById(extensionId, reason); + } + + public activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { + return this._activateById(extensionId, reason).then(() => { + const extension = this._activator.getActivatedExtension(extensionId); + if (extension.activationFailed) { + // activation failed => bubble up the error as the promise result + return Promise.reject(extension.activationFailedError); + } + return undefined; + }); + } + + public getExtensionRegistry(): Promise { + return this._readyToRunExtensions.wait().then(_ => this._registry); + } + + public getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined { + if (this._readyToRunExtensions.isOpen()) { + return this._activator.getActivatedExtension(extensionId).exports; + } else { + return null; + } + } + + // create trie to enable fast 'filename -> extension id' look up + public getExtensionPathIndex(): Promise> { + if (!this._extensionPathIndex) { + const tree = TernarySearchTree.forPaths(); + const extensions = this._registry.getAllExtensionDescriptions().map(ext => { + if (!ext.main) { + return undefined; + } + return this._hostUtils.realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext)); + }); + this._extensionPathIndex = Promise.all(extensions).then(() => tree); + } + return this._extensionPathIndex; + } + + private _deactivate(extensionId: ExtensionIdentifier): Promise { + let result = Promise.resolve(undefined); + + if (!this._readyToRunExtensions.isOpen()) { + return result; + } + + if (!this._activator.isActivated(extensionId)) { + return result; + } + + const extension = this._activator.getActivatedExtension(extensionId); + if (!extension) { + return result; + } + + // call deactivate if available + try { + if (typeof extension.module.deactivate === 'function') { + result = Promise.resolve(extension.module.deactivate()).then(undefined, (err) => { + // TODO: Do something with err if this is not the shutdown case + return Promise.resolve(undefined); + }); + } + } catch (err) { + // TODO: Do something with err if this is not the shutdown case + } + + // clean up subscriptions + try { + dispose(extension.subscriptions); + } catch (err) { + // TODO: Do something with err if this is not the shutdown case + } + + return result; + } + + // --- impl + + private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { + this._mainThreadExtensionsProxy.$onWillActivateExtension(extensionDescription.identifier); + return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => { + const activationTimes = activatedExtension.activationTimes; + const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); + this._mainThreadExtensionsProxy.$onDidActivateExtension(extensionDescription.identifier, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent); + this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes); + return activatedExtension; + }, (err) => { + this._logExtensionActivationTimes(extensionDescription, reason, 'failure'); + throw err; + }); + } + + private _logExtensionActivationTimes(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason, outcome: string, activationTimes?: ExtensionActivationTimes) { + const event = getTelemetryActivationEvent(extensionDescription, reason); + type ExtensionActivationTimesClassification = { + outcome: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + } & TelemetryActivationEventFragment & ExtensionActivationTimesFragment; + + type ExtensionActivationTimesEvent = { + outcome: string + } & ActivationTimesEvent & TelemetryActivationEvent; + + type ActivationTimesEvent = { + startup?: boolean; + codeLoadingTime?: number; + activateCallTime?: number; + activateResolvedTime?: number; + }; + + this._mainThreadTelemetryProxy.$publicLog2('extensionActivationTimes', { + ...event, + ...(activationTimes || {}), + outcome + }); + } + + private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { + const event = getTelemetryActivationEvent(extensionDescription, reason); + type ActivatePluginClassification = {} & TelemetryActivationEventFragment; + this._mainThreadTelemetryProxy.$publicLog2('activatePlugin', event); + if (!extensionDescription.main) { + // Treat the extension as being empty => NOT AN ERROR CASE + return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE)); + } + + this._logService.info(`ExtensionService#_doActivateExtension ${extensionDescription.identifier.value} ${JSON.stringify(reason)}`); + + const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); + return Promise.all([ + this._loadCommonJSModule(joinPath(extensionDescription.extensionLocation, extensionDescription.main), activationTimesBuilder), + this._loadExtensionContext(extensionDescription) + ]).then(values => { + return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); + }); + } + + protected abstract _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; + + private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { + + const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage); + const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); + + this._logService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`); + return Promise.all([ + globalState.whenReady, + workspaceState.whenReady, + this._storagePath.whenReady + ]).then(() => { + const that = this; + return Object.freeze({ + globalState, + workspaceState, + subscriptions: [], + get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, + get storagePath() { return that._storagePath.workspaceValue(extensionDescription); }, + get globalStoragePath() { return that._storagePath.globalValue(extensionDescription); }, + asAbsolutePath: (relativePath: string) => { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, + get logPath() { return path.join(that._initData.logsLocation.fsPath, extensionDescription.identifier.value); }, + executionContext: this._initData.remote.isRemote ? ExtensionExecutionContext.Remote : ExtensionExecutionContext.Local, + }); + }); + } + + private static _callActivate(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + // Make sure the extension's surface is not undefined + extensionModule = extensionModule || { + activate: undefined, + deactivate: undefined + }; + + return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => { + return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions); + }); + } + + private static _callActivateOptional(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + if (typeof extensionModule.activate === 'function') { + try { + activationTimesBuilder.activateCallStart(); + logService.trace(`ExtensionService#_callActivateOptional ${extensionId.value}`); + const scope = typeof global === 'object' ? global : self; // `global` is nodejs while `self` is for workers + const activateResult: Promise = extensionModule.activate.apply(scope, [context]); + activationTimesBuilder.activateCallStop(); + + activationTimesBuilder.activateResolveStart(); + return Promise.resolve(activateResult).then((value) => { + activationTimesBuilder.activateResolveStop(); + return value; + }); + } catch (err) { + return Promise.reject(err); + } + } else { + // No activate found => the module is the extension's exports + return Promise.resolve(extensionModule); + } + } + + // -- eager activation + + // Handle "eager" activation extensions + private _handleEagerExtensions(): Promise { + this._activateByEvent('*', true).then(undefined, (err) => { + console.error(err); + }); + + this._disposables.add(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added))); + const folders = this._extHostWorkspace.workspace ? this._extHostWorkspace.workspace.folders : []; + return this._handleWorkspaceContainsEagerExtensions(folders); + } + + private _handleWorkspaceContainsEagerExtensions(folders: ReadonlyArray): Promise { + if (folders.length === 0) { + return Promise.resolve(undefined); + } + + return Promise.all( + this._registry.getAllExtensionDescriptions().map((desc) => { + return this._handleWorkspaceContainsEagerExtension(folders, desc); + }) + ).then(() => { }); + } + + private _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray, desc: IExtensionDescription): Promise { + const activationEvents = desc.activationEvents; + if (!activationEvents) { + return Promise.resolve(undefined); + } + + if (this.isActivated(desc.identifier)) { + return Promise.resolve(undefined); + } + + const fileNames: string[] = []; + const globPatterns: string[] = []; + + for (const activationEvent of activationEvents) { + if (/^workspaceContains:/.test(activationEvent)) { + const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); + if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { + globPatterns.push(fileNameOrGlob); + } else { + fileNames.push(fileNameOrGlob); + } + } + } + + if (fileNames.length === 0 && globPatterns.length === 0) { + return Promise.resolve(undefined); + } + + const fileNamePromise = Promise.all(fileNames.map((fileName) => this._activateIfFileName(folders, desc.identifier, fileName))).then(() => { }); + const globPatternPromise = this._activateIfGlobPatterns(folders, desc.identifier, globPatterns); + + return Promise.all([fileNamePromise, globPatternPromise]).then(() => { }); + } + + private async _activateIfFileName(folders: ReadonlyArray, extensionId: ExtensionIdentifier, fileName: string): Promise { + + // find exact path + for (const { uri } of folders) { + if (await this._hostUtils.exists(path.join(URI.revive(uri).fsPath, fileName))) { + // the file was found + return ( + this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${fileName}`)) + .then(undefined, err => console.error(err)) + ); + } + } + + return undefined; + } + + private async _activateIfGlobPatterns(folders: ReadonlyArray, extensionId: ExtensionIdentifier, globPatterns: string[]): Promise { + this._logService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId.value}, entryPoint: workspaceContains`); + + if (globPatterns.length === 0) { + return Promise.resolve(undefined); + } + + const tokenSource = new CancellationTokenSource(); + const searchP = this._mainThreadWorkspaceProxy.$checkExists(folders.map(folder => folder.uri), globPatterns, tokenSource.token); + + const timer = setTimeout(async () => { + tokenSource.cancel(); + this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContainsTimeout:${globPatterns.join(',')}`)) + .then(undefined, err => console.error(err)); + }, AbstractExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT); + + let exists: boolean = false; + try { + exists = await searchP; + } catch (err) { + if (!errors.isPromiseCanceledError(err)) { + console.error(err); + } + } + + tokenSource.dispose(); + clearTimeout(timer); + + if (exists) { + // a file was found matching one of the glob patterns + return ( + this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${globPatterns.join(',')}`)) + .then(undefined, err => console.error(err)) + ); + } + + return Promise.resolve(undefined); + } + + private _handleExtensionTests(): Promise { + return this._doHandleExtensionTests().then(undefined, error => { + console.error(error); // ensure any error message makes it onto the console + + return Promise.reject(error); + }); + } + + private async _doHandleExtensionTests(): Promise { + const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment; + if (!(extensionDevelopmentLocationURI && extensionTestsLocationURI && extensionTestsLocationURI.scheme === Schemas.file)) { + return Promise.resolve(undefined); + } + + const extensionTestsPath = originalFSPath(extensionTestsLocationURI); + + // Require the test runner via node require from the provided path + let testRunner: ITestRunner | INewTestRunner | undefined; + let requireError: Error | undefined; + try { + testRunner = await this._loadCommonJSModule(URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false)); + } catch (error) { + requireError = error; + } + + // Execute the runner if it follows the old `run` spec + if (testRunner && typeof testRunner.run === 'function') { + return new Promise((c, e) => { + const oldTestRunnerCallback = (error: Error, failures: number | undefined) => { + if (error) { + e(error.toString()); + } else { + c(undefined); + } + + // after tests have run, we shutdown the host + this._gracefulExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */); + }; + + const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback); + + // Using the new API `run(): Promise` + if (runResult && runResult.then) { + runResult + .then(() => { + c(); + this._gracefulExit(0); + }) + .catch((err: Error) => { + e(err.toString()); + this._gracefulExit(1); + }); + } + }); + } + + // Otherwise make sure to shutdown anyway even in case of an error + else { + this._gracefulExit(1 /* ERROR */); + } + + return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath))); + } + + private _gracefulExit(code: number): void { + // to give the PH process a chance to flush any outstanding console + // messages to the main process, we delay the exit() by some time + setTimeout(() => { + // If extension tests are running, give the exit code to the renderer + if (this._initData.remote.isRemote && !!this._initData.environment.extensionTestsLocationURI) { + this._mainThreadExtensionsProxy.$onExtensionHostExit(code); + return; + } + + this._hostUtils.exit(code); + }, 500); + } + + private _startExtensionHost(): Promise { + if (this._started) { + throw new Error(`Extension host is already started!`); + } + this._started = true; + + return this._readyToStartExtensionHost.wait() + .then(() => this._readyToRunExtensions.open()) + .then(() => this._handleEagerExtensions()) + .then(() => this._handleExtensionTests()) + .then(() => { + this._logService.info(`eager extensions activated`); + }); + } + + // -- called by extensions + + public registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable { + this._resolvers[authorityPrefix] = resolver; + return toDisposable(() => { + delete this._resolvers[authorityPrefix]; + }); + } + + // -- called by main thread + + public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { + const authorityPlusIndex = remoteAuthority.indexOf('+'); + if (authorityPlusIndex === -1) { + throw new Error(`Not an authority that can be resolved!`); + } + const authorityPrefix = remoteAuthority.substr(0, authorityPlusIndex); + + await this._almostReadyToRunExtensions.wait(); + await this._activateByEvent(`onResolveRemoteAuthority:${authorityPrefix}`, false); + + const resolver = this._resolvers[authorityPrefix]; + if (!resolver) { + throw new Error(`No remote extension installed to resolve ${authorityPrefix}.`); + } + + try { + const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); + + // Split merged API result into separate authority/options + const authority: ResolvedAuthority = { + authority: remoteAuthority, + host: result.host, + port: result.port + }; + const options: ResolvedOptions = { + extensionHostEnv: result.extensionHostEnv + }; + + return { + type: 'ok', + value: { + authority, + options + } + }; + } catch (err) { + if (err instanceof RemoteAuthorityResolverError) { + return { + type: 'error', + error: { + code: err._code, + message: err._message, + detail: err._detail + } + }; + } + throw err; + } + } + + public $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise { + this._registry.keepOnly(enabledExtensionIds); + return this._startExtensionHost(); + } + + public $activateByEvent(activationEvent: string): Promise { + return ( + this._readyToRunExtensions.wait() + .then(_ => this._activateByEvent(activationEvent, false)) + ); + } + + public async $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + await this._readyToRunExtensions.wait(); + if (!this._registry.getExtensionDescription(extensionId)) { + // unknown extension => ignore + return false; + } + await this._activateById(extensionId, new ExtensionActivatedByEvent(false, activationEvent)); + return true; + } + + public async $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { + toAdd.forEach((extension) => (extension).extensionLocation = URI.revive(extension.extensionLocation)); + + const trie = await this.getExtensionPathIndex(); + + await Promise.all(toRemove.map(async (extensionId) => { + const extensionDescription = this._registry.getExtensionDescription(extensionId); + if (!extensionDescription) { + return; + } + const realpathValue = await this._hostUtils.realpath(extensionDescription.extensionLocation.fsPath); + trie.delete(URI.file(realpathValue).fsPath); + })); + + await Promise.all(toAdd.map(async (extensionDescription) => { + const realpathValue = await this._hostUtils.realpath(extensionDescription.extensionLocation.fsPath); + trie.set(URI.file(realpathValue).fsPath, extensionDescription); + })); + + this._registry.deltaExtensions(toAdd, toRemove); + return Promise.resolve(undefined); + } + + public async $test_latency(n: number): Promise { + return n; + } + + public async $test_up(b: VSBuffer): Promise { + return b.byteLength; + } + + public async $test_down(size: number): Promise { + let buff = VSBuffer.alloc(size); + let value = Math.random() % 256; + for (let i = 0; i < size; i++) { + buff.writeUInt8(value, i); + } + return buff; + } + + public abstract async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; +} + + +type TelemetryActivationEvent = { + id: string; + name: string; + extensionVersion: string; + publisherDisplayName: string; + activationEvents: string | null; + isBuiltin: boolean; + reason: string; +}; + +function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TelemetryActivationEvent { + const reasonStr = reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : + reason instanceof ExtensionActivatedByAPI ? 'api' : + ''; + const event = { + id: extensionDescription.identifier.value, + name: extensionDescription.name, + extensionVersion: extensionDescription.version, + publisherDisplayName: extensionDescription.publisher, + activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null, + isBuiltin: extensionDescription.isBuiltin, + reason: reasonStr + }; + + return event; +} + + +export const IExtHostExtensionService = createDecorator('IExtHostExtensionService'); + +export interface IExtHostExtensionService extends AbstractExtHostExtensionService { + _serviceBrand: any; + initialize(): Promise; + isActivated(extensionId: ExtensionIdentifier): boolean; + activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; + deactivateAll(): Promise; + getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined; + getExtensionRegistry(): Promise; + getExtensionPathIndex(): Promise>; + registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable; +} diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index 9f0519b636f..4b82d98426b 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -155,7 +155,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { private readonly _usedSchemes = new Set(); private readonly _watches = new Map(); - private _linkProviderRegistration: IDisposable; + private _linkProviderRegistration?: IDisposable; private _handlePool: number = 0; readonly fileSystem: vscode.FileSystem; diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index 85f48eff6a9..5ed5945f2f5 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -9,7 +9,7 @@ import { IRelativePattern, parse } from 'vs/base/common/glob'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import * as vscode from 'vscode'; -import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, ResourceFileEditDto, ResourceTextEditDto, MainThreadTextEditorsShape } from './extHost.protocol'; +import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, IResourceFileEditDto, IResourceTextEditDto, MainThreadTextEditorsShape } from './extHost.protocol'; import * as typeConverter from './extHostTypeConverters'; import { Disposable, WorkspaceEdit } from './extHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -169,7 +169,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ } // flatten all WorkspaceEdits collected via waitUntil-call // and apply them in one go. - const allEdits = new Array>(); + const allEdits = new Array>(); for (let edit of edits) { if (edit) { // sparse array let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); diff --git a/src/vs/workbench/api/common/extHostInitDataService.ts b/src/vs/workbench/api/common/extHostInitDataService.ts new file mode 100644 index 00000000000..2f0acd57a3c --- /dev/null +++ b/src/vs/workbench/api/common/extHostInitDataService.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInitData } from './extHost.protocol'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtHostInitDataService = createDecorator('IExtHostInitDataService'); + +export interface IExtHostInitDataService extends Readonly { + _serviceBrand: undefined; +} + diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 6c7065f29f2..0f0afd3b587 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -14,7 +14,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { asPromise } from 'vs/base/common/async'; -import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, SuggestDataDto, LinksListDto, ChainedCacheId, CodeLensListDto, CodeActionListDto, SignatureHelpDto, SignatureHelpContextDto } from './extHost.protocol'; +import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, IRawColorInfo, IMainContext, IdObject, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILanguageConfigurationDto, IWorkspaceSymbolDto, ISuggestResultDto, IWorkspaceSymbolsDto, ICodeActionDto, IDocumentFilterDto, IWorkspaceEditDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICodeLensDto, ISuggestDataDto, ILinksListDto, ChainedCacheId, ICodeLensListDto, ICodeActionListDto, ISignatureHelpDto, ISignatureHelpContextDto } 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'; @@ -111,7 +111,7 @@ class CodeLensAdapter { private readonly _provider: vscode.CodeLensProvider ) { } - provideCodeLenses(resource: URI, token: CancellationToken): Promise { + provideCodeLenses(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideCodeLenses(doc, token)).then(lenses => { @@ -124,7 +124,7 @@ class CodeLensAdapter { const disposables = new DisposableStore(); this._disposables.set(cacheId, disposables); - const result: CodeLensListDto = { + const result: ICodeLensListDto = { cacheId, lenses: [], }; @@ -141,7 +141,7 @@ class CodeLensAdapter { }); } - resolveCodeLens(symbol: CodeLensDto, token: CancellationToken): Promise { + resolveCodeLens(symbol: ICodeLensDto, token: CancellationToken): Promise { const lens = symbol.cacheId && this._cache.get(...symbol.cacheId); if (!lens) { @@ -308,7 +308,7 @@ class ReferenceAdapter { } } -export interface CustomCodeAction extends CodeActionDto { +export interface CustomCodeAction extends ICodeActionDto { _isSynthetic?: boolean; } @@ -327,7 +327,7 @@ 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.getDocument(resource); const ran = Selection.isISelection(rangeOrSelection) @@ -391,7 +391,7 @@ class CodeActionAdapter { } } - return { cacheId, actions }; + return { cacheId, actions }; }); } @@ -480,8 +480,8 @@ class NavigateTypeAdapter { this._provider = provider; } - provideWorkspaceSymbols(search: string, token: CancellationToken): Promise { - const result: WorkspaceSymbolsDto = IdObject.mixin({ symbols: [] }); + provideWorkspaceSymbols(search: string, token: CancellationToken): Promise { + const result: IWorkspaceSymbolsDto = IdObject.mixin({ symbols: [] }); return asPromise(() => this._provider.provideWorkspaceSymbols(search, token)).then(value => { if (isNonEmptyArray(value)) { for (const item of value) { @@ -506,7 +506,7 @@ class NavigateTypeAdapter { }); } - resolveWorkspaceSymbol(symbol: WorkspaceSymbolDto, token: CancellationToken): Promise { + resolveWorkspaceSymbol(symbol: IWorkspaceSymbolDto, token: CancellationToken): Promise { if (typeof this._provider.resolveWorkspaceSymbol !== 'function') { return Promise.resolve(symbol); @@ -543,7 +543,7 @@ 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 { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); @@ -556,10 +556,10 @@ class RenameAdapter { }, err => { const rejectReason = RenameAdapter._asMessage(err); if (rejectReason) { - return { rejectReason, edits: undefined! }; + return { rejectReason, edits: undefined! }; } else { // generic error - return Promise.reject(err); + return Promise.reject(err); } }); } @@ -633,7 +633,7 @@ class SuggestAdapter { this._provider = provider; } - provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { + provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); @@ -662,7 +662,7 @@ class SuggestAdapter { const wordRangeBeforePos = (doc.getWordRangeAtPosition(pos) as Range || new Range(pos, pos)) .with({ end: pos }); - const result: SuggestResultDto = { + const result: ISuggestResultDto = { x: pid, b: [], a: typeConvert.Range.from(wordRangeBeforePos), @@ -682,7 +682,7 @@ class SuggestAdapter { }); } - resolveCompletionItem(_resource: URI, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise { + resolveCompletionItem(_resource: URI, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise { if (typeof this._provider.resolveCompletionItem !== 'function') { return Promise.resolve(undefined); @@ -710,7 +710,7 @@ class SuggestAdapter { this._cache.delete(id); } - private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: ChainedCacheId): SuggestDataDto | undefined { + private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: ChainedCacheId): ISuggestDataDto | undefined { if (typeof item.label !== 'string' || item.label.length === 0) { console.warn('INVALID text edit -> must have at least a label'); return undefined; @@ -721,7 +721,7 @@ class SuggestAdapter { throw Error('DisposableStore is missing...'); } - const result: SuggestDataDto = { + const result: ISuggestDataDto = { // x: id, // @@ -778,7 +778,7 @@ class SignatureHelpAdapter { private readonly _provider: vscode.SignatureHelpProvider, ) { } - provideSignatureHelp(resource: URI, position: IPosition, context: SignatureHelpContextDto, token: CancellationToken): Promise { + provideSignatureHelp(resource: URI, position: IPosition, context: ISignatureHelpContextDto, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); const vscodeContext = this.reviveContext(context); @@ -792,7 +792,7 @@ class SignatureHelpAdapter { }); } - private reviveContext(context: SignatureHelpContextDto): vscode.SignatureHelpContext { + private reviveContext(context: ISignatureHelpContextDto): vscode.SignatureHelpContext { let activeSignatureHelp: vscode.SignatureHelp | undefined = undefined; if (context.activeSignatureHelp) { const revivedSignatureHelp = typeConvert.SignatureHelp.to(context.activeSignatureHelp); @@ -856,7 +856,7 @@ class LinkProviderAdapter { private readonly _provider: vscode.DocumentLinkProvider ) { } - provideLinks(resource: URI, token: CancellationToken): Promise { + provideLinks(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideDocumentLinks(doc, token)).then(links => { @@ -878,9 +878,9 @@ class LinkProviderAdapter { } else { // cache links for future resolving const pid = this._cache.add(links); - const result: LinksListDto = { links: [], id: pid }; + const result: ILinksListDto = { links: [], id: pid }; for (let i = 0; i < links.length; i++) { - const dto: LinkDto = typeConvert.DocumentLink.from(links[i]); + const dto: ILinkDto = typeConvert.DocumentLink.from(links[i]); dto.cacheId = [pid, i]; result.links.push(dto); } @@ -889,7 +889,7 @@ class LinkProviderAdapter { }); } - resolveLink(id: ChainedCacheId, token: CancellationToken): Promise { + resolveLink(id: ChainedCacheId, token: CancellationToken): Promise { if (typeof this._provider.resolveDocumentLink !== 'function') { return Promise.resolve(undefined); } @@ -1104,11 +1104,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { this._logService = logService; } - private _transformDocumentSelector(selector: vscode.DocumentSelector): Array { + private _transformDocumentSelector(selector: vscode.DocumentSelector): Array { return coalesce(asArray(selector).map(sel => this._doTransformDocumentSelector(sel))); } - private _doTransformDocumentSelector(selector: string | vscode.DocumentFilter): ISerializedDocumentFilter | undefined { + private _doTransformDocumentSelector(selector: string | vscode.DocumentFilter): IDocumentFilterDto | undefined { if (typeof selector === 'string') { return { $serialized: true, @@ -1216,11 +1216,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return result; } - $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { + $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined); } - $resolveCodeLens(handle: number, symbol: CodeLensDto, token: CancellationToken): Promise { + $resolveCodeLens(handle: number, symbol: ICodeLensDto, token: CancellationToken): Promise { return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined); } @@ -1315,7 +1315,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { } - $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), undefined); } @@ -1363,11 +1363,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideWorkspaceSymbols(handle: number, search: string, token: CancellationToken): Promise { + $provideWorkspaceSymbols(handle: number, search: string, token: CancellationToken): Promise { return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.provideWorkspaceSymbols(search, token), { symbols: [] }); } - $resolveWorkspaceSymbol(handle: number, symbol: WorkspaceSymbolDto, token: CancellationToken): Promise { + $resolveWorkspaceSymbol(handle: number, symbol: IWorkspaceSymbolDto, token: CancellationToken): Promise { return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.resolveWorkspaceSymbol(symbol, token), undefined); } @@ -1383,7 +1383,7 @@ 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), undefined); } @@ -1399,11 +1399,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { + $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { return this._withAdapter(handle, SuggestAdapter, adapter => adapter.provideCompletionItems(URI.revive(resource), position, context, token), undefined); } - $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise { + $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: ChainedCacheId, token: CancellationToken): Promise { return this._withAdapter(handle, SuggestAdapter, adapter => adapter.resolveCompletionItem(URI.revive(resource), position, id, token), undefined); } @@ -1414,7 +1414,7 @@ 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 | undefined = Array.isArray(metadataOrTriggerChars) + const metadata: ISignatureHelpProviderMetadataDto | undefined = Array.isArray(metadataOrTriggerChars) ? { triggerCharacters: metadataOrTriggerChars, retriggerCharacters: [] } : metadataOrTriggerChars; @@ -1423,7 +1423,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: SignatureHelpContextDto, token: CancellationToken): Promise { + $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: ISignatureHelpContextDto, token: CancellationToken): Promise { return this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(URI.revive(resource), position, context, token), undefined); } @@ -1439,11 +1439,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), undefined); } - $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise { + $resolveDocumentLink(handle: number, id: ChainedCacheId, token: CancellationToken): Promise { return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.resolveLink(id, token), undefined); } @@ -1505,14 +1505,14 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { // --- configuration - private static _serializeRegExp(regExp: RegExp): ISerializedRegExp { + private static _serializeRegExp(regExp: RegExp): IRegExpDto { return { pattern: regExp.source, flags: regExpFlags(regExp), }; } - private static _serializeIndentationRule(indentationRule: vscode.IndentationRule): ISerializedIndentationRule { + private static _serializeIndentationRule(indentationRule: vscode.IndentationRule): IIndentationRuleDto { return { decreaseIndentPattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.decreaseIndentPattern), increaseIndentPattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.increaseIndentPattern), @@ -1521,7 +1521,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { }; } - private static _serializeOnEnterRule(onEnterRule: vscode.OnEnterRule): ISerializedOnEnterRule { + private static _serializeOnEnterRule(onEnterRule: vscode.OnEnterRule): IOnEnterRuleDto { return { beforeText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.beforeText), afterText: onEnterRule.afterText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText) : undefined, @@ -1530,7 +1530,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { }; } - private static _serializeOnEnterRules(onEnterRules: vscode.OnEnterRule[]): ISerializedOnEnterRule[] { + private static _serializeOnEnterRules(onEnterRules: vscode.OnEnterRule[]): IOnEnterRuleDto[] { return onEnterRules.map(ExtHostLanguageFeatures._serializeOnEnterRule); } @@ -1550,7 +1550,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { } const handle = this._nextHandle(); - const serializedConfiguration: ISerializedLanguageConfiguration = { + const serializedConfiguration: ILanguageConfigurationDto = { comments: configuration.comments, brackets: configuration.brackets, wordPattern: configuration.wordPattern ? ExtHostLanguageFeatures._serializeRegExp(configuration.wordPattern) : undefined, diff --git a/src/vs/workbench/api/common/extHostLogService.ts b/src/vs/workbench/api/common/extHostLogService.ts deleted file mode 100644 index d67a19801d3..00000000000 --- a/src/vs/workbench/api/common/extHostLogService.ts +++ /dev/null @@ -1,34 +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 { join } from 'vs/base/common/path'; -import { ILogService, DelegatedLogService, LogLevel } from 'vs/platform/log/common/log'; -import { ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; -import { URI } from 'vs/base/common/uri'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; - -export class ExtHostLogService extends DelegatedLogService implements ILogService, ExtHostLogServiceShape { - - private _logsPath: string; - readonly logFile: URI; - - constructor( - delegate: ILogService, - logsPath: string, - ) { - super(delegate); - this._logsPath = logsPath; - this.logFile = URI.file(join(logsPath, `${ExtensionHostLogFileName}.log`)); - } - - $setLevel(level: LogLevel): void { - this.setLevel(level); - } - - getLogDirectory(extensionID: ExtensionIdentifier): string { - return join(this._logsPath, extensionID.value); - } -} diff --git a/src/vs/workbench/api/common/extHostMemento.ts b/src/vs/workbench/api/common/extHostMemento.ts index 4502bacd671..d0e68010a17 100644 --- a/src/vs/workbench/api/common/extHostMemento.ts +++ b/src/vs/workbench/api/common/extHostMemento.ts @@ -14,7 +14,7 @@ export class ExtensionMemento implements IExtensionMemento { private readonly _storage: ExtHostStorage; private readonly _init: Promise; - private _value: { [n: string]: any; }; + private _value?: { [n: string]: any; }; private readonly _storageListener: IDisposable; constructor(id: string, global: boolean, storage: ExtHostStorage) { @@ -41,7 +41,7 @@ export class ExtensionMemento implements IExtensionMemento { get(key: string): T | undefined; get(key: string, defaultValue: T): T; get(key: string, defaultValue?: T): T { - let value = this._value[key]; + let value = this._value![key]; if (typeof value === 'undefined') { value = defaultValue; } @@ -49,8 +49,8 @@ export class ExtensionMemento implements IExtensionMemento { } update(key: string, value: any): Promise { - this._value[key] = value; - return this._storage.setValue(this._shared, this._id, this._value); + this._value![key] = value; + return this._storage.setValue(this._shared, this._id, this._value!); } dispose(): void { diff --git a/src/vs/workbench/api/common/extHostOutput.ts b/src/vs/workbench/api/common/extHostOutput.ts index 09ae3c549fc..69fa1b36861 100644 --- a/src/vs/workbench/api/common/extHostOutput.ts +++ b/src/vs/workbench/api/common/extHostOutput.ts @@ -3,12 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MainContext, MainThreadOutputServiceShape, IMainContext, ExtHostOutputServiceShape } from './extHost.protocol'; +import { MainContext, MainThreadOutputServiceShape, ExtHostOutputServiceShape } from './extHost.protocol'; import * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel { @@ -27,6 +29,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements this._name = name; this._proxy = proxy; this._id = proxy.$register(this.name, log, file); + this._disposed = false; this._offset = 0; } @@ -105,38 +108,50 @@ class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel { } } -export interface IOutputChannelFactory { - createOutputChannel(name: string, logsLocation: URI, proxy: MainThreadOutputServiceShape): Promise; -} +export class LazyOutputChannel implements vscode.OutputChannel { -export const PushOutputChannelFactory = new class implements IOutputChannelFactory { - async createOutputChannel(name: string, _logsLocation: URI, proxy: MainThreadOutputServiceShape): Promise { - return new ExtHostPushOutputChannel(name, proxy); + constructor( + readonly name: string, + private readonly _channel: Promise + ) { } + + append(value: string): void { + this._channel.then(channel => channel.append(value)); } -}; + appendLine(value: string): void { + this._channel.then(channel => channel.appendLine(value)); + } + clear(): void { + this._channel.then(channel => channel.clear()); + } + show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { + this._channel.then(channel => channel.show(columnOrPreserveFocus, preserveFocus)); + } + hide(): void { + this._channel.then(channel => channel.hide()); + } + dispose(): void { + this._channel.then(channel => channel.dispose()); + } +} export class ExtHostOutputService implements ExtHostOutputServiceShape { - private readonly _factory: IOutputChannelFactory; - private readonly _logsLocation: URI; - private readonly _proxy: MainThreadOutputServiceShape; - private readonly _channels: Map = new Map(); - private _visibleChannelDisposable: IDisposable; + readonly _serviceBrand: any; - constructor(factory: IOutputChannelFactory, logsLocation: URI, mainContext: IMainContext) { - this._factory = factory; - this._logsLocation = logsLocation; - this._proxy = mainContext.getProxy(MainContext.MainThreadOutputService); + protected readonly _proxy: MainThreadOutputServiceShape; + protected readonly _channels: Map = new Map(); + protected readonly _visibleChannelDisposable = new MutableDisposable(); + + constructor(@IExtHostRpcService extHostRpc: IExtHostRpcService) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadOutputService); } $setVisibleChannel(channelId: string): void { - if (this._visibleChannelDisposable) { - this._visibleChannelDisposable = dispose(this._visibleChannelDisposable); - } if (channelId) { const channel = this._channels.get(channelId); if (channel) { - this._visibleChannelDisposable = channel.onDidAppend(() => channel.update()); + this._visibleChannelDisposable.value = channel.onDidAppend(() => channel.update()); } } } @@ -145,33 +160,8 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { name = name.trim(); if (!name) { throw new Error('illegal argument `name`. must not be falsy'); - } else { - const extHostOutputChannel = this._factory.createOutputChannel(name, this._logsLocation, this._proxy); - extHostOutputChannel.then(channel => channel._id.then(id => this._channels.set(id, channel))); - return { - get name(): string { - return name; - }, - append(value: string): void { - extHostOutputChannel.then(channel => channel.append(value)); - }, - appendLine(value: string): void { - extHostOutputChannel.then(channel => channel.appendLine(value)); - }, - clear(): void { - extHostOutputChannel.then(channel => channel.clear()); - }, - show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { - extHostOutputChannel.then(channel => channel.show(columnOrPreserveFocus, preserveFocus)); - }, - hide(): void { - extHostOutputChannel.then(channel => channel.hide()); - }, - dispose(): void { - extHostOutputChannel.then(channel => channel.dispose()); - } - }; } + return new ExtHostPushOutputChannel(name, this._proxy); } createOutputChannelFromLogFile(name: string, file: URI): vscode.OutputChannel { @@ -185,3 +175,6 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { return new ExtHostLogFileOutputChannel(name, file, this._proxy); } } + +export interface IExtHostOutputService extends ExtHostOutputService { } +export const IExtHostOutputService = createDecorator('IExtHostOutputService'); diff --git a/src/vs/workbench/api/node/extHostRequireInterceptor.ts b/src/vs/workbench/api/common/extHostRequireInterceptor.ts similarity index 61% rename from src/vs/workbench/api/node/extHostRequireInterceptor.ts rename to src/vs/workbench/api/common/extHostRequireInterceptor.ts index 789ece36253..46866f7db08 100644 --- a/src/vs/workbench/api/node/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/common/extHostRequireInterceptor.ts @@ -5,56 +5,64 @@ import { TernarySearchTree } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; -import { MainThreadKeytarShape, IEnvironment, MainThreadWindowShape, MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; +import { MainThreadTelemetryShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { endsWith } from 'vs/base/common/strings'; -import { IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl'; +import { IExtensionApiFactory } from 'vs/workbench/api/common/extHost.api.impl'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { platform } from 'vs/base/common/process'; interface LoadFunction { - (request: string, parent: { filename: string; }, isMain: any): any; + (request: string): any; } interface INodeModuleFactory { readonly nodeModuleName: string | string[]; - load(request: string, parent: { filename: string; }, isMain: any, original: LoadFunction): any; - alternaiveModuleName?(name: string): string | undefined; + load(request: string, parent: URI, original: LoadFunction): any; + alternativeModuleName?(name: string): string | undefined; } -export class NodeModuleRequireInterceptor { - public static INSTANCE = new NodeModuleRequireInterceptor(); +export abstract class RequireInterceptor { - private readonly _factories: Map; - private readonly _alternatives: ((moduleName: string) => string | undefined)[]; + protected readonly _factories: Map; + protected readonly _alternatives: ((moduleName: string) => string | undefined)[]; - constructor() { + constructor( + private _apiFactory: IExtensionApiFactory, + private _extensionRegistry: ExtensionDescriptionRegistry, + @IInstantiationService private readonly _instaService: IInstantiationService, + @IExtHostConfiguration private readonly _extHostConfiguration: IExtHostConfiguration, + @IExtHostExtensionService private readonly _extHostExtensionService: IExtHostExtensionService, + @IExtHostInitDataService private readonly _initData: IExtHostInitDataService + ) { this._factories = new Map(); this._alternatives = []; - this._installInterceptor(this._factories, this._alternatives); } - private _installInterceptor(factories: Map, alternatives: ((moduleName: string) => string | undefined)[]): void { - const node_module = require.__$__nodeRequire('module'); - const original = node_module._load; - node_module._load = function load(request: string, parent: { filename: string; }, isMain: any) { - for (let alternativeModuleName of alternatives) { - let alternative = alternativeModuleName(request); - if (alternative) { - request = alternative; - break; - } - } - if (!factories.has(request)) { - return original.apply(this, arguments); - } - return factories.get(request)!.load(request, parent, isMain, original); - }; + async install(): Promise { + + this._installInterceptor(); + + const configProvider = await this._extHostConfiguration.getConfigProvider(); + const extensionPaths = await this._extHostExtensionService.getExtensionPathIndex(); + + this.register(new VSCodeNodeModuleFactory(this._apiFactory, extensionPaths, this._extensionRegistry, configProvider)); + this.register(this._instaService.createInstance(KeytarNodeModuleFactory)); + if (this._initData.remote.isRemote) { + this.register(this._instaService.createInstance(OpenNodeModuleFactory, extensionPaths)); + } } + protected abstract _installInterceptor(): void; + public register(interceptor: INodeModuleFactory): void { if (Array.isArray(interceptor.nodeModuleName)) { for (let moduleName of interceptor.nodeModuleName) { @@ -63,19 +71,21 @@ export class NodeModuleRequireInterceptor { } else { this._factories.set(interceptor.nodeModuleName, interceptor); } - if (typeof interceptor.alternaiveModuleName === 'function') { + if (typeof interceptor.alternativeModuleName === 'function') { this._alternatives.push((moduleName) => { - return interceptor.alternaiveModuleName!(moduleName); + return interceptor.alternativeModuleName!(moduleName); }); } } } -export class VSCodeNodeModuleFactory implements INodeModuleFactory { +//#region --- vscode-module + +class VSCodeNodeModuleFactory implements INodeModuleFactory { public readonly nodeModuleName = 'vscode'; private readonly _extApiImpl = new Map(); - private _defaultApiImpl: typeof vscode; + private _defaultApiImpl?: typeof vscode; constructor( private readonly _apiFactory: IExtensionApiFactory, @@ -85,10 +95,10 @@ export class VSCodeNodeModuleFactory implements INodeModuleFactory { ) { } - public load(request: string, parent: { filename: string; }): any { + public load(_request: string, parent: URI): any { // get extension id from filename and api for extension - const ext = this._extensionPaths.findSubstr(URI.file(parent.filename).fsPath); + const ext = this._extensionPaths.findSubstr(parent.fsPath); if (ext) { let apiImpl = this._extApiImpl.get(ExtensionIdentifier.toKey(ext.identifier)); if (!apiImpl) { @@ -102,13 +112,18 @@ export class VSCodeNodeModuleFactory implements INodeModuleFactory { if (!this._defaultApiImpl) { let extensionPathsPretty = ''; this._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}`); + console.warn(`Could not identify extension for 'vscode' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`); this._defaultApiImpl = this._apiFactory(nullExtensionDescription, this._extensionRegistry, this._configProvider); } return this._defaultApiImpl; } } +//#endregion + + +//#region --- keytar-module + interface IKeytarModule { getPassword(service: string, account: string): Promise; setPassword(service: string, account: string, password: string): Promise; @@ -116,16 +131,23 @@ interface IKeytarModule { findPassword(service: string): Promise; } -export class KeytarNodeModuleFactory implements INodeModuleFactory { +class KeytarNodeModuleFactory implements INodeModuleFactory { public readonly nodeModuleName: string = 'keytar'; private alternativeNames: Set | undefined; private _impl: IKeytarModule; - constructor(mainThreadKeytar: MainThreadKeytarShape, environment: IEnvironment) { + constructor( + @IExtHostRpcService rpcService: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + + ) { + const { environment } = initData; + const mainThreadKeytar = rpcService.getProxy(MainContext.MainThreadKeytar); + if (environment.appRoot) { let appRoot = environment.appRoot.fsPath; - if (process.platform === 'win32') { + if (platform === 'win32') { appRoot = appRoot.replace(/\\/g, '/'); } if (appRoot[appRoot.length - 1] === '/') { @@ -151,11 +173,11 @@ export class KeytarNodeModuleFactory implements INodeModuleFactory { }; } - public load(request: string, parent: { filename: string; }): any { + public load(_request: string, _parent: URI): any { return this._impl; } - public alternaiveModuleName(name: string): string | undefined { + public alternativeModuleName(name: string): string | undefined { const length = name.length; // We need at least something like: `?/keytar` which requires // more than 7 characters. @@ -173,6 +195,11 @@ export class KeytarNodeModuleFactory implements INodeModuleFactory { } } +//#endregion + + +//#region --- opn/open-module + interface OpenOptions { wait: boolean; app: string | string[]; @@ -186,15 +213,23 @@ interface IOpenModule { (target: string, options?: OpenOptions): Thenable; } -export class OpenNodeModuleFactory implements INodeModuleFactory { +class OpenNodeModuleFactory implements INodeModuleFactory { public readonly nodeModuleName: string[] = ['open', 'opn']; private _extensionId: string | undefined; - private _original: IOriginalOpen; + private _original?: IOriginalOpen; private _impl: IOpenModule; + private _mainThreadTelemetry: MainThreadTelemetryShape; + + constructor( + private readonly _extensionPaths: TernarySearchTree, + @IExtHostRpcService rpcService: IExtHostRpcService, + ) { + + this._mainThreadTelemetry = rpcService.getProxy(MainContext.MainThreadTelemetry); + const mainThreadWindow = rpcService.getProxy(MainContext.MainThreadWindow); - constructor(mainThreadWindow: MainThreadWindowShape, private _mainThreadTelemerty: MainThreadTelemetryShape, private readonly _extensionPaths: TernarySearchTree) { this._impl = (target, options) => { const uri: URI = URI.parse(target); // If we have options use the original method. @@ -210,21 +245,21 @@ export class OpenNodeModuleFactory implements INodeModuleFactory { }; } - public load(request: string, parent: { filename: string; }, isMain: any, original: LoadFunction): any { + public load(request: string, parent: URI, original: LoadFunction): any { // get extension id from filename and api for extension - const extension = this._extensionPaths.findSubstr(URI.file(parent.filename).fsPath); + const extension = this._extensionPaths.findSubstr(parent.fsPath); if (extension) { this._extensionId = extension.identifier.value; this.sendShimmingTelemetry(); } - this._original = original(request, parent, isMain); + this._original = original(request); return this._impl; } private callOriginal(target: string, options: OpenOptions | undefined): Thenable { this.sendNoForwardTelemetry(); - return this._original(target, options); + return this._original!(target, options); } private sendShimmingTelemetry(): void { @@ -234,7 +269,7 @@ export class OpenNodeModuleFactory implements INodeModuleFactory { type ShimmingOpenClassification = { extension: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; - this._mainThreadTelemerty.$publicLog2<{ extension: string }, ShimmingOpenClassification>('shimming.open', { extension: this._extensionId }); + this._mainThreadTelemetry.$publicLog2<{ extension: string }, ShimmingOpenClassification>('shimming.open', { extension: this._extensionId }); } private sendNoForwardTelemetry(): void { @@ -244,6 +279,8 @@ export class OpenNodeModuleFactory implements INodeModuleFactory { type ShimmingOpenCallNoForwardClassification = { extension: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; - this._mainThreadTelemerty.$publicLog2<{ extension: string }, ShimmingOpenCallNoForwardClassification>('shimming.open.call.noForward', { extension: this._extensionId }); + this._mainThreadTelemetry.$publicLog2<{ extension: string }, ShimmingOpenCallNoForwardClassification>('shimming.open.call.noForward', { extension: this._extensionId }); } } + +//#endregion diff --git a/src/vs/workbench/api/common/extHostRpcService.ts b/src/vs/workbench/api/common/extHostRpcService.ts new file mode 100644 index 00000000000..5524e6f2ebd --- /dev/null +++ b/src/vs/workbench/api/common/extHostRpcService.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ProxyIdentifier, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtHostRpcService = createDecorator('IExtHostRpcService'); + +export interface IExtHostRpcService extends IRPCProtocol { + _serviceBrand: any; +} + +export class ExtHostRpcService implements IExtHostRpcService { + readonly _serviceBrand: any; + + readonly getProxy: (identifier: ProxyIdentifier) => T; + readonly set: (identifier: ProxyIdentifier, instance: R) => R; + readonly assertRegistered: (identifiers: ProxyIdentifier[]) => void; + + constructor(rpcProtocol: IRPCProtocol) { + this.getProxy = rpcProtocol.getProxy.bind(rpcProtocol); + this.set = rpcProtocol.set.bind(rpcProtocol); + this.assertRegistered = rpcProtocol.assertRegistered.bind(rpcProtocol); + + } + +} diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 311981d6f4d..00b3315f6c7 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -9,7 +9,7 @@ import { debounce } from 'vs/base/common/decorators'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { asPromise } from 'vs/base/common/async'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, CommandDto } from './extHost.protocol'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto } from './extHost.protocol'; import { sortedDiff } from 'vs/base/common/arrays'; import { comparePaths } from 'vs/base/common/comparers'; import * as vscode from 'vscode'; @@ -445,7 +445,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this._statusBarCommands = statusBarCommands; - const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal(c, this._statusBarDisposables.value!)) as CommandDto[]; + const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal(c, this._statusBarDisposables.value!)) as ICommandDto[]; this._proxy.$updateSourceControl(this.handle, { statusBarCommands: internal }); } diff --git a/src/vs/workbench/api/common/extHostSearch.ts b/src/vs/workbench/api/common/extHostSearch.ts new file mode 100644 index 00000000000..efc6ca49825 --- /dev/null +++ b/src/vs/workbench/api/common/extHostSearch.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable } from 'vs/base/common/lifecycle'; +import * as vscode from 'vscode'; +import { ExtHostSearchShape } from '../common/extHost.protocol'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export interface IExtHostSearch extends ExtHostSearchShape { + registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable; + registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider): IDisposable; +} + +export const IExtHostSearch = createDecorator('IExtHostSearch'); diff --git a/src/vs/workbench/api/common/extHostStorage.ts b/src/vs/workbench/api/common/extHostStorage.ts index 32df5a99877..efa42fa2142 100644 --- a/src/vs/workbench/api/common/extHostStorage.ts +++ b/src/vs/workbench/api/common/extHostStorage.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MainContext, MainThreadStorageShape, IMainContext, ExtHostStorageShape } from './extHost.protocol'; +import { MainContext, MainThreadStorageShape, ExtHostStorageShape } from './extHost.protocol'; import { Emitter } from 'vs/base/common/event'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export interface IStorageChangeEvent { shared: boolean; @@ -14,12 +16,14 @@ export interface IStorageChangeEvent { export class ExtHostStorage implements ExtHostStorageShape { + readonly _serviceBrand: any; + private _proxy: MainThreadStorageShape; private _onDidChangeStorage = new Emitter(); readonly onDidChangeStorage = this._onDidChangeStorage.event; - constructor(mainContext: IMainContext) { + constructor(mainContext: IExtHostRpcService) { this._proxy = mainContext.getProxy(MainContext.MainThreadStorage); } @@ -35,3 +39,6 @@ export class ExtHostStorage implements ExtHostStorageShape { this._onDidChangeStorage.fire({ shared, key, value }); } } + +export interface IExtHostStorage extends ExtHostStorage { } +export const IExtHostStorage = createDecorator('IExtHostStorage'); diff --git a/src/vs/workbench/api/common/extHostStoragePaths.ts b/src/vs/workbench/api/common/extHostStoragePaths.ts new file mode 100644 index 00000000000..0dcaba0f791 --- /dev/null +++ b/src/vs/workbench/api/common/extHostStoragePaths.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IExtensionStoragePaths = createDecorator('IExtensionStoragePaths'); + +export interface IExtensionStoragePaths { + _serviceBrand: any; + whenReady: Promise; + workspaceValue(extension: IExtensionDescription): string | undefined; + globalValue(extension: IExtensionDescription): string; +} diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts new file mode 100644 index 00000000000..e6233ae01a6 --- /dev/null +++ b/src/vs/workbench/api/common/extHostTask.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 { Event } from 'vs/base/common/event'; +import { ExtHostTaskShape } from 'vs/workbench/api/common/extHost.protocol'; +import * as vscode from 'vscode'; +import { TaskSystemInfoDTO } from '../common/shared/tasks'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export interface IExtHostTask extends ExtHostTaskShape { + + readonly _serviceBrand: any; + + taskExecutions: vscode.TaskExecution[]; + onDidStartTask: Event; + onDidEndTask: Event; + onDidStartTaskProcess: Event; + onDidEndTaskProcess: Event; + + registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable; + registerTaskSystem(scheme: string, info: TaskSystemInfoDTO): void; + fetchTasks(filter?: vscode.TaskFilter): Promise; + executeTask(extension: IExtensionDescription, task: vscode.Task): Promise; + terminateTask(execution: vscode.TaskExecution): Promise; +} + +export const IExtHostTask = createDecorator('IExtHostTask'); diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts new file mode 100644 index 00000000000..fcd283b560c --- /dev/null +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Event } from 'vs/base/common/event'; +import { ExtHostTerminalServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { + + _serviceBrand: any; + + activeTerminal: vscode.Terminal | undefined; + terminals: vscode.Terminal[]; + + onDidCloseTerminal: Event; + onDidOpenTerminal: Event; + onDidChangeActiveTerminal: Event; + onDidChangeTerminalDimensions: Event; + onDidWriteTerminalData: Event; + + createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal; + createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal; + createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal; + attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void; + getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string; +} + +export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index fa10d30014b..1e5404bbf2b 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -151,11 +151,11 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { private _proxy: MainThreadTextEditorsShape; private _id: string; - private _tabSize: number; - private _indentSize: number; - private _insertSpaces: boolean; - private _cursorStyle: TextEditorCursorStyle; - private _lineNumbers: TextEditorLineNumbersStyle; + private _tabSize!: number; + private _indentSize!: number; + private _insertSpaces!: boolean; + private _cursorStyle!: TextEditorCursorStyle; + private _lineNumbers!: TextEditorLineNumbersStyle; constructor(proxy: MainThreadTextEditorsShape, id: string, source: IResolvedTextEditorConfiguration) { this._proxy = proxy; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index bd3b82994d5..66c8e3ed7f7 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -13,7 +13,7 @@ import { ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.proto import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions } from 'vs/workbench/common/views'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { asPromise } from 'vs/base/common/async'; -import { TreeItemCollapsibleState, ThemeIcon, MarkdownString } from 'vs/workbench/api/common/extHostTypes'; +import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/common/extHostTypes'; import { isUndefinedOrNull, isString } from 'vs/base/common/types'; import { equals, coalesce } from 'vs/base/common/arrays'; import { ILogService } from 'vs/platform/log/common/log'; @@ -52,10 +52,21 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { private commands: ExtHostCommands, private logService: ILogService ) { + + function isTreeViewItemHandleArg(arg: any): boolean { + return arg && arg.$treeViewId && arg.$treeItemHandle; + } commands.registerArgumentProcessor({ processArgument: arg => { - if (arg && arg.$treeViewId && arg.$treeItemHandle) { + if (isTreeViewItemHandleArg(arg)) { return this.convertArgument(arg); + } else if (Array.isArray(arg) && (arg.length > 0)) { + return arg.map(item => { + if (isTreeViewItemHandleArg(item)) { + return this.convertArgument(item); + } + return item; + }); } return arg; } @@ -81,7 +92,10 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { get visible() { return treeView.visible; }, get onDidChangeVisibility() { return treeView.onDidChangeVisibility; }, get message() { return treeView.message; }, - set message(message: string | MarkdownString) { checkProposedApiEnabled(extension); treeView.message = message; }, + set message(message: string) { + checkProposedApiEnabled(extension); + treeView.message = message; + }, reveal: (element: T, options?: IRevealOptions): Promise => { return treeView.reveal(element, options); }, @@ -177,11 +191,12 @@ class ExtHostTreeView extends Disposable { private _onDidChangeData: Emitter> = this._register(new Emitter>()); private refreshPromise: Promise = Promise.resolve(); + private refreshQueue: Promise = Promise.resolve(); - constructor(private viewId: string, options: vscode.TreeViewOptions, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) { + constructor(private viewId: string, options: vscode.TreeViewOptions2, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) { super(); this.dataProvider = options.treeDataProvider; - this.proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll }); + this.proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany }); if (this.dataProvider.onDidChangeTreeData) { this._register(this.dataProvider.onDidChangeTreeData(element => this._onDidChangeData.fire({ message: false, element }))); } @@ -206,9 +221,11 @@ class ExtHostTreeView extends Disposable { return result; }, 200)(({ message, elements }) => { if (elements.length) { - const _promiseCallback = promiseCallback; - refreshingPromise = null; - this.refresh(elements).then(() => _promiseCallback()); + this.refreshQueue = this.refreshQueue.then(() => { + const _promiseCallback = promiseCallback; + refreshingPromise = null; + return this.refresh(elements).then(() => _promiseCallback()); + }); } if (message) { this.proxy.$setMessage(this.viewId, this._message); @@ -247,12 +264,12 @@ class ExtHostTreeView extends Disposable { .then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus, expand })), error => this.logService.error(error)); } - private _message: string | MarkdownString; - get message(): string | MarkdownString { + private _message: string = ''; + get message(): string { return this._message; } - set message(message: string | MarkdownString) { + set message(message: string) { this._message = message; this._onDidChangeData.fire({ message: true, element: false }); } @@ -565,9 +582,9 @@ class ExtHostTreeView extends Disposable { if (node) { if (node.children) { for (const child of node.children) { - const childEleement = this.elements.get(child.item.handle); - if (childEleement) { - this.clear(childEleement); + const childElement = this.elements.get(child.item.handle); + if (childElement) { + this.clear(childElement); } } } @@ -583,9 +600,9 @@ class ExtHostTreeView extends Disposable { if (node) { if (node.children) { for (const child of node.children) { - const childEleement = this.elements.get(child.item.handle); - if (childEleement) { - this.clear(childEleement); + const childElement = this.elements.get(child.item.handle); + if (childElement) { + this.clear(childElement); } } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 39b2cda9188..0fb461e8c97 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -19,7 +19,7 @@ import { IRange } from 'vs/editor/common/core/range'; import { ISelection } from 'vs/editor/common/core/selection'; import * as htmlContent from 'vs/base/common/htmlContent'; import * as languageSelector from 'vs/editor/common/modes/languageSelector'; -import { WorkspaceEditDto, ResourceTextEditDto, ResourceFileEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { IWorkspaceEditDto, IResourceTextEditDto, IResourceFileEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { MarkerSeverity, IRelatedInformation, IMarkerData, MarkerTag } from 'vs/platform/markers/common/markers'; import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -113,6 +113,15 @@ export namespace DiagnosticTag { } return undefined; } + export function to(value: MarkerTag): vscode.DiagnosticTag | undefined { + switch (value) { + case MarkerTag.Unnecessary: + return types.DiagnosticTag.Unnecessary; + case MarkerTag.Deprecated: + return types.DiagnosticTag.Deprecated; + } + return undefined; + } } export namespace Diagnostic { @@ -127,6 +136,15 @@ export namespace Diagnostic { tags: Array.isArray(value.tags) ? coalesce(value.tags.map(DiagnosticTag.from)) : undefined, }; } + + export function to(value: IMarkerData): vscode.Diagnostic { + const res = new types.Diagnostic(Range.to(value), value.message, DiagnosticSeverity.to(value.severity)); + res.source = value.source; + res.code = value.code; + res.relatedInformation = value.relatedInformation && value.relatedInformation.map(DiagnosticRelatedInformation.to); + res.tags = value.tags && coalesce(value.tags.map(DiagnosticTag.to)); + return res; + } } export namespace DiagnosticRelatedInformation { @@ -442,7 +460,7 @@ export namespace TextEdit { export function from(edit: vscode.TextEdit): modes.TextEdit { return { text: edit.newText, - eol: EndOfLine.from(edit.newEol), + eol: edit.newEol && EndOfLine.from(edit.newEol), range: Range.from(edit.range) }; } @@ -455,8 +473,8 @@ export namespace TextEdit { } export namespace WorkspaceEdit { - export function from(value: vscode.WorkspaceEdit, documents?: ExtHostDocumentsAndEditors): WorkspaceEditDto { - const result: WorkspaceEditDto = { + export function from(value: vscode.WorkspaceEdit, documents?: ExtHostDocumentsAndEditors): IWorkspaceEditDto { + const result: IWorkspaceEditDto = { edits: [] }; for (const entry of (value as types.WorkspaceEdit)._allEntries()) { @@ -464,28 +482,28 @@ export namespace WorkspaceEdit { if (Array.isArray(uriOrEdits)) { // text edits const doc = documents && uri ? documents.getDocument(uri) : undefined; - result.edits.push({ resource: uri, modelVersionId: doc && doc.version, edits: uriOrEdits.map(TextEdit.from) }); + result.edits.push({ resource: uri, modelVersionId: doc && doc.version, edits: uriOrEdits.map(TextEdit.from) }); } else { // resource edits - result.edits.push({ oldUri: uri, newUri: uriOrEdits, options: entry[2] }); + result.edits.push({ oldUri: uri, newUri: uriOrEdits, options: entry[2] }); } } return result; } - export function to(value: WorkspaceEditDto) { + export function to(value: IWorkspaceEditDto) { const result = new types.WorkspaceEdit(); for (const edit of value.edits) { - if (Array.isArray((edit).edits)) { + if (Array.isArray((edit).edits)) { result.set( - URI.revive((edit).resource), - (edit).edits.map(TextEdit.to) + URI.revive((edit).resource), + (edit).edits.map(TextEdit.to) ); } else { result.renameFile( - URI.revive((edit).oldUri!), - URI.revive((edit).newUri!), - (edit).options + URI.revive((edit).oldUri!), + URI.revive((edit).newUri!), + (edit).options ); } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 0a0ce14f00e..9084c617057 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -514,7 +514,7 @@ export class TextEdit { protected _range: Range; protected _newText: string | null; - protected _newEol: EndOfLine; + protected _newEol?: EndOfLine; get range(): Range { return this._range; @@ -538,11 +538,11 @@ export class TextEdit { this._newText = value; } - get newEol(): EndOfLine { + get newEol(): EndOfLine | undefined { return this._newEol; } - set newEol(value: EndOfLine) { + set newEol(value: EndOfLine | undefined) { if (value && typeof value !== 'number') { throw illegalArgument('newEol'); } @@ -550,7 +550,7 @@ export class TextEdit { } constructor(range: Range, newText: string | null) { - this.range = range; + this._range = range; this._newText = newText; } @@ -798,7 +798,7 @@ export class Location { } uri: URI; - range: Range; + range!: Range; constructor(uri: URI, rangeOrPosition: Range | Position) { this.uri = uri; @@ -861,10 +861,10 @@ export class Diagnostic { range: Range; message: string; - source: string; - code: string | number; severity: DiagnosticSeverity; - relatedInformation: DiagnosticRelatedInformation[]; + source?: string; + code?: string | number; + relatedInformation?: DiagnosticRelatedInformation[]; tags?: DiagnosticTag[]; constructor(range: Range, message: string, severity: DiagnosticSeverity = DiagnosticSeverity.Error) { @@ -989,7 +989,7 @@ export class SymbolInformation { } name: string; - location: Location; + location!: Location; kind: SymbolKind; containerName: string | undefined; @@ -1075,6 +1075,8 @@ export class CodeAction { kind?: CodeActionKind; + isPreferred?: boolean; + constructor(title: string, kind?: CodeActionKind) { this.title = title; this.kind = kind; @@ -1253,8 +1255,8 @@ export class SignatureInformation { export class SignatureHelp { signatures: SignatureInformation[]; - activeSignature: number; - activeParameter: number; + activeSignature: number = 0; + activeParameter: number = 0; constructor() { this.signatures = []; @@ -1310,19 +1312,19 @@ export enum CompletionItemKind { export class CompletionItem implements vscode.CompletionItem { label: string; - kind: CompletionItemKind | undefined; + kind?: CompletionItemKind; detail?: string; documentation?: string | MarkdownString; sortText?: string; filterText?: string; preselect?: boolean; - insertText: string | SnippetString; + insertText?: string | SnippetString; keepWhitespace?: boolean; - range: Range; + range?: Range; commitCharacters?: string[]; - textEdit: TextEdit; - additionalTextEdits: TextEdit[]; - command: vscode.Command; + textEdit?: TextEdit; + additionalTextEdits?: TextEdit[]; + command?: vscode.Command; constructor(label: string, kind?: CompletionItemKind) { this.label = label; @@ -1753,26 +1755,6 @@ export enum TaskScope { Workspace = 2 } -export class CustomExecution implements vscode.CustomExecution { - private _callback: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable; - - constructor(callback: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable) { - this._callback = callback; - } - - public computeId(): string { - return 'customExecution' + generateUuid(); - } - - public set callback(value: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable) { - this._callback = value; - } - - public get callback(): (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable { - return this._callback; - } -} - export class CustomExecution2 implements vscode.CustomExecution2 { private _callback: () => Thenable; constructor(callback: () => Thenable) { @@ -1804,7 +1786,7 @@ export class Task implements vscode.Task2 { private _definition: vscode.TaskDefinition; private _scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder | undefined; private _name: string; - private _execution: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2 | undefined; + private _execution: ProcessExecution | ShellExecution | CustomExecution2 | undefined; private _problemMatchers: string[]; private _hasDefinedMatchers: boolean; private _isBackground: boolean; @@ -1813,8 +1795,8 @@ export class Task implements vscode.Task2 { private _presentationOptions: vscode.TaskPresentationOptions; private _runOptions: vscode.RunOptions; - constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2, problemMatchers?: string | string[]); - constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2, problemMatchers?: string | string[]); + constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution2, problemMatchers?: string | string[]); + constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution2, problemMatchers?: string | string[]); constructor(definition: vscode.TaskDefinition, arg2: string | (vscode.TaskScope.Global | vscode.TaskScope.Workspace) | vscode.WorkspaceFolder, arg3: any, arg4?: any, arg5?: any, arg6?: any) { this.definition = definition; let problemMatchers: string | string[]; @@ -1879,7 +1861,7 @@ export class Task implements vscode.Task2 { type: Task.ShellType, id: this._execution.computeId() }; - } else if (this._execution instanceof CustomExecution) { + } else if (this._execution instanceof CustomExecution2) { this._definition = { type: Task.ExtensionCallbackType, id: this._execution.computeId() @@ -1926,18 +1908,18 @@ export class Task implements vscode.Task2 { } get execution(): ProcessExecution | ShellExecution | undefined { - return ((this._execution instanceof CustomExecution) || (this._execution instanceof CustomExecution2)) ? undefined : this._execution; + return (this._execution instanceof CustomExecution2) ? undefined : this._execution; } set execution(value: ProcessExecution | ShellExecution | undefined) { this.execution2 = value; } - get execution2(): ProcessExecution | ShellExecution | CustomExecution | CustomExecution2 | undefined { + get execution2(): ProcessExecution | ShellExecution | CustomExecution2 | undefined { return this._execution; } - set execution2(value: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2 | undefined) { + set execution2(value: ProcessExecution | ShellExecution | CustomExecution2 | undefined) { if (value === null) { value = undefined; } @@ -2184,6 +2166,24 @@ export class FunctionBreakpoint extends Breakpoint { } } +@es5ClassCompat +export class DataBreakpoint extends Breakpoint { + readonly label: string; + readonly dataId: string; + readonly canPersist: boolean; + + constructor(label: string, dataId: string, canPersist: boolean, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { + super(enabled, condition, hitCondition, logMessage); + if (!dataId) { + throw illegalArgument('dataId'); + } + this.label = label; + this.dataId = dataId; + this.canPersist = canPersist; + } +} + + @es5ClassCompat export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { readonly command: string; diff --git a/src/vs/workbench/api/common/extHostUriTransformerService.ts b/src/vs/workbench/api/common/extHostUriTransformerService.ts new file mode 100644 index 00000000000..e9acd9bc5d6 --- /dev/null +++ b/src/vs/workbench/api/common/extHostUriTransformerService.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IURITransformer } from 'vs/base/common/uriIpc'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI, UriComponents } from 'vs/base/common/uri'; + +export interface IURITransformerService extends IURITransformer { + _serviceBrand: any; +} + +export const IURITransformerService = createDecorator('IURITransformerService'); + +export class URITransformerService implements IURITransformerService { + _serviceBrand: any; + + transformIncoming: (uri: UriComponents) => UriComponents; + transformOutgoing: (uri: UriComponents) => UriComponents; + transformOutgoingURI: (uri: URI) => URI; + transformOutgoingScheme: (scheme: string) => string; + + constructor(delegate: IURITransformer | null) { + if (!delegate) { + this.transformIncoming = arg => arg; + this.transformOutgoing = arg => arg; + this.transformOutgoingURI = arg => arg; + this.transformOutgoingScheme = arg => arg; + } else { + this.transformIncoming = delegate.transformIncoming.bind(delegate); + this.transformOutgoing = delegate.transformOutgoing.bind(delegate); + this.transformOutgoingURI = delegate.transformOutgoingURI.bind(delegate); + this.transformOutgoingScheme = delegate.transformOutgoingScheme.bind(delegate); + } + } +} diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index aaa6a49734c..d0eea9511b9 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -5,15 +5,15 @@ import { Emitter, Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import * as modes from 'vs/editor/common/modes'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; +import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import * as vscode from 'vscode'; -import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState } from './extHost.protocol'; +import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; import { Disposable } from './extHostTypes'; -import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import * as modes from 'vs/editor/common/modes'; -import { WebviewInitData, toWebviewResource } from 'vs/workbench/api/common/shared/webview'; -import { generateUuid } from 'vs/base/common/uuid'; type IconPath = URI | { light: URI, dark: URI }; @@ -35,8 +35,8 @@ export class ExtHostWebview implements vscode.Webview { this._onMessageEmitter.dispose(); } - public toWebviewResource(resource: vscode.Uri): vscode.Uri { - return toWebviewResource(this._initData, this._handle, resource); + public asWebviewUri(resource: vscode.Uri): vscode.Uri { + return asWebviewUri(this._initData, this._handle, resource); } public get cspSource(): string { @@ -89,11 +89,12 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel { private readonly _options: vscode.WebviewPanelOptions; private readonly _webview: ExtHostWebview; - private _isDisposed: boolean = false; private _viewColumn: vscode.ViewColumn | undefined; private _visible: boolean = true; private _active: boolean = true; + _isDisposed: boolean = false; + readonly _onDisposeEmitter = new Emitter(); public readonly onDidDispose: Event = this._onDisposeEmitter.event; @@ -297,21 +298,21 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } } - public $onDidChangeWebviewPanelViewState( - handle: WebviewPanelHandle, - newState: WebviewPanelViewState - ): void { - const panel = this.getWebviewPanel(handle); - if (!panel) { - return; - } + public $onDidChangeWebviewPanelViewStates(newStates: WebviewPanelViewStateData): void { + for (const handle of Object.keys(newStates)) { + const panel = this.getWebviewPanel(handle); + if (!panel || panel._isDisposed) { + continue; + } - const viewColumn = typeConverters.ViewColumn.to(newState.position); - if (panel.active !== newState.active || panel.visible !== newState.visible || panel.viewColumn !== viewColumn) { - panel._setActive(newState.active); - panel._setVisible(newState.visible); - panel._setViewColumn(viewColumn); - panel._onDidChangeViewStateEmitter.fire({ webviewPanel: panel }); + const newState = newStates[handle]; + const viewColumn = typeConverters.ViewColumn.to(newState.position); + if (panel.active !== newState.active || panel.visible !== newState.visible || panel.viewColumn !== viewColumn) { + panel._setActive(newState.active); + panel._setVisible(newState.visible); + panel._setViewColumn(viewColumn); + panel._onDidChangeViewStateEmitter.fire({ webviewPanel: panel }); + } } } diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index a97d82b4935..d217d63b4d6 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -20,11 +20,14 @@ import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspa import { Range, RelativePattern } from 'vs/workbench/api/common/extHostTypes'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import * as vscode from 'vscode'; -import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, IMainContext, MainContext, IStaticWorkspaceData } from './extHost.protocol'; +import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, MainContext } from './extHost.protocol'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; import { Schemas } from 'vs/base/common/network'; import { withUndefinedAsNull } from 'vs/base/common/types'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export interface IExtHostWorkspaceProvider { getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise; @@ -153,6 +156,8 @@ class ExtHostWorkspaceImpl extends Workspace { export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspaceProvider { + readonly _serviceBrand: any; + private readonly _onDidChangeWorkspace = new Emitter(); readonly onDidChangeWorkspace: Event = this._onDidChangeWorkspace.event; @@ -169,21 +174,21 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = []; constructor( - mainContext: IMainContext, - logService: ILogService, - requestIdProvider: Counter, - data?: IStaticWorkspaceData + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @ILogService logService: ILogService, ) { this._logService = logService; - this._requestIdProvider = requestIdProvider; + this._requestIdProvider = new Counter(); this._barrier = new Barrier(); - this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace); - this._messageService = mainContext.getProxy(MainContext.MainThreadMessageService); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadWorkspace); + this._messageService = extHostRpc.getProxy(MainContext.MainThreadMessageService); + const data = initData.workspace; this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, [], data.configuration ? URI.revive(data.configuration) : null, !!data.isUntitled) : undefined; } - $initializeWorkspace(data: IWorkspaceData): void { + $initializeWorkspace(data: IWorkspaceData | null): void { this.$acceptWorkspaceData(data); this._barrier.open(); } @@ -289,7 +294,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac this._unconfirmedWorkspace = undefined; // show error to user - this._messageService.$showMessage(Severity.Error, localize('updateerror', "Extension '{0}' failed to update workspace folders: {1}", extName, error), { extension }, []); + this._messageService.$showMessage(Severity.Error, localize('updateerror', "Extension '{0}' failed to update workspace folders: {1}", extName, error.toString()), { extension }, []); }); } @@ -390,7 +395,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac } } - $acceptWorkspaceData(data: IWorkspaceData): void { + $acceptWorkspaceData(data: IWorkspaceData | null): void { const { workspace, added, removed } = ExtHostWorkspaceImpl.toExtHostWorkspace(data, this._confirmedWorkspace, this._unconfirmedWorkspace); @@ -546,3 +551,6 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac return this._proxy.$resolveProxy(url); } } + +export const IExtHostWorkspace = createDecorator('IExtHostWorkspace'); +export interface IExtHostWorkspace extends ExtHostWorkspace, ExtHostWorkspaceShape, IExtHostWorkspaceProvider { } diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 65395d5ed4c..cb6cddcb57d 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -12,7 +12,7 @@ import { IExtensionPointUser, ExtensionMessageCollector, ExtensionsRegistry } fr import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { MenuId, MenuRegistry, ILocalizedString, IMenuItem } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; namespace schema { @@ -329,7 +329,7 @@ namespace schema { }; } -let _commandRegistrations: IDisposable[] = []; +const _commandRegistrations = new DisposableStore(); export const commandsExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'commands', @@ -338,7 +338,7 @@ export const commandsExtensionPoint = ExtensionsRegistry.registerExtensionPoint< commandsExtensionPoint.setHandler(extensions => { - function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser, disposables: IDisposable[]) { + function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser) { if (!schema.isValidCommand(userFriendlyCommand, extension.collector)) { return; @@ -368,20 +368,20 @@ commandsExtensionPoint.setHandler(extensions => { precondition: ContextKeyExpr.deserialize(enablement), iconLocation: absoluteIcon }); - disposables.push(registration); + _commandRegistrations.add(registration); } // remove all previous command registrations - _commandRegistrations = dispose(_commandRegistrations); + _commandRegistrations.clear(); - for (let extension of extensions) { + for (const extension of extensions) { const { value } = extension; - if (Array.isArray(value)) { - for (let command of value) { - handleCommand(command, extension, _commandRegistrations); + if (Array.isArray(value)) { + for (const command of value) { + handleCommand(command, extension); } } else { - handleCommand(value, extension, _commandRegistrations); + handleCommand(value, extension); } } }); diff --git a/src/vs/workbench/api/common/shared/tasks.ts b/src/vs/workbench/api/common/shared/tasks.ts index a27f90777d3..b8fab0b5a02 100644 --- a/src/vs/workbench/api/common/shared/tasks.ts +++ b/src/vs/workbench/api/common/shared/tasks.ts @@ -66,10 +66,6 @@ export interface ShellExecutionDTO { options?: ShellExecutionOptionsDTO; } -export interface CustomExecutionDTO { - customExecution: 'customExecution'; -} - export interface CustomExecution2DTO { customExecution: 'customExecution2'; } @@ -88,7 +84,7 @@ export interface TaskHandleDTO { export interface TaskDTO { _id: string; name?: string; - execution: ProcessExecutionDTO | ShellExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined; + execution: ProcessExecutionDTO | ShellExecutionDTO | CustomExecution2DTO | undefined; definition: TaskDefinitionDTO; isBackground?: boolean; source: TaskSourceDTO; @@ -129,4 +125,4 @@ export interface TaskSystemInfoDTO { scheme: string; authority: string; platform: string; -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/common/shared/webview.ts b/src/vs/workbench/api/common/shared/webview.ts index 3969d74dbe1..9740cd21a6f 100644 --- a/src/vs/workbench/api/common/shared/webview.ts +++ b/src/vs/workbench/api/common/shared/webview.ts @@ -11,7 +11,7 @@ export interface WebviewInitData { readonly webviewCspSource: string; } -export function toWebviewResource( +export function asWebviewUri( initData: WebviewInitData, uuid: string, resource: vscode.Uri diff --git a/src/vs/workbench/api/node/extHost.services.ts b/src/vs/workbench/api/node/extHost.services.ts new file mode 100644 index 00000000000..a227d8a67b3 --- /dev/null +++ b/src/vs/workbench/api/node/extHost.services.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; +import { ExtHostOutputService2 } from 'vs/workbench/api/node/extHostOutputService'; +import { IExtHostWorkspace, ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostDecorations, ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; +import { IExtHostConfiguration, ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; +import { IExtHostCommands, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; +import { ExtHostTask } from 'vs/workbench/api/node/extHostTask'; +import { ExtHostDebugService } from 'vs/workbench/api/node/extHostDebugService'; +import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; +import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; +import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; +import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; + +// register singleton services +registerSingleton(ILogService, ExtHostLogService); +registerSingleton(IExtHostOutputService, ExtHostOutputService2); +registerSingleton(IExtHostWorkspace, ExtHostWorkspace); +registerSingleton(IExtHostDecorations, ExtHostDecorations); +registerSingleton(IExtHostConfiguration, ExtHostConfiguration); +registerSingleton(IExtHostCommands, ExtHostCommands); +registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); +registerSingleton(IExtHostTerminalService, ExtHostTerminalService); +registerSingleton(IExtHostTask, ExtHostTask); +registerSingleton(IExtHostDebugService, ExtHostDebugService); +registerSingleton(IExtHostSearch, ExtHostSearch); +registerSingleton(IExtensionStoragePaths, ExtensionStoragePaths); +registerSingleton(IExtHostExtensionService, ExtHostExtensionService); +registerSingleton(IExtHostStorage, ExtHostStorage); diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index 3b06ea4c064..f923a36d324 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -6,7 +6,7 @@ import { generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net'; import * as http from 'http'; import * as fs from 'fs'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; @@ -38,7 +38,7 @@ export class CLIServer { private _server: http.Server; private _ipcHandlePath: string | undefined; - constructor(private _commands: ExtHostCommands) { + constructor(@IExtHostCommands private _commands: IExtHostCommands) { this._server = http.createServer((req, res) => this.onRequest(req, res)); this.setup().catch(err => { console.error(err); @@ -179,4 +179,4 @@ export class CLIServer { fs.unlinkSync(this._ipcHandlePath); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index be353ca5bde..c82f788a370 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -11,33 +11,37 @@ import { asPromise } from 'vs/base/common/async'; import * as nls from 'vs/nls'; import { MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID, - IMainContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto + IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto } from 'vs/workbench/api/common/extHost.protocol'; import * as vscode from 'vscode'; -import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes'; +import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint } from 'vs/workbench/api/common/extHostTypes'; import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; -import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; -import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug'; import { hasChildProcesses, prepareCommand, runInExternalTerminal } from 'vs/workbench/contrib/debug/node/terminals'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; -import { ExtHostConfiguration, ExtHostConfigProvider } from '../common/extHostConfiguration'; +import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration'; import { convertToVSCPaths, convertToDAPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; -import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { SignService } from 'vs/platform/sign/node/signService'; import { ISignService } from 'vs/platform/sign/common/sign'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; -export class ExtHostDebugService implements ExtHostDebugServiceShape { +export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugServiceShape { + + readonly _serviceBrand: any; private _configProviderHandleCounter: number; private _configProviders: ConfigProviderTuple[]; @@ -86,13 +90,14 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { private _signService: ISignService; - constructor(mainContext: IMainContext, - private _workspaceService: IExtHostWorkspaceProvider, - private _extensionService: ExtHostExtensionService, - private _editorsService: ExtHostDocumentsAndEditors, - private _configurationService: ExtHostConfiguration, - private _terminalService: ExtHostTerminalService, - private _commandService: ExtHostCommands + constructor( + @IExtHostRpcService extHostRpcService: IExtHostRpcService, + @IExtHostWorkspace private _workspaceService: IExtHostWorkspace, + @IExtHostExtensionService private _extensionService: IExtHostExtensionService, + @IExtHostDocumentsAndEditors private _editorsService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration private _configurationService: IExtHostConfiguration, + @IExtHostTerminalService private _terminalService: IExtHostTerminalService, + @IExtHostCommands private _commandService: IExtHostCommands ) { this._configProviderHandleCounter = 0; this._configProviders = []; @@ -112,7 +117,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this._onDidChangeActiveDebugSession = new Emitter(); this._onDidReceiveDebugSessionCustomEvent = new Emitter(); - this._debugServiceProxy = mainContext.getProxy(MainContext.MainThreadDebugService); + this._debugServiceProxy = extHostRpcService.getProxy(MainContext.MainThreadDebugService); this._onDidChangeBreakpoints = new Emitter({ onFirstListenerAdd: () => { @@ -243,7 +248,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { // unregister with VS Code const ids = breakpoints.filter(bp => bp instanceof SourceBreakpoint).map(bp => bp.id); const fids = breakpoints.filter(bp => bp instanceof FunctionBreakpoint).map(bp => bp.id); - return this._debugServiceProxy.$unregisterBreakpoints(ids, fids); + const dids = breakpoints.filter(bp => bp instanceof DataBreakpoint).map(bp => bp.id); + return this._debugServiceProxy.$unregisterBreakpoints(ids, fids, dids); } public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession): Promise { @@ -344,7 +350,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { }).then(async needNewTerminal => { const configProvider = await this._configurationService.getConfigProvider(); - const shell = this._terminalService.getDefaultShell(configProvider); + const shell = this._terminalService.getDefaultShell(true, configProvider); if (needNewTerminal || !this._integratedTerminalInstance) { const options: vscode.TerminalOptions = { @@ -549,6 +555,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { let bp: vscode.Breakpoint; if (bpd.type === 'function') { bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); + } else if (bpd.type === 'data') { + bp = new DataBreakpoint(bpd.label, bpd.dataId, bpd.canPersist, bpd.enabled, bpd.hitCondition, bpd.condition, bpd.logMessage); } else { const uri = URI.revive(bpd.uri); bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); @@ -599,7 +607,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this.fireBreakpointChanges(a, r, c); } - public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { + public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined, token: CancellationToken): Promise { return asPromise(async () => { const provider = this.getConfigProviderByHandle(configProviderHandle); if (!provider) { @@ -609,7 +617,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { throw new Error('DebugConfigurationProvider has no method provideDebugConfigurations'); } const folder = await this.getFolder(folderUri); - return provider.provideDebugConfigurations(folder, CancellationToken.None); + return provider.provideDebugConfigurations(folder, token); }).then(debugConfigurations => { if (!debugConfigurations) { throw new Error('nothing returned from DebugConfigurationProvider.provideDebugConfigurations'); @@ -618,7 +626,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { }); } - public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): Promise { + public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration, token: CancellationToken): Promise { return asPromise(async () => { const provider = this.getConfigProviderByHandle(configProviderHandle); if (!provider) { @@ -628,7 +636,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { throw new Error('DebugConfigurationProvider has no method resolveDebugConfiguration'); } const folder = await this.getFolder(folderUri); - return provider.resolveDebugConfiguration(folder, debugConfiguration, CancellationToken.None); + return provider.resolveDebugConfiguration(folder, debugConfiguration, token); }); } diff --git a/src/vs/workbench/api/node/extHostDownloadService.ts b/src/vs/workbench/api/node/extHostDownloadService.ts index c86a8ecd8f9..9d81fe712ed 100644 --- a/src/vs/workbench/api/node/extHostDownloadService.ts +++ b/src/vs/workbench/api/node/extHostDownloadService.ts @@ -6,23 +6,26 @@ import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; import { generateUuid } from 'vs/base/common/uuid'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { Disposable } from 'vs/base/common/lifecycle'; -import { MainThreadDownloadServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { URI } from 'vs/base/common/uri'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export class ExtHostDownloadService extends Disposable { constructor( - proxy: MainThreadDownloadServiceShape, - commands: ExtHostCommands + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostCommands commands: IExtHostCommands ) { super(); + + const proxy = extHostRpc.getProxy(MainContext.MainThreadDownloadService); + commands.registerCommand(false, '_workbench.downloadResource', async (resource: URI): Promise => { const location = URI.file(join(tmpdir(), generateUuid())); await proxy.$download(resource, location); return location; }); } - -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 18e6bf78416..8e96e6738ec 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -3,791 +3,107 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import * as path from 'vs/base/common/path'; -import { originalFSPath } from 'vs/base/common/resources'; -import { Barrier } from 'vs/base/common/async'; -import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { TernarySearchTree } from 'vs/base/common/map'; -import { URI } from 'vs/base/common/uri'; -import { ILogService } from 'vs/platform/log/common/log'; -import { createApiFactory, IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl'; -import { NodeModuleRequireInterceptor, VSCodeNodeModuleFactory, KeytarNodeModuleFactory, OpenNodeModuleFactory } from 'vs/workbench/api/node/extHostRequireInterceptor'; -import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IResolveAuthorityResult } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; -import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; -import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; -import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; -import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl'; +import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; +import { MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator'; 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 * as vscode from 'vscode'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadService'; +import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; +import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; -import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; -import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/workbench/api/common/extHostTypes'; -import { IURITransformer } from 'vs/base/common/uriIpc'; -import { ResolvedAuthority, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver'; -interface ITestRunner { - /** Old test runner API, as exported from `vscode/lib/testrunner` */ - run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; -} - -interface INewTestRunner { - /** New test runner API, as explained in the extension test doc */ - run(): Promise; -} - -export interface IHostUtils { - exit(code?: number): void; - exists(path: string): Promise; - realpath(path: string): Promise; -} - -type TelemetryActivationEventFragment = { - id: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; - name: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; - extensionVersion: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; - publisherDisplayName: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - activationEvents: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - isBuiltin: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - reason: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; -}; - -export class ExtHostExtensionService implements ExtHostExtensionServiceShape { - - private static readonly WORKSPACE_CONTAINS_TIMEOUT = 7000; - - private readonly _hostUtils: IHostUtils; - private readonly _initData: IInitData; - private readonly _extHostContext: IMainContext; - private readonly _extHostWorkspace: ExtHostWorkspace; - private readonly _extHostConfiguration: ExtHostConfiguration; - private readonly _environment: IEnvironment; - private readonly _extHostLogService: ExtHostLogService; - - private readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape; - private readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; - private readonly _mainThreadExtensionsProxy: MainThreadExtensionServiceShape; - - private readonly _almostReadyToRunExtensions: Barrier; - private readonly _readyToStartExtensionHost: Barrier; - private readonly _readyToRunExtensions: Barrier; - private readonly _registry: ExtensionDescriptionRegistry; - private readonly _storage: ExtHostStorage; - private readonly _storagePath: ExtensionStoragePaths; - private readonly _activator: ExtensionsActivator; - private _extensionPathIndex: Promise> | null; - private readonly _extensionApiFactory: IExtensionApiFactory; - - private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver; }; - - private _started: boolean; - - private readonly _disposables: DisposableStore; - - constructor( - hostUtils: IHostUtils, - initData: IInitData, - extHostContext: IMainContext, - extHostWorkspace: ExtHostWorkspace, - extHostConfiguration: ExtHostConfiguration, - environment: IEnvironment, - extHostLogService: ExtHostLogService, - uriTransformer: IURITransformer | null - ) { - this._hostUtils = hostUtils; - this._initData = initData; - this._extHostContext = extHostContext; - this._extHostWorkspace = extHostWorkspace; - this._extHostConfiguration = extHostConfiguration; - this._environment = environment; - this._extHostLogService = extHostLogService; - this._disposables = new DisposableStore(); - - this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace); - this._mainThreadTelemetryProxy = this._extHostContext.getProxy(MainContext.MainThreadTelemetry); - this._mainThreadExtensionsProxy = this._extHostContext.getProxy(MainContext.MainThreadExtensionService); - - this._almostReadyToRunExtensions = new Barrier(); - this._readyToStartExtensionHost = new Barrier(); - this._readyToRunExtensions = new Barrier(); - this._registry = new ExtensionDescriptionRegistry(initData.extensions); - this._storage = new ExtHostStorage(this._extHostContext); - this._storagePath = new ExtensionStoragePaths(withNullAsUndefined(initData.workspace), initData.environment); - - const hostExtensions = new Set(); - initData.hostExtensions.forEach((extensionId) => hostExtensions.add(ExtensionIdentifier.toKey(extensionId))); - - this._activator = new ExtensionsActivator(this._registry, initData.resolvedExtensions, initData.hostExtensions, { - onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { - this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); - }, - - actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { - if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { - const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); - await this._mainThreadExtensionsProxy.$activateExtension(extensionId, activationEvent); - return new HostExtension(); - } - const extensionDescription = this._registry.getExtensionDescription(extensionId)!; - return this._activateExtension(extensionDescription, reason); - } - }); - this._extensionPathIndex = null; - - // 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, - uriTransformer - ); - - this._resolvers = Object.create(null); - - this._started = false; - - this._initialize(); - - if (this._initData.autoStart) { - this._startExtensionHost(); - } - } - - private async _initialize(): Promise { - try { - const configProvider = await this._extHostConfiguration.getConfigProvider(); - const extensionPaths = await this.getExtensionPathIndex(); - NodeModuleRequireInterceptor.INSTANCE.register(new VSCodeNodeModuleFactory(this._extensionApiFactory, extensionPaths, this._registry, configProvider)); - NodeModuleRequireInterceptor.INSTANCE.register(new KeytarNodeModuleFactory(this._extHostContext.getProxy(MainContext.MainThreadKeytar), this._environment)); - if (this._initData.remote.isRemote) { - NodeModuleRequireInterceptor.INSTANCE.register(new OpenNodeModuleFactory( - this._extHostContext.getProxy(MainContext.MainThreadWindow), - this._extHostContext.getProxy(MainContext.MainThreadTelemetry), - extensionPaths - )); - } - - // Do this when extension service exists, but extensions are not being activated yet. - await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._extHostLogService, this._mainThreadTelemetryProxy); - this._almostReadyToRunExtensions.open(); - - await this._extHostWorkspace.waitForInitializeCall(); - this._readyToStartExtensionHost.open(); - } catch (err) { - errors.onUnexpectedError(err); - } - } - - public async deactivateAll(): Promise { - let allPromises: Promise[] = []; - try { - const allExtensions = this._registry.getAllExtensionDescriptions(); - const allExtensionsIds = allExtensions.map(ext => ext.identifier); - const activatedExtensions = allExtensionsIds.filter(id => this.isActivated(id)); - - allPromises = activatedExtensions.map((extensionId) => { - return this._deactivate(extensionId); - }); - } catch (err) { - // TODO: write to log once we have one - } - await allPromises; - } - - public isActivated(extensionId: ExtensionIdentifier): boolean { - if (this._readyToRunExtensions.isOpen()) { - return this._activator.isActivated(extensionId); - } - return false; - } - - private _activateByEvent(activationEvent: string, startup: boolean): Promise { - const reason = new ExtensionActivatedByEvent(startup, activationEvent); - return this._activator.activateByEvent(activationEvent, reason); - } - - private _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { - return this._activator.activateById(extensionId, reason); - } - - public activateByIdWithErrors(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { - return this._activateById(extensionId, reason).then(() => { - const extension = this._activator.getActivatedExtension(extensionId); - if (extension.activationFailed) { - // activation failed => bubble up the error as the promise result - return Promise.reject(extension.activationFailedError); - } - return undefined; - }); - } - - public getExtensionRegistry(): Promise { - return this._readyToRunExtensions.wait().then(_ => this._registry); - } - - public getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined { - if (this._readyToRunExtensions.isOpen()) { - return this._activator.getActivatedExtension(extensionId).exports; - } else { - return null; - } - } - - // create trie to enable fast 'filename -> extension id' look up - public getExtensionPathIndex(): Promise> { - if (!this._extensionPathIndex) { - const tree = TernarySearchTree.forPaths(); - const extensions = this._registry.getAllExtensionDescriptions().map(ext => { - if (!ext.main) { - return undefined; - } - return this._hostUtils.realpath(ext.extensionLocation.fsPath).then(value => tree.set(URI.file(value).fsPath, ext)); - }); - this._extensionPathIndex = Promise.all(extensions).then(() => tree); - } - return this._extensionPathIndex; - } - - private _deactivate(extensionId: ExtensionIdentifier): Promise { - let result = Promise.resolve(undefined); - - if (!this._readyToRunExtensions.isOpen()) { - return result; - } - - if (!this._activator.isActivated(extensionId)) { - return result; - } - - const extension = this._activator.getActivatedExtension(extensionId); - if (!extension) { - return result; - } - - // call deactivate if available - try { - if (typeof extension.module.deactivate === 'function') { - result = Promise.resolve(extension.module.deactivate()).then(undefined, (err) => { - // TODO: Do something with err if this is not the shutdown case - return Promise.resolve(undefined); - }); - } - } catch (err) { - // TODO: Do something with err if this is not the shutdown case - } - - // clean up subscriptions - try { - dispose(extension.subscriptions); - } catch (err) { - // TODO: Do something with err if this is not the shutdown case - } - - return result; - } - - // --- impl - - private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { - this._mainThreadExtensionsProxy.$onWillActivateExtension(extensionDescription.identifier); - return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => { - const activationTimes = activatedExtension.activationTimes; - const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); - this._mainThreadExtensionsProxy.$onDidActivateExtension(extensionDescription.identifier, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent); - this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes); - return activatedExtension; - }, (err) => { - this._logExtensionActivationTimes(extensionDescription, reason, 'failure'); - throw err; - }); - } - - private _logExtensionActivationTimes(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason, outcome: string, activationTimes?: ExtensionActivationTimes) { - const event = getTelemetryActivationEvent(extensionDescription, reason); - type ExtensionActivationTimesClassification = { - outcome: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - } & TelemetryActivationEventFragment & ExtensionActivationTimesFragment; - - type ExtensionActivationTimesEvent = { - outcome: string - } & ActivationTimesEvent & TelemetryActivationEvent; - - type ActivationTimesEvent = { - startup?: boolean; - codeLoadingTime?: number; - activateCallTime?: number; - activateResolvedTime?: number; - }; - - this._mainThreadTelemetryProxy.$publicLog2('extensionActivationTimes', { - ...event, - ...(activationTimes || {}), - outcome - }); - } - - private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { - const event = getTelemetryActivationEvent(extensionDescription, reason); - type ActivatePluginClassification = {} & TelemetryActivationEventFragment; - this._mainThreadTelemetryProxy.$publicLog2('activatePlugin', event); - if (!extensionDescription.main) { - // Treat the extension as being empty => NOT AN ERROR CASE - return Promise.resolve(new EmptyExtension(ExtensionActivationTimes.NONE)); - } - - this._extHostLogService.info(`ExtensionService#_doActivateExtension ${extensionDescription.identifier.value} ${JSON.stringify(reason)}`); - - const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup); - return Promise.all([ - loadCommonJSModule(this._extHostLogService, extensionDescription.main, activationTimesBuilder), - this._loadExtensionContext(extensionDescription) - ]).then(values => { - return ExtHostExtensionService._callActivate(this._extHostLogService, extensionDescription.identifier, values[0], values[1], activationTimesBuilder); - }); - } - - private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { - - const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage); - const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); - - this._extHostLogService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`); - return Promise.all([ - globalState.whenReady, - workspaceState.whenReady, - this._storagePath.whenReady - ]).then(() => { - const that = this; - return Object.freeze({ - globalState, - workspaceState, - subscriptions: [], - get extensionPath() { return extensionDescription.extensionLocation.fsPath; }, - storagePath: this._storagePath.workspaceValue(extensionDescription), - globalStoragePath: this._storagePath.globalValue(extensionDescription), - asAbsolutePath: (relativePath: string) => { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, - logPath: that._extHostLogService.getLogDirectory(extensionDescription.identifier), - executionContext: this._initData.remote.isRemote ? ExtensionExecutionContext.Remote : ExtensionExecutionContext.Local, - }); - }); - } - - private static _callActivate(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { - // Make sure the extension's surface is not undefined - extensionModule = extensionModule || { - activate: undefined, - deactivate: undefined - }; - - return this._callActivateOptional(logService, extensionId, extensionModule, context, activationTimesBuilder).then((extensionExports) => { - return new ActivatedExtension(false, null, activationTimesBuilder.build(), extensionModule, extensionExports, context.subscriptions); - }); - } - - private static _callActivateOptional(logService: ILogService, extensionId: ExtensionIdentifier, extensionModule: IExtensionModule, context: IExtensionContext, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { - if (typeof extensionModule.activate === 'function') { - try { - activationTimesBuilder.activateCallStart(); - logService.trace(`ExtensionService#_callActivateOptional ${extensionId.value}`); - const activateResult: Promise = extensionModule.activate.apply(global, [context]); - activationTimesBuilder.activateCallStop(); - - activationTimesBuilder.activateResolveStart(); - return Promise.resolve(activateResult).then((value) => { - activationTimesBuilder.activateResolveStop(); - return value; - }); - } catch (err) { - return Promise.reject(err); - } - } else { - // No activate found => the module is the extension's exports - return Promise.resolve(extensionModule); - } - } - - // -- eager activation - - // Handle "eager" activation extensions - private _handleEagerExtensions(): Promise { - this._activateByEvent('*', true).then(undefined, (err) => { - console.error(err); - }); - - this._disposables.add(this._extHostWorkspace.onDidChangeWorkspace((e) => this._handleWorkspaceContainsEagerExtensions(e.added))); - const folders = this._extHostWorkspace.workspace ? this._extHostWorkspace.workspace.folders : []; - return this._handleWorkspaceContainsEagerExtensions(folders); - } - - private _handleWorkspaceContainsEagerExtensions(folders: ReadonlyArray): Promise { - if (folders.length === 0) { - return Promise.resolve(undefined); - } - - return Promise.all( - this._registry.getAllExtensionDescriptions().map((desc) => { - return this._handleWorkspaceContainsEagerExtension(folders, desc); - }) - ).then(() => { }); - } - - private _handleWorkspaceContainsEagerExtension(folders: ReadonlyArray, desc: IExtensionDescription): Promise { - const activationEvents = desc.activationEvents; - if (!activationEvents) { - return Promise.resolve(undefined); - } - - if (this.isActivated(desc.identifier)) { - return Promise.resolve(undefined); - } - - const fileNames: string[] = []; - const globPatterns: string[] = []; - - for (const activationEvent of activationEvents) { - if (/^workspaceContains:/.test(activationEvent)) { - const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); - if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { - globPatterns.push(fileNameOrGlob); - } else { - fileNames.push(fileNameOrGlob); +class NodeModuleRequireInterceptor extends RequireInterceptor { + + protected _installInterceptor(): void { + const that = this; + const node_module = require.__$__nodeRequire('module'); + const original = node_module._load; + node_module._load = function load(request: string, parent: { filename: string; }, isMain: any) { + for (let alternativeModuleName of that._alternatives) { + let alternative = alternativeModuleName(request); + if (alternative) { + request = alternative; + break; } } - } - - if (fileNames.length === 0 && globPatterns.length === 0) { - return Promise.resolve(undefined); - } - - const fileNamePromise = Promise.all(fileNames.map((fileName) => this._activateIfFileName(folders, desc.identifier, fileName))).then(() => { }); - const globPatternPromise = this._activateIfGlobPatterns(folders, desc.identifier, globPatterns); - - return Promise.all([fileNamePromise, globPatternPromise]).then(() => { }); - } - - private async _activateIfFileName(folders: ReadonlyArray, extensionId: ExtensionIdentifier, fileName: string): Promise { - - // find exact path - for (const { uri } of folders) { - if (await this._hostUtils.exists(path.join(URI.revive(uri).fsPath, fileName))) { - // the file was found - return ( - this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${fileName}`)) - .then(undefined, err => console.error(err)) - ); + if (!that._factories.has(request)) { + return original.apply(this, arguments); } - } - - return undefined; - } - - private async _activateIfGlobPatterns(folders: ReadonlyArray, extensionId: ExtensionIdentifier, globPatterns: string[]): Promise { - this._extHostLogService.trace(`extensionHostMain#activateIfGlobPatterns: fileSearch, extension: ${extensionId.value}, entryPoint: workspaceContains`); - - if (globPatterns.length === 0) { - return Promise.resolve(undefined); - } - - const tokenSource = new CancellationTokenSource(); - const searchP = this._mainThreadWorkspaceProxy.$checkExists(folders.map(folder => folder.uri), globPatterns, tokenSource.token); - - const timer = setTimeout(async () => { - tokenSource.cancel(); - this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContainsTimeout:${globPatterns.join(',')}`)) - .then(undefined, err => console.error(err)); - }, ExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT); - - let exists: boolean = false; - try { - exists = await searchP; - } catch (err) { - if (!errors.isPromiseCanceledError(err)) { - console.error(err); - } - } - - tokenSource.dispose(); - clearTimeout(timer); - - if (exists) { - // a file was found matching one of the glob patterns - return ( - this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${globPatterns.join(',')}`)) - .then(undefined, err => console.error(err)) + return that._factories.get(request)!.load( + request, + URI.file(parent.filename), + request => original.apply(this, [request, parent, isMain]) ); + }; + } +} + +export class ExtHostExtensionService extends AbstractExtHostExtensionService { + + protected async _beforeAlmostReadyToRunExtensions(): Promise { + // initialize API and register actors + const extensionApiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors); + + // Register Download command + this._instaService.createInstance(ExtHostDownloadService); + + // Register CLI Server for ipc + if (this._initData.remote.isRemote && this._initData.remote.authority) { + const cliServer = this._instaService.createInstance(CLIServer); + process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath; } - return Promise.resolve(undefined); + // Module loading tricks + const interceptor = this._instaService.createInstance(NodeModuleRequireInterceptor, extensionApiFactory, this._registry); + await interceptor.install(); + + // Do this when extension service exists, but extensions are not being activated yet. + const configProvider = await this._extHostConfiguration.getConfigProvider(); + await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy); + + // Use IPC messages to forward console-calls, note that the console is + // already patched to use`process.send()` + const nativeProcessSend = process.send!; + const mainThreadConsole = this._extHostContext.getProxy(MainContext.MainThreadConsole); + process.send = (...args: any[]) => { + if (args.length === 0 || !args[0] || args[0].type !== '__$console') { + return nativeProcessSend.apply(process, args); + } + mainThreadConsole.$logExtensionHostMessage(args[0]); + }; } - private _handleExtensionTests(): Promise { - return this._doHandleExtensionTests().then(undefined, error => { - console.error(error); // ensure any error message makes it onto the console - - return Promise.reject(error); - }); - } - - private _doHandleExtensionTests(): Promise { - const { extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment; - if (!(extensionDevelopmentLocationURI && extensionTestsLocationURI && extensionTestsLocationURI.scheme === Schemas.file)) { - return Promise.resolve(undefined); + protected _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + if (module.scheme !== Schemas.file) { + throw new Error(`Cannot load URI: '${module}', must be of file-scheme`); } - - const extensionTestsPath = originalFSPath(extensionTestsLocationURI); - - // Require the test runner via node require from the provided path - let testRunner: ITestRunner | INewTestRunner | undefined; - let requireError: Error | undefined; + let r: T | null = null; + activationTimesBuilder.codeLoadingStart(); + this._logService.info(`ExtensionService#loadCommonJSModule ${module.toString(true)}`); try { - testRunner = require.__$__nodeRequire(extensionTestsPath); - } catch (error) { - requireError = error; + r = require.__$__nodeRequire(module.fsPath); + } catch (e) { + return Promise.reject(e); + } finally { + activationTimesBuilder.codeLoadingStop(); } - - // Execute the runner if it follows the old `run` spec - if (testRunner && typeof testRunner.run === 'function') { - return new Promise((c, e) => { - const oldTestRunnerCallback = (error: Error, failures: number | undefined) => { - if (error) { - e(error.toString()); - } else { - c(undefined); - } - - // after tests have run, we shutdown the host - this._gracefulExit(error || (typeof failures === 'number' && failures > 0) ? 1 /* ERROR */ : 0 /* OK */); - }; - - const runResult = testRunner!.run(extensionTestsPath, oldTestRunnerCallback); - - // Using the new API `run(): Promise` - if (runResult && runResult.then) { - runResult - .then(() => { - c(); - this._gracefulExit(0); - }) - .catch((err: Error) => { - e(err.toString()); - this._gracefulExit(1); - }); - } - }); - } - - // Otherwise make sure to shutdown anyway even in case of an error - else { - this._gracefulExit(1 /* ERROR */); - } - - return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath))); + return Promise.resolve(r); } - private _gracefulExit(code: number): void { - // to give the PH process a chance to flush any outstanding console - // messages to the main process, we delay the exit() by some time - setTimeout(() => { - // If extension tests are running, give the exit code to the renderer - if (this._initData.remote.isRemote && !!this._initData.environment.extensionTestsLocationURI) { - this._mainThreadExtensionsProxy.$onExtensionHostExit(code); - return; + public async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise { + if (!this._initData.remote.isRemote) { + return; + } + + for (const key in env) { + const value = env[key]; + if (value === null) { + delete process.env[key]; + } else { + process.env[key] = value; } - - this._hostUtils.exit(code); - }, 500); - } - - private _startExtensionHost(): Promise { - if (this._started) { - throw new Error(`Extension host is already started!`); - } - this._started = true; - - return this._readyToStartExtensionHost.wait() - .then(() => this._readyToRunExtensions.open()) - .then(() => this._handleEagerExtensions()) - .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(() => { - delete this._resolvers[authorityPrefix]; - }); - } - - // -- called by main thread - - public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { - const authorityPlusIndex = remoteAuthority.indexOf('+'); - if (authorityPlusIndex === -1) { - throw new Error(`Not an authority that can be resolved!`); - } - const authorityPrefix = remoteAuthority.substr(0, authorityPlusIndex); - - await this._almostReadyToRunExtensions.wait(); - await this._activateByEvent(`onResolveRemoteAuthority:${authorityPrefix}`, false); - - const resolver = this._resolvers[authorityPrefix]; - if (!resolver) { - throw new Error(`No remote extension installed to resolve ${authorityPrefix}.`); - } - - try { - const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); - - // Split merged API result into separate authority/options - const authority: ResolvedAuthority = { - authority: remoteAuthority, - host: result.host, - port: result.port - }; - const options: ResolvedOptions = { - extensionHostEnv: result.extensionHostEnv - }; - - return { - type: 'ok', - value: { - authority, - options - } - }; - } catch (err) { - if (err instanceof RemoteAuthorityResolverError) { - return { - type: 'error', - error: { - code: err._code, - message: err._message, - detail: err._detail - } - }; - } - throw err; } } - - public $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise { - this._registry.keepOnly(enabledExtensionIds); - return this._startExtensionHost(); - } - - public $activateByEvent(activationEvent: string): Promise { - return ( - this._readyToRunExtensions.wait() - .then(_ => this._activateByEvent(activationEvent, false)) - ); - } - - public async $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise { - await this._readyToRunExtensions.wait(); - if (!this._registry.getExtensionDescription(extensionId)) { - // unknown extension => ignore - return false; - } - await this._activateById(extensionId, new ExtensionActivatedByEvent(false, activationEvent)); - return true; - } - - public async $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { - toAdd.forEach((extension) => (extension).extensionLocation = URI.revive(extension.extensionLocation)); - - const trie = await this.getExtensionPathIndex(); - - await Promise.all(toRemove.map(async (extensionId) => { - const extensionDescription = this._registry.getExtensionDescription(extensionId); - if (!extensionDescription) { - return; - } - const realpathValue = await this._hostUtils.realpath(extensionDescription.extensionLocation.fsPath); - trie.delete(URI.file(realpathValue).fsPath); - })); - - await Promise.all(toAdd.map(async (extensionDescription) => { - const realpathValue = await this._hostUtils.realpath(extensionDescription.extensionLocation.fsPath); - trie.set(URI.file(realpathValue).fsPath, extensionDescription); - })); - - this._registry.deltaExtensions(toAdd, toRemove); - return Promise.resolve(undefined); - } - - public async $test_latency(n: number): Promise { - return n; - } - - public async $test_up(b: VSBuffer): Promise { - return b.byteLength; - } - - public async $test_down(size: number): Promise { - let buff = VSBuffer.alloc(size); - let value = Math.random() % 256; - for (let i = 0; i < size; i++) { - buff.writeUInt8(value, i); - } - return buff; - } - -} - -function loadCommonJSModule(logService: ILogService, modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { - let r: T | null = null; - activationTimesBuilder.codeLoadingStart(); - logService.info(`ExtensionService#loadCommonJSModule ${modulePath}`); - try { - r = require.__$__nodeRequire(modulePath); - } catch (e) { - return Promise.reject(e); - } finally { - activationTimesBuilder.codeLoadingStop(); - } - return Promise.resolve(r); -} - -type TelemetryActivationEvent = { - id: string; - name: string; - extensionVersion: string; - publisherDisplayName: string; - activationEvents: string | null; - isBuiltin: boolean; - reason: string; -}; - -function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TelemetryActivationEvent { - const reasonStr = reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : - reason instanceof ExtensionActivatedByAPI ? 'api' : - ''; - const event = { - id: extensionDescription.identifier.value, - name: extensionDescription.name, - extensionVersion: extensionDescription.version, - publisherDisplayName: extensionDescription.publisher, - activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null, - isBuiltin: extensionDescription.isBuiltin, - reason: reasonStr - }; - - return event; } diff --git a/src/vs/workbench/api/node/extHostLogService.ts b/src/vs/workbench/api/node/extHostLogService.ts new file mode 100644 index 00000000000..f29d417620d --- /dev/null +++ b/src/vs/workbench/api/node/extHostLogService.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { join } from 'vs/base/common/path'; +import { ILogService, DelegatedLogService, LogLevel } from 'vs/platform/log/common/log'; +import { ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; +import { URI } from 'vs/base/common/uri'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { Schemas } from 'vs/base/common/network'; +import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; + +export class ExtHostLogService extends DelegatedLogService implements ILogService, ExtHostLogServiceShape { + + constructor( + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostOutputService extHostOutputService: IExtHostOutputService + ) { + if (initData.logsLocation.scheme !== Schemas.file) { throw new Error('Only file-logging supported'); } + super(new SpdLogService(ExtensionHostLogFileName, initData.logsLocation.fsPath, initData.logLevel)); + + // Register an output channel for exthost log + extHostOutputService.createOutputChannelFromLogFile( + initData.remote.isRemote ? localize('remote extension host Log', "Remote Extension Host") : localize('extension host Log', "Extension Host"), + URI.file(join(initData.logsLocation.fsPath, `${ExtensionHostLogFileName}.log`)) + ); + } + + $setLevel(level: LogLevel): void { + this.setLevel(level); + } +} diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index e06299a5a32..3a69496d989 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -10,7 +10,9 @@ import { join } from 'vs/base/common/path'; import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender'; import { toLocalISOString } from 'vs/base/common/date'; import { dirExists, mkdirp } from 'vs/base/node/pfs'; -import { AbstractExtHostOutputChannel, IOutputChannelFactory, ExtHostPushOutputChannel } from 'vs/workbench/api/common/extHostOutput'; +import { AbstractExtHostOutputChannel, ExtHostPushOutputChannel, ExtHostOutputService, LazyOutputChannel } from 'vs/workbench/api/common/extHostOutput'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { @@ -43,23 +45,39 @@ export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChann } } -export const LogOutputChannelFactory = new class implements IOutputChannelFactory { +export class ExtHostOutputService2 extends ExtHostOutputService { - _namePool = 1; + private _logsLocation: URI; + private _namePool: number = 1; - async createOutputChannel(name: string, logsLocation: URI, proxy: MainThreadOutputServiceShape): Promise { + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + ) { + super(extHostRpc); + this._logsLocation = initData.logsLocation; + } + + createOutputChannel(name: string): vscode.OutputChannel { + name = name.trim(); + if (!name) { + throw new Error('illegal argument `name`. must not be falsy'); + } + return new LazyOutputChannel(name, this._doCreateOutChannel(name)); + } + + private async _doCreateOutChannel(name: string): Promise { try { - const outputDirPath = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); - const outputDir = await dirExists(outputDirPath).then(exists => exists ? exists : mkdirp(outputDirPath).then(() => true)).then(() => outputDirPath); + const outputDirPath = join(this._logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + const outputDir = await dirExists(outputDirPath).then(exists => exists || mkdirp(outputDirPath).then(() => true)).then(() => outputDirPath); const fileName = `${this._namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}`; const file = URI.file(join(outputDir, `${fileName}.log`)); const appender = new OutputAppender(fileName, file.fsPath); - return new ExtHostOutputChannelBackedByFile(name, appender, proxy); + return new ExtHostOutputChannelBackedByFile(name, appender, this._proxy); } catch (error) { // Do not crash if logger cannot be created console.log(error); - return new ExtHostPushOutputChannel(name, proxy); + return new ExtHostPushOutputChannel(name, this._proxy); } } -}; - +} diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 5f6be2d3e60..b0e8753c0cd 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -15,8 +15,10 @@ import { RipgrepSearchProvider } from 'vs/workbench/services/search/node/ripgrep import { OutputChannel } from 'vs/workbench/services/search/node/ripgrepSearchUtils'; import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import * as vscode from 'vscode'; -import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from '../common/extHost.protocol'; -import { IURITransformer } from 'vs/base/common/uriIpc'; +import { ExtHostSearchShape, MainContext, MainThreadSearchShape } from '../common/extHost.protocol'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; export class ExtHostSearch implements ExtHostSearchShape { @@ -32,16 +34,30 @@ export class ExtHostSearch implements ExtHostSearchShape { private _fileSearchManager: FileSearchManager; - constructor(mainContext: IMainContext, private _uriTransformer: IURITransformer | null, private _logService: ILogService, private _pfs = pfs) { - this._proxy = mainContext.getProxy(MainContext.MainThreadSearch); + protected _pfs: typeof pfs = pfs; // allow extending for tests + + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @IURITransformerService private _uriTransformer: IURITransformerService, + @ILogService private _logService: ILogService, + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadSearch); this._fileSearchManager = new FileSearchManager(); + + if (initData.remote.isRemote && initData.remote.authority) { + this._registerEHSearchProviders(); + } + } + + private _registerEHSearchProviders(): void { + const outputChannel = new OutputChannel(this._logService); + this.registerTextSearchProvider('file', new RipgrepSearchProvider(outputChannel)); + this.registerInternalFileSearchProvider('file', new SearchService()); } private _transformScheme(scheme: string): string { - if (this._uriTransformer) { - return this._uriTransformer.transformOutgoingScheme(scheme); - } - return scheme; + return this._uriTransformer.transformOutgoingScheme(scheme); } registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider): IDisposable { @@ -148,12 +164,6 @@ export class ExtHostSearch implements ExtHostSearchShape { } } -export function registerEHSearchProviders(extHostSearch: ExtHostSearch, logService: ILogService): void { - const outputChannel = new OutputChannel(logService); - extHostSearch.registerTextSearchProvider('file', new RipgrepSearchProvider(outputChannel)); - extHostSearch.registerInternalFileSearchProvider('file', new SearchService()); -} - function reviveQuery(rawQuery: U): U extends IRawTextQuery ? ITextQuery : IFileQuery { return { ...rawQuery, // TODO diff --git a/src/vs/workbench/api/node/extHostStoragePaths.ts b/src/vs/workbench/api/node/extHostStoragePaths.ts index 0adb497de02..5a301ad857c 100644 --- a/src/vs/workbench/api/node/extHostStoragePaths.ts +++ b/src/vs/workbench/api/node/extHostStoragePaths.ts @@ -8,23 +8,24 @@ import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { IEnvironment, IStaticWorkspaceData } from 'vs/workbench/api/common/extHost.protocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { withNullAsUndefined } from 'vs/base/common/types'; -export class ExtensionStoragePaths { +export class ExtensionStoragePaths implements IExtensionStoragePaths { + + readonly _serviceBrand: undefined; private readonly _workspace?: IStaticWorkspaceData; private readonly _environment: IEnvironment; - private readonly _ready: Promise; + readonly whenReady: Promise; private _value?: string; - constructor(workspace: IStaticWorkspaceData | undefined, environment: IEnvironment) { - this._workspace = workspace; - this._environment = environment; - this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value); - } - - get whenReady(): Promise { - return this._ready; + constructor(@IExtHostInitDataService initData: IExtHostInitDataService) { + this._workspace = withNullAsUndefined(initData.workspace); + this._environment = initData.environment; + this.whenReady = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value); } workspaceValue(extension: IExtensionDescription): string | undefined { diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 9191b53aa47..cf9b522ef11 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -12,27 +12,28 @@ import { Event, Emitter } from 'vs/base/common/event'; import { win32 } from 'vs/base/node/processes'; -import { MainContext, MainThreadTaskShape, ExtHostTaskShape, IMainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext, MainThreadTaskShape, ExtHostTaskShape } from 'vs/workbench/api/common/extHost.protocol'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { ExtHostWorkspace, IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostWorkspaceProvider, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import * as vscode from 'vscode'; import { TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO, ProcessExecutionOptionsDTO, ProcessExecutionDTO, ShellExecutionOptionsDTO, ShellExecutionDTO, - CustomExecutionDTO, CustomExecution2DTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, TaskSetDTO } from '../common/shared/tasks'; import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; -import { ExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/node/extHostTerminalService'; +import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { Schemas } from 'vs/base/common/network'; namespace TaskDefinitionDTO { export function from(value: vscode.TaskDefinition): TaskDefinitionDTO | undefined { @@ -80,7 +81,7 @@ namespace ProcessExecutionOptionsDTO { } namespace ProcessExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is ProcessExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined): value is ProcessExecutionDTO { if (value) { const candidate = value as ProcessExecutionDTO; return candidate && !!candidate.process; @@ -125,7 +126,7 @@ namespace ShellExecutionOptionsDTO { } namespace ShellExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is ShellExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined): value is ShellExecutionDTO { if (value) { const candidate = value as ShellExecutionDTO; return candidate && (!!candidate.commandLine || !!candidate.command); @@ -162,25 +163,8 @@ namespace ShellExecutionDTO { } } -namespace CustomExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is CustomExecutionDTO { - if (value) { - let candidate = value as CustomExecutionDTO; - return candidate && candidate.customExecution === 'customExecution'; - } else { - return false; - } - } - - export function from(value: vscode.CustomExecution): CustomExecutionDTO { - return { - customExecution: 'customExecution' - }; - } -} - namespace CustomExecution2DTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is CustomExecution2DTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined): value is CustomExecution2DTO { if (value) { let candidate = value as CustomExecution2DTO; return candidate && candidate.customExecution === 'customExecution2'; @@ -229,13 +213,11 @@ namespace TaskDTO { if (value === undefined || value === null) { return undefined; } - let execution: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined; + let execution: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined; if (value.execution instanceof types.ProcessExecution) { execution = ProcessExecutionDTO.from(value.execution); } else if (value.execution instanceof types.ShellExecution) { execution = ShellExecutionDTO.from(value.execution); - } else if ((value).execution2 && (value).execution2 instanceof types.CustomExecution) { - execution = CustomExecutionDTO.from((value).execution2); } else if ((value).execution2 && (value).execution2 instanceof types.CustomExecution2) { execution = CustomExecution2DTO.from((value).execution2); } @@ -373,122 +355,18 @@ interface HandlerData { extension: IExtensionDescription; } -class CustomExecutionData implements IDisposable { - private static waitForDimensionsTimeoutInMs: number = 5000; - private _cancellationSource?: CancellationTokenSource; - private readonly _onTaskExecutionComplete: Emitter = new Emitter(); - private readonly _disposables = new DisposableStore(); - private terminal?: vscode.Terminal; - private terminalId?: number; - public result: number | undefined; - - constructor( - private readonly customExecution: vscode.CustomExecution, - private readonly terminalService: ExtHostTerminalService) { - } - - public dispose(): void { - this._cancellationSource = undefined; - this._disposables.dispose(); - } - - public get onTaskExecutionComplete(): Event { - return this._onTaskExecutionComplete.event; - } - - private onDidCloseTerminal(terminal: vscode.Terminal): void { - if ((this.terminal === terminal) && this._cancellationSource) { - this._cancellationSource.cancel(); - } - } - - private onDidOpenTerminal(terminal: vscode.Terminal): void { - if (!(terminal instanceof ExtHostTerminal)) { - throw new Error('How could this not be a extension host terminal?'); - } - - if (this.terminalId && terminal._id === this.terminalId) { - this.startCallback(this.terminalId); - } - } - - public async startCallback(terminalId: number): Promise { - this.terminalId = terminalId; - - // If we have already started the extension task callback, then - // do not start it again. - // It is completely valid for multiple terminals to be opened - // before the one for our task. - if (this._cancellationSource) { - return undefined; - } - - const callbackTerminals: vscode.Terminal[] = this.terminalService.terminals.filter((terminal) => terminal._id === terminalId); - - if (!callbackTerminals || callbackTerminals.length === 0) { - this._disposables.add(this.terminalService.onDidOpenTerminal(this.onDidOpenTerminal.bind(this))); - return; - } - - if (callbackTerminals.length !== 1) { - throw new Error(`Expected to only have one terminal at this point`); - } - - this.terminal = callbackTerminals[0]; - const terminalRenderer: vscode.TerminalRenderer = await this.terminalService.resolveTerminalRenderer(terminalId); - - // If we don't have the maximum dimensions yet, then we need to wait for them (but not indefinitely). - // Custom executions will expect the dimensions to be set properly before they are launched. - // BUT, due to the API contract VSCode has for terminals and dimensions, they are still responsible for - // handling cases where they are not set. - if (!terminalRenderer.maximumDimensions) { - const dimensionTimeout: Promise = new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, CustomExecutionData.waitForDimensionsTimeoutInMs); - }); - - let dimensionsRegistration: IDisposable | undefined; - const dimensionsPromise: Promise = new Promise((resolve) => { - dimensionsRegistration = terminalRenderer.onDidChangeMaximumDimensions((newDimensions) => { - resolve(); - }); - }); - - await Promise.race([dimensionTimeout, dimensionsPromise]); - if (dimensionsRegistration) { - dimensionsRegistration.dispose(); - } - } - - this._cancellationSource = new CancellationTokenSource(); - this._disposables.add(this._cancellationSource); - - this._disposables.add(this.terminalService.onDidCloseTerminal(this.onDidCloseTerminal.bind(this))); - - // Regardless of how the task completes, we are done with this custom execution task. - this.customExecution.callback(terminalRenderer, this._cancellationSource.token).then( - (success) => { - this.result = success; - this._onTaskExecutionComplete.fire(this); - }, (rejected) => { - this._onTaskExecutionComplete.fire(this); - }); - } -} - export class ExtHostTask implements ExtHostTaskShape { - private _proxy: MainThreadTaskShape; - private _workspaceProvider: IExtHostWorkspaceProvider; - private _editorService: ExtHostDocumentsAndEditors; - private _configurationService: ExtHostConfiguration; - private _terminalService: ExtHostTerminalService; + readonly _serviceBrand: any; + + private readonly _proxy: MainThreadTaskShape; + private readonly _workspaceProvider: IExtHostWorkspaceProvider; + private readonly _editorService: IExtHostDocumentsAndEditors; + private readonly _configurationService: IExtHostConfiguration; + private readonly _terminalService: IExtHostTerminalService; private _handleCounter: number; private _handlers: Map; private _taskExecutions: Map; - private _providedCustomExecutions: Map; - private _activeCustomExecutions: Map; private _providedCustomExecutions2: Map; private _activeCustomExecutions2: Map; @@ -499,12 +377,14 @@ export class ExtHostTask implements ExtHostTaskShape { private readonly _onDidTaskProcessEnded: Emitter = new Emitter(); constructor( - mainContext: IMainContext, - workspaceService: ExtHostWorkspace, - editorService: ExtHostDocumentsAndEditors, - configurationService: ExtHostConfiguration, - extHostTerminalService: ExtHostTerminalService) { - this._proxy = mainContext.getProxy(MainContext.MainThreadTask); + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostWorkspace workspaceService: IExtHostWorkspace, + @IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration configurationService: IExtHostConfiguration, + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTask); this._workspaceProvider = workspaceService; this._editorService = editorService; this._configurationService = configurationService; @@ -512,10 +392,16 @@ export class ExtHostTask implements ExtHostTaskShape { this._handleCounter = 0; this._handlers = new Map(); this._taskExecutions = new Map(); - this._providedCustomExecutions = new Map(); - this._activeCustomExecutions = new Map(); this._providedCustomExecutions2 = new Map(); this._activeCustomExecutions2 = new Map(); + + if (initData.remote.isRemote && initData.remote.authority) { + this.registerTaskSystem(Schemas.vscodeRemote, { + scheme: Schemas.vscodeRemote, + authority: initData.remote.authority, + platform: process.platform + }); + } } public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable { @@ -558,6 +444,14 @@ export class ExtHostTask implements ExtHostTaskShape { if (dto === undefined) { return Promise.reject(new Error('Task is not valid')); } + + // If this task is a custom execution, then we need to save it away + // in the provided custom execution map that is cleaned up after the + // task is executed. + if (CustomExecution2DTO.is(dto.execution)) { + await this.addCustomExecution2(dto, task); + } + return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, task)); } } @@ -588,26 +482,7 @@ export class ExtHostTask implements ExtHostTaskShape { // Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task. this._activeCustomExecutions2.set(execution.id, execution2); - await this._terminalService.attachPtyToTerminal(terminalId, await execution2.callback()); - } - - // Once a terminal is spun up for the custom execution task this event will be fired. - // At that point, we need to actually start the callback, but - // only if it hasn't already begun. - const extensionCallback: CustomExecutionData | undefined = this._providedCustomExecutions.get(execution.id); - if (extensionCallback) { - if (this._activeCustomExecutions.get(execution.id) !== undefined) { - throw new Error('We should not be trying to start the same custom task executions twice.'); - } - - this._activeCustomExecutions.set(execution.id, extensionCallback); - - const taskExecutionComplete: IDisposable = extensionCallback.onTaskExecutionComplete(() => { - this.customExecutionComplete(execution); - taskExecutionComplete.dispose(); - }); - - extensionCallback.startCallback(terminalId); + this._terminalService.attachPtyToTerminal(terminalId, await execution2.callback()); } this._onDidExecuteTask.fire({ @@ -662,12 +537,6 @@ export class ExtHostTask implements ExtHostTaskShape { return Promise.reject(new Error('no handler found')); } - // For custom execution tasks, we need to store the execution objects locally - // since we obviously cannot send callback functions through the proxy. - // So, clear out any existing ones. - this._providedCustomExecutions.clear(); - this._providedCustomExecutions2.clear(); - // Set up a list of task ID promises that we can wait on // before returning the provided tasks. The ensures that // our task IDs are calculated for any custom execution tasks. @@ -689,14 +558,11 @@ export class ExtHostTask implements ExtHostTaskShape { if (taskDTO) { taskDTOs.push(taskDTO); - if (CustomExecutionDTO.is(taskDTO.execution)) { + if (CustomExecution2DTO.is(taskDTO.execution)) { // The ID is calculated on the main thread task side, so, let's call into it here. // We need the task id's pre-computed for custom task executions because when OnDidStartTask // is invoked, we have to be able to map it back to our data. - taskIdPromises.push(this.addCustomExecution(taskDTO, task)); - } else if (CustomExecution2DTO.is(taskDTO.execution)) { taskIdPromises.push(this.addCustomExecution2(taskDTO, task)); - } } } @@ -741,12 +607,12 @@ export class ExtHostTask implements ExtHostTaskShape { throw new Error('Unexpected: Task cannot be resolved.'); } - if (CustomExecutionDTO.is(resolvedTaskDTO.execution)) { - await this.addCustomExecution(taskDTO, task); + if (resolvedTask.definition !== task.definition) { + throw new Error('Unexpected: The resolved task definition must be the same object as the original task definition. The task definition cannot be changed.'); } if (CustomExecution2DTO.is(resolvedTaskDTO.execution)) { - await this.addCustomExecution2(taskDTO, task); + await this.addCustomExecution2(resolvedTaskDTO, resolvedTask); } return resolvedTaskDTO; @@ -797,11 +663,6 @@ export class ExtHostTask implements ExtHostTaskShape { return this._handleCounter++; } - private async addCustomExecution(taskDTO: TaskDTO, task: vscode.Task2): Promise { - const taskId = await this._proxy.$createTaskId(taskDTO); - this._providedCustomExecutions.set(taskId, new CustomExecutionData((task).execution2, this._terminalService)); - } - private async addCustomExecution2(taskDTO: TaskDTO, task: vscode.Task2): Promise { const taskId = await this._proxy.$createTaskId(taskDTO); this._providedCustomExecutions2.set(taskId, (task).execution2); @@ -830,15 +691,21 @@ export class ExtHostTask implements ExtHostTaskShape { } private customExecutionComplete(execution: TaskExecutionDTO): void { - const extensionCallback: CustomExecutionData | undefined = this._activeCustomExecutions.get(execution.id); - if (extensionCallback) { - this._activeCustomExecutions.delete(execution.id); - this._proxy.$customExecutionComplete(execution.id, extensionCallback.result); - extensionCallback.dispose(); - } const extensionCallback2: vscode.CustomExecution2 | undefined = this._activeCustomExecutions2.get(execution.id); if (extensionCallback2) { this._activeCustomExecutions2.delete(execution.id); } + + const lastCustomExecution = this._providedCustomExecutions2.get(execution.id); + // Technically we don't really need to do this, however, if an extension + // is executing a task through "executeTask" over and over again + // with different properties in the task definition, then this list + // could grow indefinitely, something we don't want. + this._providedCustomExecutions2.clear(); + // We do still need to hang on to the last custom execution so that the + // Rerun Task command doesn't choke when it tries to rerun a custom execution + if (lastCustomExecution) { + this._providedCustomExecutions2.set(execution.id, lastCustomExecution); + } } } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index ef336f561a2..5c5d76564f8 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -10,26 +10,25 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; +import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto, ITerminalDimensionsDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostConfiguration, ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { ILogService } from 'vs/platform/log/common/log'; import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig, ITerminalEnvironment, ITerminalChildProcess, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; import { timeout } from 'vs/base/common/async'; -import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; -import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; -import { IDisposable } from 'vs/base/common/lifecycle'; - -const RENDERER_NO_PROCESS_ID = -1; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export class BaseExtHostTerminal { - public _id: number; + public _id: number | undefined; protected _idPromise: Promise; - private _idPromiseComplete: (value: number) => any; + private _idPromiseComplete: ((value: number) => any) | undefined; private _disposed: boolean = false; private _queuedRequests: ApiRequest[] = []; @@ -71,9 +70,12 @@ export class BaseExtHostTerminal { public _runQueuedRequests(id: number): void { this._id = id; - this._idPromiseComplete(id); + if (this._idPromiseComplete) { + this._idPromiseComplete(id); + this._idPromiseComplete = undefined; + } this._queuedRequests.forEach((r) => { - r.run(this._proxy, this._id); + r.run(this._proxy, id); }); this._queuedRequests.length = 0; } @@ -82,32 +84,29 @@ export class BaseExtHostTerminal { export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal { private _pidPromise: Promise; private _cols: number | undefined; - private _pidPromiseComplete: ((value: number | undefined) => any) | null; + private _pidPromiseComplete: ((value: number | undefined) => any) | undefined; private _rows: number | undefined; + /** @deprecated */ private readonly _onData = new Emitter(); + /** @deprecated */ public get onDidWriteData(): Event { // Tell the main side to start sending data if it's not already - this._idPromise.then(c => { - this._proxy.$registerOnDataListener(this._id); + this._idPromise.then(id => { + this._proxy.$registerOnDataListener(id); }); return this._onData.event; } + public isOpen: boolean = false; + constructor( proxy: MainThreadTerminalServiceShape, private _name?: string, - id?: number, - pid?: number + id?: number ) { super(proxy, id); - this._pidPromise = new Promise(c => { - if (pid === RENDERER_NO_PROCESS_ID) { - c(undefined); - } else { - this._pidPromiseComplete = c; - } - }); + this._pidPromise = new Promise(c => this._pidPromiseComplete = c); } public async create( @@ -124,10 +123,11 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi this._runQueuedRequests(terminal.id); } - public async createExtensionTerminal(): Promise { + public async createExtensionTerminal(): Promise { const terminal = await this._proxy.$createTerminal({ name: this._name, isExtensionTerminal: true }); this._name = terminal.name; this._runQueuedRequests(terminal.id); + return terminal.id; } public get name(): string { @@ -181,7 +181,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi // The event may fire 2 times when the panel is restored if (this._pidPromiseComplete) { this._pidPromiseComplete(processId); - this._pidPromiseComplete = null; + this._pidPromiseComplete = undefined; } else { // Recreate the promise if this is the nth processId set (e.g. reused task terminals) this._pidPromise.then(pid => { @@ -197,92 +197,14 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi } } -export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vscode.TerminalRenderer { - public get name(): string { return this._name; } - public set name(newName: string) { - this._name = newName; - this._checkDisposed(); - this._queueApiRequest(this._proxy.$terminalRendererSetName, [this._name]); - } +export class ExtHostTerminalService implements IExtHostTerminalService, ExtHostTerminalServiceShape { - private readonly _onInput = new Emitter(); - public get onDidAcceptInput(): Event { - this._checkDisposed(); - this._queueApiRequest(this._proxy.$terminalRendererRegisterOnInputListener, [this._id]); - // Tell the main side to start sending data if it's not already - // this._proxy.$terminalRendererRegisterOnDataListener(this._id); - return this._onInput && this._onInput.event; - } + readonly _serviceBrand: any; - private _dimensions: vscode.TerminalDimensions | undefined; - public get dimensions(): vscode.TerminalDimensions | undefined { return this._dimensions; } - public set dimensions(dimensions: vscode.TerminalDimensions | undefined) { - this._checkDisposed(); - this._dimensions = dimensions; - this._queueApiRequest(this._proxy.$terminalRendererSetDimensions, [dimensions]); - } - - private _maximumDimensions: vscode.TerminalDimensions | undefined; - public get maximumDimensions(): vscode.TerminalDimensions | undefined { - if (!this._maximumDimensions) { - return undefined; - } - return { - rows: this._maximumDimensions.rows, - columns: this._maximumDimensions.columns - }; - } - - private readonly _onDidChangeMaximumDimensions: Emitter = new Emitter(); - public get onDidChangeMaximumDimensions(): Event { - return this._onDidChangeMaximumDimensions && this._onDidChangeMaximumDimensions.event; - } - - public get terminal(): ExtHostTerminal { - return this._terminal; - } - - constructor( - proxy: MainThreadTerminalServiceShape, - private _name: string, - private _terminal: ExtHostTerminal, - id?: number - ) { - super(proxy, id); - - if (!id) { - this._proxy.$createTerminalRenderer(this._name).then(id => { - this._runQueuedRequests(id); - (this._terminal)._runQueuedRequests(id); - }); - } - } - - public write(data: string): void { - this._checkDisposed(); - this._queueApiRequest(this._proxy.$terminalRendererWrite, [data]); - } - - public _fireOnInput(data: string): void { - this._onInput.fire(data); - } - - public _setMaximumDimensions(columns: number, rows: number): void { - if (this._maximumDimensions && this._maximumDimensions.columns === columns && this._maximumDimensions.rows === rows) { - return; - } - const newValue = { columns, rows }; - this._maximumDimensions = newValue; - this._onDidChangeMaximumDimensions.fire(newValue); - } -} - -export class ExtHostTerminalService implements ExtHostTerminalServiceShape { private _proxy: MainThreadTerminalServiceShape; private _activeTerminal: ExtHostTerminal | undefined; private _terminals: ExtHostTerminal[] = []; private _terminalProcesses: { [id: number]: ITerminalChildProcess } = {}; - private _terminalRenderers: ExtHostTerminalRenderer[] = []; private _getTerminalPromises: { [id: number]: Promise } = {}; private _variableResolver: ExtHostVariableResolverService | undefined; private _lastActiveWorkspace: IWorkspaceFolder | undefined; @@ -301,15 +223,21 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public get onDidChangeActiveTerminal(): Event { return this._onDidChangeActiveTerminal && this._onDidChangeActiveTerminal.event; } private readonly _onDidChangeTerminalDimensions: Emitter = new Emitter(); public get onDidChangeTerminalDimensions(): Event { return this._onDidChangeTerminalDimensions && this._onDidChangeTerminalDimensions.event; } + private readonly _onDidWriteTerminalData: Emitter; + public get onDidWriteTerminalData(): Event { return this._onDidWriteTerminalData && this._onDidWriteTerminalData.event; } constructor( - mainContext: IMainContext, - private _extHostConfiguration: ExtHostConfiguration, - private _extHostWorkspace: ExtHostWorkspace, - private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, - private _logService: ILogService + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostConfiguration private _extHostConfiguration: ExtHostConfiguration, + @IExtHostWorkspace private _extHostWorkspace: ExtHostWorkspace, + @IExtHostDocumentsAndEditors private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, + @ILogService private _logService: ILogService ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadTerminalService); + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTerminalService); + this._onDidWriteTerminalData = new Emitter({ + onFirstListenerAdd: () => this._proxy.$startSendingDataEvents(), + onLastListenerRemove: () => this._proxy.$stopSendingDataEvents() + }); this._updateLastActiveWorkspace(); this._updateVariableResolver(); this._registerListeners(); @@ -332,12 +260,12 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options.name); const p = new ExtHostPseudoterminal(options.pty); - terminal.createExtensionTerminal().then(() => this._setupExtHostProcessListeners(terminal._id, p)); + terminal.createExtensionTerminal().then(id => this._setupExtHostProcessListeners(id, p)); this._terminals.push(terminal); return terminal; } - public async attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): Promise { + public attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void { const terminal = this._getTerminalByIdEventually(id); if (!terminal) { throw new Error(`Cannot resolve terminal with id ${id} for virtual process`); @@ -346,18 +274,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { this._setupExtHostProcessListeners(id, p); } - public createTerminalRenderer(name: string): vscode.TerminalRenderer { - const terminal = new ExtHostTerminal(this._proxy, name); - terminal._setProcessId(undefined); - this._terminals.push(terminal); - - const renderer = new ExtHostTerminalRenderer(this._proxy, name, terminal); - this._terminalRenderers.push(renderer); - - return renderer; - } - - public getDefaultShell(configProvider: ExtHostConfigProvider): string { + public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string { const fetchSetting = (key: string) => { const setting = configProvider .getConfiguration(key.substr(0, key.lastIndexOf('.'))) @@ -371,11 +288,13 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'), process.env.windir, this._lastActiveWorkspace, - this._variableResolver + this._variableResolver, + this._logService, + useAutomationShell ); } - private _getDefaultShellArgs(configProvider: ExtHostConfigProvider): string[] | string { + private _getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string { const fetchSetting = (key: string) => { const setting = configProvider .getConfiguration(key.substr(0, key.lastIndexOf('.'))) @@ -383,28 +302,10 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return this._apiInspectConfigToPlain(setting); }; - return terminalEnvironment.getDefaultShellArgs(fetchSetting, this._isWorkspaceShellAllowed, this._lastActiveWorkspace, this._variableResolver); + return terminalEnvironment.getDefaultShellArgs(fetchSetting, this._isWorkspaceShellAllowed, useAutomationShell, this._lastActiveWorkspace, this._variableResolver, this._logService); } - public async resolveTerminalRenderer(id: number): Promise { - // Check to see if the extension host already knows about this terminal. - for (const terminalRenderer of this._terminalRenderers) { - if (terminalRenderer._id === id) { - return terminalRenderer; - } - } - - const terminal = this._getTerminalById(id); - if (!terminal) { - throw new Error(`Cannot resolve terminal renderer for terminal id ${id}`); - } - const renderer = new ExtHostTerminalRenderer(this._proxy, terminal.name, terminal, terminal._id); - this._terminalRenderers.push(renderer); - - return renderer; - } - - public $acceptActiveTerminalChanged(id: number | null): void { + public async $acceptActiveTerminalChanged(id: number | null): Promise { const original = this._activeTerminal; if (id === null) { this._activeTerminal = undefined; @@ -413,69 +314,62 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { } return; } - this.performTerminalIdAction(id, terminal => { - if (terminal) { - this._activeTerminal = terminal; - if (original !== this._activeTerminal) { - this._onDidChangeActiveTerminal.fire(this._activeTerminal); - } + const terminal = await this._getTerminalByIdEventually(id); + if (terminal) { + this._activeTerminal = terminal; + if (original !== this._activeTerminal) { + this._onDidChangeActiveTerminal.fire(this._activeTerminal); } - }); + } } - public $acceptTerminalProcessData(id: number, data: string): void { - this._getTerminalByIdEventually(id).then(terminal => { - if (terminal) { - terminal._fireOnData(data); - } - }); + /** @deprecated */ + public async $acceptTerminalProcessData(id: number, data: string): Promise { + const terminal = await this._getTerminalByIdEventually(id); + if (terminal) { + terminal._fireOnData(data); + } } - public $acceptTerminalDimensions(id: number, cols: number, rows: number): void { - this._getTerminalByIdEventually(id).then(terminal => { - if (terminal) { - if (terminal.setDimensions(cols, rows)) { - this._onDidChangeTerminalDimensions.fire({ - terminal: terminal, - dimensions: terminal.dimensions as vscode.TerminalDimensions - }); - } - } - }); + public async $acceptTerminalProcessData2(id: number, data: string): Promise { + const terminal = await this._getTerminalByIdEventually(id); + if (terminal) { + this._onDidWriteTerminalData.fire({ terminal, data }); + } } - public $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void { + public async $acceptTerminalDimensions(id: number, cols: number, rows: number): Promise { + const terminal = await this._getTerminalByIdEventually(id); + if (terminal) { + if (terminal.setDimensions(cols, rows)) { + this._onDidChangeTerminalDimensions.fire({ + terminal: terminal, + dimensions: terminal.dimensions as vscode.TerminalDimensions + }); + } + } + } + + public async $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): Promise { + await this._getTerminalByIdEventually(id); + if (this._terminalProcesses[id]) { - // Virtual processes only - when virtual process resize fires it means that the + // Extension pty terminal only - when virtual process resize fires it means that the // terminal's maximum dimensions changed this._terminalProcesses[id].resize(cols, rows); - } else { - // Terminal renderer - this._getTerminalByIdEventually(id).then(() => { - // When a terminal's dimensions change, a renderer's _maximum_ dimensions change - const renderer = this._getTerminalRendererById(id); - if (renderer) { - renderer._setMaximumDimensions(cols, rows); - } - }); } } - public $acceptTerminalRendererInput(id: number, data: string): void { - const renderer = this._getTerminalRendererById(id); - if (renderer) { - renderer._fireOnInput(data); - } - } - - public $acceptTerminalTitleChange(id: number, name: string): void { + public async $acceptTerminalTitleChange(id: number, name: string): Promise { + await this._getTerminalByIdEventually(id); const extHostTerminal = this._getTerminalObjectById(this.terminals, id); if (extHostTerminal) { extHostTerminal.name = name; } } - public $acceptTerminalClosed(id: number): void { + public async $acceptTerminalClosed(id: number): Promise { + await this._getTerminalByIdEventually(id); const index = this._getTerminalObjectIndexById(this.terminals, id); if (index !== null) { const terminal = this._terminals.splice(index, 1)[0]; @@ -488,20 +382,25 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { if (index !== null) { // The terminal has already been created (via createTerminal*), only fire the event this._onDidOpenTerminal.fire(this.terminals[index]); + this.terminals[index].isOpen = true; return; } - const renderer = this._getTerminalRendererById(id); - const terminal = new ExtHostTerminal(this._proxy, name, id, renderer ? RENDERER_NO_PROCESS_ID : undefined); + const terminal = new ExtHostTerminal(this._proxy, name, id); this._terminals.push(terminal); this._onDidOpenTerminal.fire(terminal); + terminal.isOpen = true; } - public $acceptTerminalProcessId(id: number, processId: number): void { - this.performTerminalIdAction(id, terminal => terminal._setProcessId(processId)); + public async $acceptTerminalProcessId(id: number, processId: number): Promise { + const terminal = await this._getTerminalByIdEventually(id); + if (terminal) { + terminal._setProcessId(processId); + } } public performTerminalIdAction(id: number, callback: (terminal: ExtHostTerminal) => void): void { + // TODO: Use await this._getTerminalByIdEventually(id); let terminal = this._getTerminalById(id); if (terminal) { callback(terminal); @@ -550,7 +449,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._extHostDocumentsAndEditors, configProvider); } - public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { + public async $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { const shellLaunchConfig: IShellLaunchConfig = { name: shellLaunchConfigDto.name, executable: shellLaunchConfigDto.executable, @@ -563,8 +462,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); const configProvider = await this._extHostConfiguration.getConfigProvider(); if (!shellLaunchConfig.executable) { - shellLaunchConfig.executable = this.getDefaultShell(configProvider); - shellLaunchConfig.args = this._getDefaultShellArgs(configProvider); + shellLaunchConfig.executable = this.getDefaultShell(false, configProvider); + shellLaunchConfig.args = this._getDefaultShellArgs(false, configProvider); } else { if (this._variableResolver) { shellLaunchConfig.executable = this._variableResolver.resolve(this._lastActiveWorkspace, shellLaunchConfig.executable); @@ -598,6 +497,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { const terminalConfig = configProvider.getConfiguration('terminal.integrated'); const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, os.homedir(), lastActiveWorkspace ? lastActiveWorkspace : undefined, this._variableResolver, activeWorkspaceRootUri, terminalConfig.cwd, this._logService); + shellLaunchConfig.cwd = initialCwd; const envFromConfig = this._apiInspectConfigToPlain(configProvider.getConfiguration('terminal.integrated').inspect(`env.${platformKey}`)); const baseEnv = terminalConfig.get('inheritEnv', true) ? process.env as platform.IProcessEnvironment : await this._getNonInheritedEnv(); @@ -624,7 +524,27 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public async $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise { // Make sure the ExtHostTerminal exists so onDidOpenTerminal has fired before we call // Pseudoterminal.start - await this._getTerminalByIdEventually(id); + const terminal = await this._getTerminalByIdEventually(id); + if (!terminal) { + return; + } + + // Wait for onDidOpenTerminal to fire + let openPromise: Promise; + if (terminal.isOpen) { + openPromise = Promise.resolve(); + } else { + openPromise = new Promise(r => { + // Ensure open is called after onDidOpenTerminal + const listener = this.onDidOpenTerminal(async e => { + if (e === terminal) { + listener.dispose(); + r(); + } + }); + }); + } + await openPromise; // Processes should be initialized here for normal virtual process terminals, however for // tasks they are responsible for attaching the virtual process to a terminal so this @@ -685,11 +605,11 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return detectAvailableShells(); } - public async $requestDefaultShellAndArgs(): Promise { + public async $requestDefaultShellAndArgs(useAutomationShell: boolean): Promise { const configProvider = await this._extHostConfiguration.getConfigProvider(); return Promise.resolve({ - shell: this.getDefaultShell(configProvider), - args: this._getDefaultShellArgs(configProvider) + shell: this.getDefaultShell(useAutomationShell, configProvider), + args: this._getDefaultShellArgs(useAutomationShell, configProvider) }); } @@ -701,7 +621,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { this._proxy.$sendProcessExit(id, exitCode); } - private _getTerminalByIdEventually(id: number, retries: number = 5): Promise { + // TODO: This could be improved by using a single promise and resolve it when the terminal is ready + private _getTerminalByIdEventually(id: number, retries: number = 5): Promise { if (!this._getTerminalPromises[id]) { this._getTerminalPromises[id] = this._createGetTerminalPromise(id, retries); } else { @@ -734,16 +655,12 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return this._getTerminalObjectById(this._terminals, id); } - private _getTerminalRendererById(id: number): ExtHostTerminalRenderer | null { - return this._getTerminalObjectById(this._terminalRenderers, id); - } - - private _getTerminalObjectById(array: T[], id: number): T | null { + private _getTerminalObjectById(array: T[], id: number): T | null { const index = this._getTerminalObjectIndexById(array, id); return index !== null ? array[index] : null; } - private _getTerminalObjectIndexById(array: T[], id: number): number | null { + private _getTerminalObjectIndexById(array: T[], id: number): number | null { let index: number | null = null; array.some((item, i) => { const thisId = item._id; @@ -776,9 +693,6 @@ class ApiRequest { } class ExtHostPseudoterminal implements ITerminalChildProcess { - private _queuedEvents: (IQueuedEvent | IQueuedEvent | IQueuedEvent<{ pid: number, cwd: string }> | IQueuedEvent)[] = []; - private _queueDisposables: IDisposable[] | undefined; - private readonly _onProcessData = new Emitter(); public readonly onProcessData: Event = this._onProcessData.event; private readonly _onProcessExit = new Emitter(); @@ -790,23 +704,10 @@ class ExtHostPseudoterminal implements ITerminalChildProcess { private readonly _onProcessOverrideDimensions = new Emitter(); public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } - constructor( - private readonly _pty: vscode.Pseudoterminal - ) { - this._queueDisposables = []; - this._queueDisposables.push(this._pty.onDidWrite(e => this._queuedEvents.push({ emitter: this._onProcessData, data: e }))); - if (this._pty.onDidClose) { - this._queueDisposables.push(this._pty.onDidClose(e => this._queuedEvents.push({ emitter: this._onProcessExit, data: 0 }))); - } - if (this._pty.onDidOverrideDimensions) { - this._queueDisposables.push(this._pty.onDidOverrideDimensions(e => this._queuedEvents.push({ emitter: this._onProcessOverrideDimensions, data: e ? { cols: e.columns, rows: e.rows } : undefined }))); - } - } + constructor(private readonly _pty: vscode.Pseudoterminal) { } shutdown(): void { - if (this._pty.close) { - this._pty.close(); - } + this._pty.close(); } input(data: string): void { @@ -834,12 +735,7 @@ class ExtHostPseudoterminal implements ITerminalChildProcess { } startSendingEvents(initialDimensions: ITerminalDimensionsDto | undefined): void { - // Flush all buffered events - this._queuedEvents.forEach(e => (e.emitter.fire)(e.data)); - this._queuedEvents = []; - this._queueDisposables = undefined; - - // Attach the real listeners + // Attach the listeners this._pty.onDidWrite(e => this._onProcessData.fire(e)); if (this._pty.onDidClose) { this._pty.onDidClose(e => this._onProcessExit.fire(0)); @@ -848,13 +744,7 @@ class ExtHostPseudoterminal implements ITerminalChildProcess { this._pty.onDidOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : e)); } - if (this._pty.open) { - this._pty.open(initialDimensions); - } + this._pty.open(initialDimensions ? initialDimensions : undefined); } } -interface IQueuedEvent { - emitter: Emitter; - data: T; -} diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts new file mode 100644 index 00000000000..a40ce65b7bf --- /dev/null +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl'; +import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator'; +import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { endsWith, startsWith } from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import { joinPath } from 'vs/base/common/resources'; +import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor'; + +class ExportsTrap { + + static readonly Instance = new ExportsTrap(); + + private readonly _names: string[] = []; + private readonly _exports = new Map(); + + private constructor() { + + const exportsProxy = new Proxy({}, { + set: (target: any, p: PropertyKey, value: any) => { + // store in target + target[p] = value; + // store in named-bucket + const name = this._names[this._names.length - 1]; + this._exports.get(name)![p] = value; + return true; + } + }); + + + const moduleProxy = new Proxy({}, { + + get: (target: any, p: PropertyKey) => { + if (p === 'exports') { + return exportsProxy; + } + + return target[p]; + }, + + set: (target: any, p: PropertyKey, value: any) => { + // store in target + target[p] = value; + + // override bucket + if (p === 'exports') { + const name = this._names[this._names.length - 1]; + this._exports.set(name, value); + } + return true; + } + }); + + (self).exports = exportsProxy; + (self).module = moduleProxy; + } + + add(name: string) { + this._exports.set(name, Object.create(null)); + this._names.push(name); + + return { + claim: () => { + const result = this._exports.get(name); + this._exports.delete(name); + this._names.pop(); + return result; + } + }; + } +} + +class WorkerRequireInterceptor extends RequireInterceptor { + + _installInterceptor() { } + + getModule(request: string, parent: URI): undefined | any { + for (let alternativeModuleName of this._alternatives) { + let alternative = alternativeModuleName(request); + if (alternative) { + request = alternative; + break; + } + } + + if (this._factories.has(request)) { + return this._factories.get(request)!.load(request, parent, () => { throw new Error('CANNOT LOAD MODULE from here.'); }); + } + return undefined; + } +} + +export class ExtHostExtensionService extends AbstractExtHostExtensionService { + + private _fakeModules: WorkerRequireInterceptor; + + protected async _beforeAlmostReadyToRunExtensions(): Promise { + // initialize API and register actors + const apiFactory = this._instaService.invokeFunction(createApiFactoryAndRegisterActors); + this._fakeModules = this._instaService.createInstance(WorkerRequireInterceptor, apiFactory, this._registry); + await this._fakeModules.install(); + } + + protected _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + + (self).window = self; // <- that's improper but might help extensions that aren't authored correctly + + // FAKE require function that only works for the vscode-module + const moduleStack: URI[] = []; + (self).require = (mod: string) => { + + const parent = moduleStack[moduleStack.length - 1]; + const result = this._fakeModules.getModule(mod, parent); + + if (result !== undefined) { + return result; + } + + if (!startsWith(mod, '.')) { + throw new Error(`Cannot load module '${mod}'`); + } + + const next = joinPath(parent, '..', ensureSuffix(mod, '.js')); + moduleStack.push(next); + const trap = ExportsTrap.Instance.add(next.toString()); + importScripts(asDomUri(next).toString(true)); + moduleStack.pop(); + + return trap.claim(); + }; + + try { + activationTimesBuilder.codeLoadingStart(); + module = module.with({ path: ensureSuffix(module.path, '.js') }); + moduleStack.push(module); + const trap = ExportsTrap.Instance.add(module.toString()); + importScripts(asDomUri(module).toString(true)); + moduleStack.pop(); + return Promise.resolve(trap.claim()); + + } finally { + activationTimesBuilder.codeLoadingStop(); + } + } + + async $setRemoteEnvironment(_env: { [key: string]: string | null }): Promise { + throw new Error('Not supported'); + } +} + +// todo@joh this is a copy of `dom.ts#asDomUri` +function asDomUri(uri: URI): URI { + if (Schemas.vscodeRemote === uri.scheme) { + // rewrite vscode-remote-uris to uris of the window location + // so that they can be intercepted by the service worker + return URI.parse(window.location.href).with({ path: '/vscode-remote', query: JSON.stringify(uri) }); + } + return uri; +} + +function ensureSuffix(path: string, suffix: string): string { + return endsWith(path, suffix) ? path : path + suffix; +} diff --git a/src/vs/workbench/api/worker/extHostLogService.ts b/src/vs/workbench/api/worker/extHostLogService.ts new file mode 100644 index 00000000000..79211c61945 --- /dev/null +++ b/src/vs/workbench/api/worker/extHostLogService.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log'; +import { ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; +import * as vscode from 'vscode'; + +export class ExtHostLogService extends AbstractLogService implements ILogService, ExtHostLogServiceShape { + + _serviceBrand: any; + + private readonly _logChannel: vscode.OutputChannel; + + constructor( + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostOutputService extHostOutputService: IExtHostOutputService + ) { + super(); + this.setLevel(initData.logLevel); + this._logChannel = extHostOutputService.createOutputChannel('Log (Worker Extension Host)'); + } + + $setLevel(level: LogLevel): void { + this.setLevel(level); + } + + trace(_message: string, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Trace) { + this._logChannel.appendLine(this._format(arguments)); + } + } + + debug(_message: string, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Debug) { + this._logChannel.appendLine(this._format(arguments)); + } + } + + info(_message: string, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Info) { + this._logChannel.appendLine(this._format(arguments)); + } + } + + warn(_message: string, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Warning) { + this._logChannel.appendLine(this._format(arguments)); + } + } + + error(_message: string | Error, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Error) { + this._logChannel.appendLine(this._format(arguments)); + } + } + + critical(_message: string | Error, ..._args: any[]): void { + if (this.getLevel() <= LogLevel.Critical) { + this._logChannel.appendLine(String(arguments)); + } + } + + private _format(args: any): string { + let result = ''; + + for (let i = 0; i < args.length; i++) { + let a = args[i]; + + if (typeof a === 'object') { + try { + a = JSON.stringify(a); + } catch (e) { } + } + + result += (i > 0 ? ' ' : '') + a; + } + + return result; + } +} diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index e3d71c85338..aac3da30445 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -156,7 +156,7 @@ export interface IActionBarRegistry { class ActionBarRegistry implements IActionBarRegistry { private readonly actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0; }[] = []; private readonly actionBarContributorInstances: Map = new Map(); - private instantiationService: IInstantiationService; + private instantiationService!: IInstantiationService; start(accessor: ServicesAccessor): void { this.instantiationService = accessor.get(IInstantiationService); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 5fd57fadf7b..42b78700c7a 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/screencast'; + import { Action } from 'vs/base/common/actions'; import { IWindowService } from 'vs/platform/windows/common/windows'; import * as nls from 'vs/nls'; @@ -11,6 +13,7 @@ import { domEvent } from 'vs/base/browser/event'; import { Event } from 'vs/base/common/event'; import { IDisposable, toDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { getDomNodePagePosition, createStyleSheet, createCSSRule, append, $ } from 'vs/base/browser/dom'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -20,6 +23,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { clamp } from 'vs/base/common/numbers'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; export class InspectContextKeysAction extends Action { @@ -96,7 +102,8 @@ export class ToggleScreencastModeAction extends Action { id: string, label: string, @IKeybindingService private readonly keybindingService: IKeybindingService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); } @@ -108,26 +115,17 @@ export class ToggleScreencastModeAction extends Action { return; } - const container = this.layoutService.getWorkbenchElement(); + const disposables = new DisposableStore(); - const mouseMarker = append(container, $('div')); - mouseMarker.style.position = 'absolute'; - mouseMarker.style.border = '2px solid red'; - mouseMarker.style.borderRadius = '20px'; - mouseMarker.style.width = '20px'; - mouseMarker.style.height = '20px'; - mouseMarker.style.top = '0'; - mouseMarker.style.left = '0'; - mouseMarker.style.zIndex = '100000'; - mouseMarker.style.content = ' '; - mouseMarker.style.pointerEvents = 'none'; - mouseMarker.style.display = 'none'; + const container = this.layoutService.getWorkbenchElement(); + const mouseMarker = append(container, $('.screencast-mouse')); + disposables.add(toDisposable(() => mouseMarker.remove())); const onMouseDown = domEvent(container, 'mousedown', true); const onMouseUp = domEvent(container, 'mouseup', true); const onMouseMove = domEvent(container, 'mousemove', true); - const mouseListener = onMouseDown(e => { + disposables.add(onMouseDown(e => { mouseMarker.style.top = `${e.clientY - 10}px`; mouseMarker.style.left = `${e.clientX - 10}px`; mouseMarker.style.display = 'block'; @@ -141,56 +139,59 @@ export class ToggleScreencastModeAction extends Action { mouseMarker.style.display = 'none'; mouseMoveListener.dispose(); }); - }); + })); - const keyboardMarker = append(container, $('div')); - keyboardMarker.style.position = 'absolute'; - keyboardMarker.style.backgroundColor = 'rgba(0, 0, 0 ,0.5)'; - keyboardMarker.style.width = '100%'; - keyboardMarker.style.height = '100px'; - keyboardMarker.style.bottom = '20%'; - keyboardMarker.style.left = '0'; - keyboardMarker.style.zIndex = '100000'; - keyboardMarker.style.pointerEvents = 'none'; - keyboardMarker.style.color = 'white'; - keyboardMarker.style.lineHeight = '100px'; - keyboardMarker.style.textAlign = 'center'; - keyboardMarker.style.fontSize = '56px'; - keyboardMarker.style.display = 'none'; + const keyboardMarker = append(container, $('.screencast-keyboard')); + disposables.add(toDisposable(() => keyboardMarker.remove())); + + const updateKeyboardMarker = () => { + keyboardMarker.style.bottom = `${clamp(this.configurationService.getValue('screencastMode.verticalOffset') || 0, 0, 90)}%`; + }; + + updateKeyboardMarker(); + disposables.add(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('screencastMode.verticalOffset')) { + updateKeyboardMarker(); + } + })); const onKeyDown = domEvent(container, 'keydown', true); let keyboardTimeout: IDisposable = Disposable.None; + let length = 0; - const keyboardListener = onKeyDown(e => { + disposables.add(onKeyDown(e => { keyboardTimeout.dispose(); const event = new StandardKeyboardEvent(e); - const keybinding = this.keybindingService.resolveKeyboardEvent(event); - const label = keybinding.getLabel(); + const shortcut = this.keybindingService.softDispatch(event, event.target); - if (!event.ctrlKey && !event.altKey && !event.metaKey && !event.shiftKey && this.keybindingService.mightProducePrintableCharacter(event) && label) { - keyboardMarker.textContent += ' ' + label; - } else { - keyboardMarker.textContent = label; + if (shortcut || !this.configurationService.getValue('screencastMode.onlyKeyboardShortcuts')) { + if ( + event.ctrlKey || event.altKey || event.metaKey || event.shiftKey + || length > 20 + || event.keyCode === KeyCode.Backspace || event.keyCode === KeyCode.Escape + ) { + keyboardMarker.innerHTML = ''; + length = 0; + } + + const keybinding = this.keybindingService.resolveKeyboardEvent(event); + const label = keybinding.getLabel(); + const key = $('span.key', {}, label || ''); + length++; + append(keyboardMarker, key); } - keyboardMarker.style.display = 'block'; - const promise = timeout(800); keyboardTimeout = toDisposable(() => promise.cancel()); promise.then(() => { keyboardMarker.textContent = ''; - keyboardMarker.style.display = 'none'; + length = 0; }); - }); + })); - ToggleScreencastModeAction.disposable = toDisposable(() => { - mouseListener.dispose(); - keyboardListener.dispose(); - mouseMarker.remove(); - keyboardMarker.remove(); - }); + ToggleScreencastModeAction.disposable = disposables; } } @@ -215,8 +216,35 @@ export class LogStorageAction extends Action { } } +// --- Actions Registration + const developerCategory = nls.localize('developer', "Developer"); const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); \ No newline at end of file +registry.registerWorkbenchAction(new SyncActionDescriptor(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); + +// --- Menu Registration + +// Screencast Mode +const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); +configurationRegistry.registerConfiguration({ + id: 'screencastMode', + order: 9, + title: nls.localize('screencastModeConfigurationTitle', "Screencast Mode"), + type: 'object', + properties: { + 'screencastMode.verticalOffset': { + type: 'number', + default: 20, + minimum: 0, + maximum: 90, + description: nls.localize('screencastMode.location.verticalPosition', "Controls the vertical offset of the screencast mode overlay from the bottom as a percentage of the workbench height.") + }, + 'screencastMode.onlyKeyboardShortcuts': { + type: 'boolean', + description: nls.localize('screencastMode.onlyKeyboardShortcuts', "Only show keyboard shortcuts in Screencast Mode."), + default: false + } + } +}); diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 634dcd69599..7de343ab1d3 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -320,6 +320,38 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.focusParent', + weight: KeybindingWeight.WorkbenchContrib, + when: WorkbenchListFocusContextKey, + handler: (accessor) => { + const focused = accessor.get(IListService).lastFocusedList; + + if (!focused || focused instanceof List || focused instanceof PagedList) { + return; + } + + if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { + const tree = focused; + const focusedElements = tree.getFocus(); + if (focusedElements.length === 0) { + return; + } + const focus = focusedElements[0]; + const parent = tree.getParentElement(focus); + if (parent) { + const fakeKeyboardEvent = new KeyboardEvent('keydown'); + tree.setFocus([parent], fakeKeyboardEvent); + tree.reveal(parent); + } + } else { + const tree = focused; + tree.focusParent({ origin: 'keyboard' }); + } + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.expand', weight: KeybindingWeight.WorkbenchContrib, @@ -749,11 +781,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ if (focused instanceof List || focused instanceof PagedList) { const list = focused; - if (list.getSelection().length > 0) { - list.setSelection([]); - } else if (list.getFocus().length > 0) { - list.setFocus([]); - } + list.setSelection([]); + list.setFocus([]); } // ObjectTree @@ -761,22 +790,16 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const list = focused; const fakeKeyboardEvent = new KeyboardEvent('keydown'); - if (list.getSelection().length > 0) { - list.setSelection([], fakeKeyboardEvent); - } else if (list.getFocus().length > 0) { - list.setFocus([], fakeKeyboardEvent); - } + list.setSelection([], fakeKeyboardEvent); + list.setFocus([], fakeKeyboardEvent); } // Tree else if (focused) { const tree = focused; - if (tree.getSelection().length) { - tree.clearSelection({ origin: 'keyboard' }); - } else if (tree.getFocus()) { - tree.clearFocus({ origin: 'keyboard' }); - } + tree.clearSelection({ origin: 'keyboard' }); + tree.clearFocus({ origin: 'keyboard' }); } } }); diff --git a/src/vs/workbench/browser/actions/media/screencast.css b/src/vs/workbench/browser/actions/media/screencast.css new file mode 100644 index 00000000000..b4b71b36726 --- /dev/null +++ b/src/vs/workbench/browser/actions/media/screencast.css @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .screencast-mouse { + position: absolute; + border: 2px solid red; + border-radius: 20px; + width: 20px; + height: 20px; + top: 0; + left: 0; + z-index: 100000; + content: ' '; + pointer-events: none; + display: none; +} + +.monaco-workbench .screencast-keyboard { + position: absolute; + background-color: rgba(0, 0, 0 ,0.5); + width: 100%; + height: 100px; + bottom: 20%; + left: 0; + z-index: 100000; + pointer-events: none; + color: #eee; + line-height: 100px; + text-align: center; + font-size: 56px; + transition: opacity 0.3s ease-out; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.monaco-workbench .screencast-keyboard:empty { + opacity: 0; +} + +.monaco-workbench .screencast-keyboard > .key { + padding: 0 8px; + box-shadow: inset 0 -3px 0 hsla(0,0%,73%,.4); + margin-right: 6px; + border: 1px solid hsla(0,0%,80%,.4); + border-radius: 5px; + background-color: rgba(255, 255, 255, 0.05); +} diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 838875097c8..928ab7ae0af 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -7,11 +7,11 @@ import 'vs/css!./media/actions'; import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { IWindowService, IURIToOpen, IWindowsService } from 'vs/platform/windows/common/windows'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IsFullscreenContext, IsDevelopmentContext } from 'vs/workbench/browser/contextkeys'; +import { IsFullscreenContext, IsDevelopmentContext, IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -226,6 +226,24 @@ export class ReloadWindowAction extends Action { } } +export class ShowAboutDialogAction extends Action { + + static readonly ID = 'workbench.action.showAboutDialog'; + static readonly LABEL = nls.localize('about', "About"); + + constructor( + id: string, + label: string, + @IWindowsService private readonly windowsService: IWindowsService + ) { + super(id, label); + } + + run(): Promise { + return this.windowsService.openAboutDialog(); + } +} + const registry = Registry.as(Extensions.WorkbenchActions); // --- Actions Registration @@ -240,6 +258,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction const developerCategory = nls.localize('developer', "Developer"); registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL), 'Developer: Reload Window', developerCategory); +const helpCategory = nls.localize('help', "Help"); +registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), `Help: About`, helpCategory); + // --- Commands/Keybindings Registration const recentFilesPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(inRecentFilesPickerContextKey)); @@ -297,4 +318,14 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { toggled: IsFullscreenContext }, order: 1 -}); \ No newline at end of file +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: 'z_about', + command: { + id: ShowAboutDialogAction.ID, + title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") + }, + order: 1, + when: IsMacNativeContext.toNegated() +}); diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 3cd1005e09d..1d6ee2b796f 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -11,7 +11,7 @@ import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/p import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands'; +import { ICommandService, ICommandHandler, CommandsRegistry } 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 { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -20,6 +20,9 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { toResource } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class OpenFileAction extends Action { @@ -305,3 +308,19 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { return this.windowService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); } } + +// --- Menu Registration + +const workspacesCategory = nls.localize('workspaces', "Workspaces"); + +CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { + serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run(); +}); + +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: OpenWorkspaceConfigFileAction.ID, + title: { value: `${workspacesCategory}: ${OpenWorkspaceConfigFileAction.LABEL}`, original: 'Workspaces: Open Workspace Configuration File' }, + }, + when: WorkbenchStateContext.isEqualTo('workspace') +}); diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index 123e9a9b692..bab3dcfb849 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -35,7 +35,7 @@ export abstract class Composite extends Component implements IComposite { private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; - private _onDidFocus: Emitter; + private _onDidFocus!: Emitter; get onDidFocus(): Event { if (!this._onDidFocus) { this.registerFocusTrackEvents(); @@ -50,7 +50,7 @@ export abstract class Composite extends Component implements IComposite { } } - private _onDidBlur: Emitter; + private _onDidBlur!: Emitter; get onDidBlur(): Event { if (!this._onDidBlur) { this.registerFocusTrackEvents(); @@ -68,10 +68,10 @@ export abstract class Composite extends Component implements IComposite { this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire())); } - protected actionRunner: IActionRunner; + protected actionRunner: IActionRunner | undefined; private visible: boolean; - private parent: HTMLElement; + private parent!: HTMLElement; constructor( id: string, diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index b1de1c5bdc6..10bb2639e9e 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; -import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -52,17 +52,20 @@ export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; private activeEditorContext: IContextKey; + + private activeEditorGroupEmpty: IContextKey; + private activeEditorGroupIndex: IContextKey; + private activeEditorGroupLast: IContextKey; + private multipleEditorGroupsContext: IContextKey; + private editorsVisibleContext: IContextKey; private textCompareEditorVisibleContext: IContextKey; private textCompareEditorActiveContext: IContextKey; - private activeEditorGroupEmpty: IContextKey; - private multipleEditorGroupsContext: IContextKey; private splitEditorsVerticallyContext: IContextKey; private workbenchStateContext: IContextKey; private workspaceFolderCountContext: IContextKey; - private inZenModeContext: IContextKey; private isFullscreenContext: IContextKey; private isCenteredLayoutContext: IContextKey; @@ -90,8 +93,10 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys())); this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys())); + this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorContextKeys())); this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorContextKeys())); + this._register(this.editorGroupService.onDidGroupIndexChange(() => this.updateEditorContextKeys())); this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true)); @@ -141,6 +146,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this.textCompareEditorVisibleContext = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorActiveContext = TextCompareEditorActiveContext.bindTo(this.contextKeyService); this.activeEditorGroupEmpty = ActiveEditorGroupEmptyContext.bindTo(this.contextKeyService); + this.activeEditorGroupIndex = ActiveEditorGroupIndexContext.bindTo(this.contextKeyService); + this.activeEditorGroupLast = ActiveEditorGroupLastContext.bindTo(this.contextKeyService); this.multipleEditorGroupsContext = MultipleEditorGroupsContext.bindTo(this.contextKeyService); // Inputs @@ -176,6 +183,7 @@ export class WorkbenchContextKeysHandler extends Disposable { } private updateEditorContextKeys(): void { + const activeGroup = this.editorGroupService.activeGroup; const activeControl = this.editorService.activeControl; const visibleEditors = this.editorService.visibleControls; @@ -194,12 +202,16 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorGroupEmpty.reset(); } - if (this.editorGroupService.count > 1) { + const groupCount = this.editorGroupService.count; + if (groupCount > 1) { this.multipleEditorGroupsContext.set(true); } else { this.multipleEditorGroupsContext.reset(); } + this.activeEditorGroupIndex.set(activeGroup.index); + this.activeEditorGroupLast.set(activeGroup.index === groupCount - 1); + if (activeControl) { this.activeEditorContext.set(activeControl.getId()); } else { @@ -250,4 +262,4 @@ export class WorkbenchContextKeysHandler extends Disposable { private updateSideBarContextKeys(): void { this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART)); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index cf1d6b5b60f..b99f800164d 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -113,7 +113,7 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array r.resource.fsPath === file.path) /* prevent duplicates */) { + if (file && file.path /* Electron only */ && !resources.some(r => r.resource.fsPath === file.path) /* prevent duplicates */) { try { resources.push({ resource: URI.file(file.path), isExternal: true }); } catch (error) { diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 9db4979a3aa..35a19a119b4 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -317,40 +317,34 @@ class ResourceLabelWidget extends IconLabel { } notifyFormattersChange(): void { - if (this.label && this.label.resource) { - this.setFile(this.label.resource, this.options); - } this.render(false); } setResource(label: IResourceLabelProps, options?: IResourceLabelOptions): void { - const hasResourceChanged = this.hasResourceChanged(label, options); + const hasPathLabelChanged = this.hasPathLabelChanged(label, options); + const clearIconCache = this.clearIconCache(label, options); this.label = label; this.options = options; - if (hasResourceChanged) { + if (hasPathLabelChanged) { this.computedPathLabel = undefined; // reset path label due to resource change } - this.render(hasResourceChanged); + this.render(clearIconCache); } - private hasResourceChanged(label: IResourceLabelProps, options?: IResourceLabelOptions): boolean { - const newResource = label ? label.resource : undefined; + private clearIconCache(newLabel: IResourceLabelProps, newOptions?: IResourceLabelOptions): boolean { + const newResource = newLabel ? newLabel.resource : undefined; const oldResource = this.label ? this.label.resource : undefined; - const newFileKind = options ? options.fileKind : undefined; + const newFileKind = newOptions ? newOptions.fileKind : undefined; const oldFileKind = this.options ? this.options.fileKind : undefined; if (newFileKind !== oldFileKind) { return true; // same resource but different kind (file, folder) } - if (newResource && this.computedPathLabel !== this.labelService.getUriLabel(newResource)) { - return true; - } - if (newResource && oldResource) { return newResource.toString() !== oldResource.toString(); } @@ -362,6 +356,12 @@ class ResourceLabelWidget extends IconLabel { return true; } + private hasPathLabelChanged(newLabel: IResourceLabelProps, newOptions?: IResourceLabelOptions): boolean { + const newResource = newLabel ? newLabel.resource : undefined; + + return !!newResource && this.computedPathLabel !== this.labelService.getUriLabel(newResource); + } + setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void { this.setResource({ resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), @@ -460,6 +460,7 @@ class ResourceLabelWidget extends IconLabel { } iconLabelOptions.extraClasses = this.computedIconClasses.slice(0); } + if (this.options && this.options.extraClasses) { iconLabelOptions.extraClasses!.push(...this.options.extraClasses); } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a213ed7b024..357a3488de1 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -27,13 +27,15 @@ import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { Sizing, Direction, Grid, SerializableGrid, ISerializableView, ISerializedGrid, GridBranchNode, GridLeafNode, isGridBranchNode } from 'vs/base/browser/ui/grid/grid'; +import { Grid, SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction } from 'vs/base/browser/ui/grid/grid'; import { WorkbenchLegacyLayout } from 'vs/workbench/browser/legacyLayout'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { IFileService } from 'vs/platform/files/common/files'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { coalesce } from 'vs/base/common/arrays'; enum Settings { MENUBAR_VISIBLE = 'window.menuBarVisibility', @@ -45,24 +47,38 @@ enum Settings { ZEN_MODE_RESTORE = 'zenMode.restore', + // TODO @misolori update this when finished + OCTICONS_UPDATE_ENABLED = 'workbench.octiconsUpdate.enabled', } enum Storage { SIDEBAR_HIDDEN = 'workbench.sidebar.hidden', + SIDEBAR_SIZE = 'workbench.sidebar.size', PANEL_HIDDEN = 'workbench.panel.hidden', PANEL_POSITION = 'workbench.panel.location', + PANEL_SIZE = 'workbench.panel.size', PANEL_SIZE_BEFORE_MAXIMIZED = 'workbench.panel.sizeBeforeMaximized', ZEN_MODE_ENABLED = 'workbench.zenmode.active', CENTERED_LAYOUT_ENABLED = 'workbench.centerededitorlayout.active', - GRID_LAYOUT = 'workbench.grid.layout' + GRID_LAYOUT = 'workbench.grid.layout', + GRID_WIDTH = 'workbench.grid.width', + GRID_HEIGHT = 'workbench.grid.height' +} + +enum Classes { + SIDEBAR_HIDDEN = 'nosidebar', + EDITOR_HIDDEN = 'noeditorarea', + PANEL_HIDDEN = 'nopanel', + STATUSBAR_HIDDEN = 'nostatusbar', + FULLSCREEN = 'fullscreen' } export abstract class Layout extends Disposable implements IWorkbenchLayoutService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly _onTitleBarVisibilityChange: Emitter = this._register(new Emitter()); readonly onTitleBarVisibilityChange: Event = this._onTitleBarVisibilityChange.event; @@ -160,6 +176,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi wasSideBarVisible: false, wasPanelVisible: false, transitionDisposables: new DisposableStore() + }, + + // TODO @misolori update this when finished + octiconsUpdate: { + enabled: false } }; @@ -232,6 +253,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) { this._onTitleBarVisibilityChange.fire(); + + if (this.workbenchGrid instanceof SerializableGrid) { + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + } + this.layout(); } } @@ -242,9 +268,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Apply as CSS class if (this.state.fullscreen) { - addClass(this.container, 'fullscreen'); + addClass(this.container, Classes.FULLSCREEN); } else { - removeClass(this.container, 'fullscreen'); + removeClass(this.container, Classes.FULLSCREEN); if (this.state.zenMode.transitionedToFullScreen && this.state.zenMode.active) { this.toggleZenMode(); @@ -254,6 +280,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { this._onTitleBarVisibilityChange.fire(); + + if (this.workbenchGrid instanceof SerializableGrid) { + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + } + this.layout(); // handle title bar when fullscreen changes } @@ -291,17 +322,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const newMenubarVisibility = this.configurationService.getValue(Settings.MENUBAR_VISIBLE); this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); + // TODO @misolori update this when finished + const newOcticonsUpdate = this.configurationService.getValue(Settings.OCTICONS_UPDATE_ENABLED); + this.setOcticonsUpdate(newOcticonsUpdate); + } private setSideBarPosition(position: Position): void { const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); const sideBar = this.getPart(Parts.SIDEBAR_PART); const wasHidden = this.state.sideBar.hidden; - - if (this.state.sideBar.hidden) { - this.setSideBarHidden(false, true /* Skip Layout */); - } - const newPositionValue = (position === Position.LEFT) ? 'left' : 'right'; const oldPositionValue = (this.state.sideBar.position === Position.LEFT) ? 'left' : 'right'; this.state.sideBar.position = position; @@ -322,11 +352,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView).width; } - this.workbenchGrid.removeView(this.sideBarPartView); - this.workbenchGrid.removeView(this.activityBarPartView); - - if (!this.state.panel.hidden && this.state.panel.position === Position.BOTTOM) { - this.workbenchGrid.removeView(this.panelPartView); + if (position === Position.LEFT) { + this.workbenchGrid.moveViewTo(this.activityBarPartView, [1, 0]); + this.workbenchGrid.moveViewTo(this.sideBarPartView, [1, 1]); + } else { + this.workbenchGrid.moveViewTo(this.sideBarPartView, [1, 4]); + this.workbenchGrid.moveViewTo(this.activityBarPartView, [1, 4]); } this.layout(); @@ -407,6 +438,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Zen mode enablement this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE); + // TODO @misolori update this when finished + this.state.octiconsUpdate.enabled = this.configurationService.getValue(Settings.OCTICONS_UPDATE_ENABLED); + this.setOcticonsUpdate(this.state.octiconsUpdate.enabled); + } private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditor[] { @@ -578,7 +613,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.zenMode.active = !this.state.zenMode.active; this.state.zenMode.transitionDisposables.clear(); - const setLineNumbers = (lineNumbers: any) => this.editorService.visibleTextEditorWidgets.forEach(editor => editor.updateOptions({ lineNumbers })); + const setLineNumbers = (lineNumbers?: any) => this.editorService.visibleTextEditorWidgets.forEach(editor => { + // To properly reset line numbers we need to read the configuration for each editor respecting it's uri. + if (!lineNumbers && isCodeEditor(editor) && editor.hasModel()) { + const model = editor.getModel(); + this.configurationService.getValue('editor.lineNumbers', { resource: model.uri }); + } else { + editor.updateOptions({ lineNumbers }); + } + }); // Check if zen mode transitioned to full screen and if now we are out of zen mode // -> we need to go out of full screen (same goes for the centered editor layout) @@ -641,7 +684,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.centerEditorLayout(false, true); } - setLineNumbers(this.configurationService.getValue('editor.lineNumbers')); + setLineNumbers(); // Status bar and activity bar visibility come from settings -> update their visibility. this.doUpdateLayoutConfiguration(true); @@ -684,21 +727,37 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Adjust CSS if (hidden) { - addClass(this.container, 'nostatusbar'); + addClass(this.container, Classes.STATUSBAR_HIDDEN); } else { - removeClass(this.container, 'nostatusbar'); + removeClass(this.container, Classes.STATUSBAR_HIDDEN); + } + + // Propagate to grid + if (this.workbenchGrid instanceof Grid) { + this.workbenchGrid.setViewVisible(this.statusBarPartView, !hidden); } // Layout if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { + if (!(this.workbenchGrid instanceof Grid)) { this.workbenchGrid.layout(); } } } + // TODO @misolori update this when finished + private setOcticonsUpdate(enabled: boolean): void { + this.state.octiconsUpdate.enabled = enabled; + + // Update DOM + if (enabled) { + document.body.dataset.octiconsUpdate = 'enabled'; + } else { + document.body.dataset.octiconsUpdate = ''; + } + + } + protected createWorkbenchLayout(instantiationService: IInstantiationService): void { const titleBar = this.getPart(Parts.TITLEBAR_PART); const editorPart = this.getPart(Parts.EDITOR_PART); @@ -717,64 +776,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.panelPartView = panelPart; this.statusBarPartView = statusBar; - let workbenchGrid: SerializableGrid | undefined; + const viewMap = { + [Parts.ACTIVITYBAR_PART]: this.activityBarPartView, + [Parts.TITLEBAR_PART]: this.titleBarPartView, + [Parts.EDITOR_PART]: this.editorPartView, + [Parts.PANEL_PART]: this.panelPartView, + [Parts.SIDEBAR_PART]: this.sideBarPartView, + [Parts.STATUSBAR_PART]: this.statusBarPartView + }; - const savedGrid = this.storageService.get(Storage.GRID_LAYOUT, StorageScope.GLOBAL, undefined); - if (savedGrid) { - const parsedGrid: ISerializedGrid = JSON.parse(savedGrid); - - const fromJSON = (serializedPart: { type: Parts } | null) => { - if (serializedPart && serializedPart.type) { - switch (serializedPart.type) { - case Parts.ACTIVITYBAR_PART: - return this.activityBarPartView; - case Parts.TITLEBAR_PART: - return this.titleBarPartView; - case Parts.EDITOR_PART: - return this.editorPartView; - case Parts.PANEL_PART: - return this.panelPartView; - case Parts.SIDEBAR_PART: - return this.sideBarPartView; - case Parts.STATUSBAR_PART: - return this.statusBarPartView; - default: - return this.editorPartView; - } - } else { - return this.editorPartView; - } - }; - - try { - workbenchGrid = SerializableGrid.deserialize(parsedGrid, { fromJSON }, { proportionalLayout: false }); - - const root = workbenchGrid.getViews(); - const titleBarSection = root.children[0]; - - if (isGridBranchNode(titleBarSection) || titleBarSection.view !== this.titleBarPartView) { - throw new Error('Bad grid'); - } - - const middleSection = root.children[1] as GridBranchNode; - const sideBarPosition = (middleSection.children[0] as GridLeafNode).view === this.activityBarPartView ? Position.LEFT : Position.RIGHT; - if (sideBarPosition !== this.state.sideBar.position) { - throw new Error('Bad Grid'); - } - - const panelPosition = isGridBranchNode(middleSection.children[2]) || isGridBranchNode(middleSection.children[0]) ? Position.BOTTOM : Position.RIGHT; - if (panelPosition !== this.state.panel.position) { - throw new Error('Bad Grid'); - } - } catch (err) { - workbenchGrid = undefined; - console.error(err); - } - } - - if (!workbenchGrid) { - workbenchGrid = new SerializableGrid(this.editorPartView, { proportionalLayout: false }); - } + const fromJSON = ({ type }: { type: Parts }) => viewMap[type]; + const workbenchGrid = SerializableGrid.deserialize( + this.createGridDescriptor(), + { fromJSON }, + { proportionalLayout: false } + ); this.container.prepend(workbenchGrid.element); this.workbenchGrid = workbenchGrid; @@ -791,15 +807,24 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.setEditorHidden(!visible, true); })); - this._register(this.lifecycleService.onBeforeShutdown(beforeShutdownEvent => { - beforeShutdownEvent.veto(new Promise((resolve) => { - const grid = this.workbenchGrid as SerializableGrid; - const serializedGrid = grid.serialize(); + this._register(this.storageService.onWillSaveState(() => { + const grid = this.workbenchGrid as SerializableGrid; - this.storageService.store(Storage.GRID_LAYOUT, JSON.stringify(serializedGrid), StorageScope.GLOBAL); + const sideBarSize = this.state.sideBar.hidden + ? grid.getViewCachedVisibleSize(this.sideBarPartView) + : grid.getViewSize(this.sideBarPartView).width; - resolve(); - })); + this.storageService.store(Storage.SIDEBAR_SIZE, sideBarSize, StorageScope.GLOBAL); + + const panelSize = this.state.panel.hidden + ? grid.getViewCachedVisibleSize(this.panelPartView) + : (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width); + + this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL); + + const gridSize = grid.getViewSize(); + this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL); + this.storageService.store(Storage.GRID_HEIGHT, gridSize.height, StorageScope.GLOBAL); })); } else { this.workbenchGrid = instantiationService.createInstance( @@ -828,9 +853,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout the grid widget this.workbenchGrid.layout(this._dimension.width, this._dimension.height); - - // Layout grid views - this.layoutGrid(); } else { this.workbenchGrid.layout(options); } @@ -840,95 +862,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - private layoutGrid(): void { - if (!(this.workbenchGrid instanceof Grid)) { - return; - } - - let panelInGrid = this.workbenchGrid.hasView(this.panelPartView); - let sidebarInGrid = this.workbenchGrid.hasView(this.sideBarPartView); - let activityBarInGrid = this.workbenchGrid.hasView(this.activityBarPartView); - let statusBarInGrid = this.workbenchGrid.hasView(this.statusBarPartView); - let titlebarInGrid = this.workbenchGrid.hasView(this.titleBarPartView); - - // Add parts to grid - if (!statusBarInGrid) { - this.workbenchGrid.addView(this.statusBarPartView, Sizing.Split, this.editorPartView, Direction.Down); - statusBarInGrid = true; - } - - if (!titlebarInGrid) { - this.workbenchGrid.addView(this.titleBarPartView, Sizing.Split, this.editorPartView, Direction.Up); - - titlebarInGrid = true; - } - - if (!activityBarInGrid) { - this.workbenchGrid.addView(this.activityBarPartView, Sizing.Split, panelInGrid && this.state.sideBar.position === this.state.panel.position ? this.panelPartView : this.editorPartView, this.state.sideBar.position === Position.RIGHT ? Direction.Right : Direction.Left); - activityBarInGrid = true; - } - - if (!sidebarInGrid) { - this.workbenchGrid.addView(this.sideBarPartView, this.state.sideBar.width !== undefined ? this.state.sideBar.width : Sizing.Split, this.activityBarPartView, this.state.sideBar.position === Position.LEFT ? Direction.Right : Direction.Left); - sidebarInGrid = true; - } - - if (!panelInGrid) { - this.workbenchGrid.addView(this.panelPartView, Sizing.Split, this.editorPartView, this.state.panel.position === Position.BOTTOM ? Direction.Down : Direction.Right); - panelInGrid = true; - } - - // Hide parts - if (this.state.panel.hidden) { - this.workbenchGrid.setViewVisible(this.panelPartView, false); - } - - if (this.state.statusBar.hidden) { - this.workbenchGrid.setViewVisible(this.statusBarPartView, false); - } - - if (titlebarInGrid && !this.isVisible(Parts.TITLEBAR_PART)) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, false); - } - - if (this.state.activityBar.hidden) { - this.workbenchGrid.setViewVisible(this.activityBarPartView, false); - } - - if (this.state.sideBar.hidden) { - this.workbenchGrid.setViewVisible(this.sideBarPartView, false); - } - - if (this.state.editor.hidden) { - this.workbenchGrid.setViewVisible(this.editorPartView, false); - } - - // Show visible parts - if (!this.state.editor.hidden) { - this.workbenchGrid.setViewVisible(this.editorPartView, true); - } - - if (!this.state.statusBar.hidden) { - this.workbenchGrid.setViewVisible(this.statusBarPartView, true); - } - - if (this.isVisible(Parts.TITLEBAR_PART)) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, true); - } - - if (!this.state.activityBar.hidden) { - this.workbenchGrid.setViewVisible(this.activityBarPartView, true); - } - - if (!this.state.sideBar.hidden) { - this.workbenchGrid.setViewVisible(this.sideBarPartView, true); - } - - if (!this.state.panel.hidden) { - this.workbenchGrid.setViewVisible(this.panelPartView, true); - } - } - isEditorLayoutCentered(): boolean { return this.state.editor.centered; } @@ -1019,41 +952,60 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void { this.state.activityBar.hidden = hidden; + // Propagate to grid + if (this.workbenchGrid instanceof Grid) { + this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden); + } + // Layout if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { + if (!(this.workbenchGrid instanceof Grid)) { this.workbenchGrid.layout(); } } } setEditorHidden(hidden: boolean, skipLayout?: boolean): void { - if (!(this.workbenchGrid instanceof Grid) || hidden === this.state.editor.hidden) { + if (!(this.workbenchGrid instanceof Grid)) { return; } this.state.editor.hidden = hidden; - // The editor and the panel cannot be hidden at the same time - if (this.state.editor.hidden && this.state.panel.hidden) { - this.setPanelHidden(false, true); + // Adjust CSS + if (hidden) { + addClass(this.container, Classes.EDITOR_HIDDEN); + } else { + removeClass(this.container, Classes.EDITOR_HIDDEN); } - if (!skipLayout) { - this.layout(); + // Propagate to grid + this.workbenchGrid.setViewVisible(this.editorPartView, !hidden); + + // The editor and panel cannot be hidden at the same time + if (hidden && this.state.panel.hidden) { + this.setPanelHidden(false, true); } } + getLayoutClasses(): string[] { + return coalesce([ + this.state.sideBar.hidden ? Classes.SIDEBAR_HIDDEN : undefined, + this.state.editor.hidden ? Classes.EDITOR_HIDDEN : undefined, + this.state.panel.hidden ? Classes.PANEL_HIDDEN : undefined, + this.state.statusBar.hidden ? Classes.STATUSBAR_HIDDEN : undefined, + this.state.fullscreen ? Classes.FULLSCREEN : undefined + ]); + } + setSideBarHidden(hidden: boolean, skipLayout?: boolean): void { this.state.sideBar.hidden = hidden; // Adjust CSS if (hidden) { - addClass(this.container, 'nosidebar'); + addClass(this.container, Classes.SIDEBAR_HIDDEN); } else { - removeClass(this.container, 'nosidebar'); + removeClass(this.container, Classes.SIDEBAR_HIDDEN); } // If sidebar becomes hidden, also hide the current active Viewlet if any @@ -1080,6 +1032,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + // Propagate to grid + if (this.workbenchGrid instanceof Grid) { + this.workbenchGrid.setViewVisible(this.sideBarPartView, !hidden); + } + // Remember in settings const defaultHidden = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; if (hidden !== defaultHidden) { @@ -1090,9 +1047,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { + if (!(this.workbenchGrid instanceof Grid)) { this.workbenchGrid.layout(); } } @@ -1103,9 +1058,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Adjust CSS if (hidden) { - addClass(this.container, 'nopanel'); + addClass(this.container, Classes.PANEL_HIDDEN); } else { - removeClass(this.container, 'nopanel'); + removeClass(this.container, Classes.PANEL_HIDDEN); } // If panel part becomes hidden, also hide the current active panel if any @@ -1123,6 +1078,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + // Propagate to grid + if (this.workbenchGrid instanceof Grid) { + this.workbenchGrid.setViewVisible(this.panelPartView, !hidden); + } + // Remember in settings if (!hidden) { this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE); @@ -1137,9 +1097,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { + if (!(this.workbenchGrid instanceof Grid)) { this.workbenchGrid.layout(); } } @@ -1147,33 +1105,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi toggleMaximizedPanel(): void { if (this.workbenchGrid instanceof Grid) { - const curSize = this.workbenchGrid.getViewSize(this.panelPartView); - const size = { ...curSize }; - + const size = this.workbenchGrid.getViewSize(this.panelPartView); if (!this.isPanelMaximized()) { - if (this.state.panel.position === Position.BOTTOM) { - size.height = this.panelPartView.maximumHeight; - this.state.panel.sizeBeforeMaximize = curSize.height; - } else { - size.width = this.panelPartView.maximumWidth; - this.state.panel.sizeBeforeMaximize = curSize.width; - } - + this.state.panel.sizeBeforeMaximize = this.state.panel.position === Position.BOTTOM ? size.height : size.width; this.storageService.store(Storage.PANEL_SIZE_BEFORE_MAXIMIZED, this.state.panel.sizeBeforeMaximize, StorageScope.GLOBAL); + this.setEditorHidden(true); } else { - if (this.state.panel.position === Position.BOTTOM) { - size.height = this.state.panel.sizeBeforeMaximize; - } else { - size.width = this.state.panel.sizeBeforeMaximize; - } - - // Unhide the editor if needed - if (this.state.editor.hidden) { - this.setEditorHidden(false); - } + this.setEditorHidden(false); + this.workbenchGrid.resizeView(this.panelPartView, { width: this.state.panel.position === Position.BOTTOM ? size.width : this.state.panel.sizeBeforeMaximize, height: this.state.panel.position === Position.BOTTOM ? this.state.panel.sizeBeforeMaximize : size.height }); } - - this.workbenchGrid.resizeView(this.panelPartView, size); } else { this.workbenchGrid.layout({ toggleMaximizedPanel: true, source: Parts.PANEL_PART }); } @@ -1185,16 +1125,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } if (this.workbenchGrid instanceof Grid) { - try { - // The panel is maximum when the editor is minimum - if (this.state.panel.position === Position.BOTTOM) { - return this.workbenchGrid.getViewSize(this.editorPartView).height <= this.editorPartView.minimumHeight; - } else { - return this.workbenchGrid.getViewSize(this.editorPartView).width <= this.editorPartView.minimumWidth; - } - } catch (e) { - return false; - } + return this.state.editor.hidden; } else { return this.workbenchGrid.isPanelMaximized(); } @@ -1211,8 +1142,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout if (!skipLayout) { if (this.workbenchGrid instanceof Grid) { - const dimensions = getClientArea(this.parent); - this.workbenchGrid.layout(dimensions.width, dimensions.height); + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); } else { this.workbenchGrid.layout(); } @@ -1228,13 +1158,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return this.state.panel.position; } - setPanelPosition(position: Position): void { - const panelPart = this.getPart(Parts.PANEL_PART); - + setPanelPosition(position: Position.BOTTOM | Position.RIGHT): void { if (this.state.panel.hidden) { - this.setPanelHidden(false, true /* Skip Layout */); + this.setPanelHidden(false); } + const panelPart = this.getPart(Parts.PANEL_PART); const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right'; const oldPositionValue = (this.state.panel.position === Position.BOTTOM) ? 'bottom' : 'right'; this.state.panel.position = position; @@ -1258,8 +1187,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout if (this.workbenchGrid instanceof Grid) { - this.workbenchGrid.removeView(this.panelPartView); - this.layout(); + const size = this.workbenchGrid.getViewSize(this.panelPartView); + const sideBarSize = this.workbenchGrid.getViewSize(this.sideBarPartView); + + if (position === Position.BOTTOM) { + this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.height : size.width, this.editorPartView, Direction.Down); + } else { + this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : size.height, this.editorPartView, Direction.Right); + } + + // Reset sidebar to original size before shifting the panel + this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize); } else { this.workbenchGrid.layout(); } @@ -1267,9 +1205,91 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); } + private createGridDescriptor(): ISerializedGrid { + const workbenchDimensions = getClientArea(this.parent); + const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width); + const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height); + // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys + const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))!); + const panelSize = this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3)); + + const titleBarHeight = this.titleBarPartView.minimumHeight; + const statusBarHeight = this.statusBarPartView.minimumHeight; + const activityBarWidth = this.activityBarPartView.minimumWidth; + const middleSectionHeight = height - titleBarHeight - statusBarHeight; + const editorSectionWidth = width - (this.state.activityBar.hidden ? 0 : activityBarWidth) - (this.state.sideBar.hidden ? 0 : sideBarSize); + + const activityBarNode: ISerializedLeafNode = { + type: 'leaf', + data: { type: Parts.ACTIVITYBAR_PART }, + size: activityBarWidth, + visible: !this.state.activityBar.hidden + }; + + const sideBarNode: ISerializedLeafNode = { + type: 'leaf', + data: { type: Parts.SIDEBAR_PART }, + size: sideBarSize, + visible: !this.state.sideBar.hidden + }; + + const editorNode: ISerializedLeafNode = { + type: 'leaf', + data: { type: Parts.EDITOR_PART }, + size: this.state.panel.position === Position.BOTTOM ? middleSectionHeight - (this.state.panel.hidden ? 0 : panelSize) : editorSectionWidth - (this.state.panel.hidden ? 0 : panelSize) + }; + + const panelNode: ISerializedLeafNode = { + type: 'leaf', + data: { type: Parts.PANEL_PART }, + size: panelSize, + visible: !this.state.panel.hidden + }; + + const editorSectionNode: ISerializedNode[] = this.state.panel.position === Position.BOTTOM + ? [{ type: 'branch', data: [editorNode, panelNode], size: editorSectionWidth }] + : [editorNode, panelNode]; + + const middleSection: ISerializedNode[] = this.state.sideBar.position === Position.LEFT + ? [activityBarNode, sideBarNode, ...editorSectionNode] + : [...editorSectionNode, sideBarNode, activityBarNode]; + + const result: ISerializedGrid = { + root: { + type: 'branch', + size: width, + data: [ + { + type: 'leaf', + data: { type: Parts.TITLEBAR_PART }, + size: titleBarHeight, + visible: this.isVisible(Parts.TITLEBAR_PART) + }, + { + type: 'branch', + data: middleSection, + size: middleSectionHeight + }, + { + type: 'leaf', + data: { type: Parts.STATUSBAR_PART }, + size: statusBarHeight, + visible: !this.state.statusBar.hidden + } + ] + }, + orientation: Orientation.VERTICAL, + width, + height + }; + + return result; + } + dispose(): void { super.dispose(); this.disposed = true; } } + diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index 889c107932f..3e1a33fbe2c 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -25,7 +25,7 @@ export class PanelDescriptor extends CompositeDescriptor { } export class PanelRegistry extends CompositeRegistry { - private defaultPanelId: string; + private defaultPanelId!: string; /** * Registers a panel to the platform. diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index d73545fb3ef..5c4c70a2e75 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -171,7 +171,7 @@ export class PlaceHolderViewletActivityAction extends ViewletActivityAction { super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, layoutService, telemetryService); const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar - DOM.createCSSRule(iconClass, `-webkit-mask: url('${DOM.asDomUri(iconUrl) || ''}') no-repeat 50% 50%; -webkit-mask-size: 24px;`); + DOM.createCSSRule(iconClass, `-webkit-mask: ${DOM.asCSSUrl(iconUrl)} no-repeat 50% 50%; -webkit-mask-size: 24px;`); } setActivity(activity: IActivity): void { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index c8ce2bc031f..f2d8631a6c3 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -48,7 +48,7 @@ interface ICachedViewlet { export class ActivitybarPart extends Part implements IActivityBarService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static readonly ACTION_HEIGHT = 48; private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets'; @@ -508,4 +508,4 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } -registerSingleton(IActivityBarService, ActivitybarPart); \ No newline at end of file +registerSingleton(IActivityBarService, ActivitybarPart); diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 69f53744b05..7a39fce8178 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -46,10 +46,10 @@ export interface ICompositeBarOptions { export class CompositeBar extends Widget implements ICompositeBar { - private dimension: Dimension; + private dimension: Dimension | undefined; - private compositeSwitcherBar: ActionBar; - private compositeOverflowAction: CompositeOverflowActivityAction | null; + private compositeSwitcherBar!: ActionBar; + private compositeOverflowAction: CompositeOverflowActivityAction | undefined; private compositeOverflowActionViewItem: CompositeOverflowActivityActionViewItem | undefined; private model: CompositeBarModel; @@ -343,7 +343,7 @@ export class CompositeBar extends Widget implements ICompositeBar { this.compositeSwitcherBar.pull(this.compositeSwitcherBar.length() - 1); this.compositeOverflowAction.dispose(); - this.compositeOverflowAction = null; + this.compositeOverflowAction = undefined; if (this.compositeOverflowActionViewItem) { this.compositeOverflowActionViewItem.dispose(); @@ -460,7 +460,7 @@ interface ICompositeBarModelItem extends ICompositeBarItem { class CompositeBarModel { - private _items: ICompositeBarModelItem[]; + private _items: ICompositeBarModelItem[] = []; private readonly options: ICompositeBarOptions; activeItem?: ICompositeBarModelItem; diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index d3445a9bbb6..d93d9aec8b7 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -124,12 +124,12 @@ export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptio } export class ActivityActionViewItem extends BaseActionViewItem { - protected container: HTMLElement; - protected label: HTMLElement; - protected badge: HTMLElement; - protected options: IActivityActionViewItemOptions; + protected container!: HTMLElement; + protected label!: HTMLElement; + protected badge!: HTMLElement; + protected options!: IActivityActionViewItemOptions; - private badgeContent: HTMLElement; + private badgeContent: HTMLElement | undefined; private readonly badgeDisposable = this._register(new MutableDisposable()); private mouseUpTimeout: any; @@ -347,7 +347,7 @@ export class CompositeOverflowActivityAction extends ActivityAction { } export class CompositeOverflowActivityActionViewItem extends ActivityActionViewItem { - private actions: Action[]; + private actions: Action[] | undefined; constructor( action: ActivityAction, @@ -371,8 +371,8 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI this.contextMenuService.showContextMenu({ getAnchor: () => this.element!, - getActions: () => this.actions, - onHide: () => dispose(this.actions) + getActions: () => this.actions!, + onHide: () => dispose(this.actions!) }); } @@ -402,7 +402,9 @@ export class CompositeOverflowActivityActionViewItem extends ActivityActionViewI dispose(): void { super.dispose(); - this.actions = dispose(this.actions); + if (this.actions) { + this.actions = dispose(this.actions); + } } } @@ -431,7 +433,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { private static manageExtensionAction: ManageExtensionAction; - private compositeActivity: IActivity | null; + private compositeActivity: IActivity | undefined; private compositeTransfer: LocalSelectionTransfer; constructor( @@ -454,7 +456,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); } - this._register(compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = null; this.updateActivity(); }, this)); + this._register(compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = undefined; this.updateActivity(); }, this)); } protected get activity(): IActivity { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index e5d7cfbe7c8..95c15731550 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -58,18 +58,18 @@ export abstract class CompositePart extends Part { protected readonly onDidCompositeOpen = this._register(new Emitter<{ composite: IComposite, focus: boolean }>()); protected readonly onDidCompositeClose = this._register(new Emitter()); - protected toolBar: ToolBar; + protected toolBar!: ToolBar; private mapCompositeToCompositeContainer = new Map(); private mapActionsBindingToComposite = new Map void>(); private activeComposite: Composite | null; private lastActiveCompositeId: string; private instantiatedCompositeItems: Map; - private titleLabel: ICompositeTitleLabel; - private progressBar: ProgressBar; - private contentAreaSize: Dimension; + private titleLabel!: ICompositeTitleLabel; + private progressBar!: ProgressBar; + private contentAreaSize: Dimension | undefined; private readonly telemetryActionsListener = this._register(new MutableDisposable()); - private currentCompositeOpenToken: string; + private currentCompositeOpenToken: string | undefined; constructor( private notificationService: INotificationService, diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index ca8fb69dd8c..0bd6c69fbdc 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -29,7 +29,7 @@ export interface IBreadcrumbsService { export class BreadcrumbsService implements IBreadcrumbsService { - _serviceBrand: ServiceIdentifier; + _serviceBrand: any; private readonly _map = new Map(); @@ -55,8 +55,8 @@ registerSingleton(IBreadcrumbsService, BreadcrumbsService, true); export abstract class BreadcrumbsConfig { - name: string; - onDidChange: Event; + abstract get name(): string; + abstract get onDidChange(): Event; abstract getValue(overrides?: IConfigurationOverrides): T; abstract updateValue(value: T, overrides?: IConfigurationOverrides): Promise; @@ -71,6 +71,7 @@ export abstract class BreadcrumbsConfig { static FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath'); static SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath'); static SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder'); + static Icons = BreadcrumbsConfig._stub('breadcrumbs.icons'); static FileExcludes = BreadcrumbsConfig._stub('files.exclude'); @@ -160,6 +161,11 @@ Registry.as(Extensions.Configuration).registerConfigurat localize('symbolSortOrder.name', "Show symbol outline in alphabetical order."), localize('symbolSortOrder.type', "Show symbol outline in symbol type order."), ] + }, + 'breadcrumbs.icons': { + description: localize('icons', "Render breadcrumb items with icons."), + type: 'boolean', + default: true } } }); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 0614722d536..39927e683a4 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -69,7 +69,9 @@ class Item extends BreadcrumbsItem { return false; } if (this.element instanceof FileElement && other.element instanceof FileElement) { - return isEqual(this.element.uri, other.element.uri, false); + return (isEqual(this.element.uri, other.element.uri, false) && + this.options.showFileIcons === other.options.showFileIcons && + this.options.showSymbolIcons === other.options.showSymbolIcons); } if (this.element instanceof TreeElement && other.element instanceof TreeElement) { return this.element.id === other.element.id; @@ -143,6 +145,7 @@ export class BreadcrumbsControl { private readonly _ckBreadcrumbsActive: IContextKey; private readonly _cfUseQuickPick: BreadcrumbsConfig; + private readonly _cfShowIcons: BreadcrumbsConfig; readonly domNode: HTMLDivElement; private readonly _widget: BreadcrumbsWidget; @@ -185,6 +188,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService); + this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(_configurationService); this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); } @@ -196,6 +200,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible.reset(); this._ckBreadcrumbsActive.reset(); this._cfUseQuickPick.dispose(); + this._cfShowIcons.dispose(); this._widget.dispose(); this.domNode.remove(); } @@ -246,15 +251,23 @@ export class BreadcrumbsControl { dom.toggleClass(this.domNode, 'backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); const updateBreadcrumbs = () => { - const items = model.getElements().map(element => new Item(element, this._options, this._instantiationService)); + const showIcons = this._cfShowIcons.getValue(); + const options: IBreadcrumbsControlOptions = { + ...this._options, + showFileIcons: this._options.showFileIcons && showIcons, + showSymbolIcons: this._options.showSymbolIcons && showIcons + }; + const items = model.getElements().map(element => new Item(element, options, this._instantiationService)); this._widget.setItems(items); this._widget.reveal(items[items.length - 1]); }; const listener = model.onDidUpdate(updateBreadcrumbs); + const configListener = this._cfShowIcons.onDidChange(updateBreadcrumbs); updateBreadcrumbs(); this._breadcrumbsDisposables.clear(); this._breadcrumbsDisposables.add(model); this._breadcrumbsDisposables.add(listener); + this._breadcrumbsDisposables.add(configListener); // close picker on hide/update this._breadcrumbsDisposables.add({ diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 1c4f92cd459..73b3e5f7664 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -9,7 +9,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { size } from 'vs/base/common/collections'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual, dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -36,14 +36,14 @@ type FileInfo = { path: FileElement[], folder?: IWorkspaceFolder }; export class EditorBreadcrumbsModel { - private readonly _disposables: IDisposable[] = []; + private readonly _disposables = new DisposableStore(); private readonly _fileInfo: FileInfo; private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>; private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>; private _outlineElements: Array = []; - private _outlineDisposables: IDisposable[] = []; + private _outlineDisposables = new DisposableStore(); private _onDidUpdate = new Emitter(); readonly onDidUpdate: Event = this._onDidUpdate.event; @@ -58,8 +58,8 @@ export class EditorBreadcrumbsModel { this._cfgFilePath = BreadcrumbsConfig.FilePath.bindTo(configurationService); this._cfgSymbolPath = BreadcrumbsConfig.SymbolPath.bindTo(configurationService); - this._disposables.push(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this))); - this._disposables.push(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this))); + this._disposables.add(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this))); + this._disposables.add(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this))); this._fileInfo = EditorBreadcrumbsModel._initFilePathInfo(this._uri, workspaceService); this._bindToEditor(); @@ -69,7 +69,7 @@ export class EditorBreadcrumbsModel { dispose(): void { this._cfgFilePath.dispose(); this._cfgSymbolPath.dispose(); - dispose(this._disposables); + this._disposables.dispose(); } isRelative(): boolean { @@ -133,20 +133,27 @@ export class EditorBreadcrumbsModel { if (!this._editor) { return; } - // update as model changes - this._disposables.push(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline())); - this._disposables.push(this._editor.onDidChangeModel(_ => this._updateOutline())); - this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._updateOutline())); - this._disposables.push(Event.debounce(this._editor.onDidChangeModelContent, _ => _, 350)(_ => this._updateOutline(true))); + // update as language, model, providers changes + this._disposables.add(DocumentSymbolProviderRegistry.onDidChange(_ => this._updateOutline())); + this._disposables.add(this._editor.onDidChangeModel(_ => this._updateOutline())); + this._disposables.add(this._editor.onDidChangeModelLanguage(_ => this._updateOutline())); + + // update soon'ish as model content change + const updateSoon = new TimeoutTimer(); + this._disposables.add(updateSoon); + this._disposables.add(this._editor.onDidChangeModelContent(_ => { + const timeout = OutlineModel.getRequestDelay(this._editor!.getModel()); + updateSoon.cancelAndSet(() => this._updateOutline(true), timeout); + })); this._updateOutline(); // stop when editor dies - this._disposables.push(this._editor.onDidDispose(() => this._outlineDisposables = dispose(this._outlineDisposables))); + this._disposables.add(this._editor.onDidDispose(() => this._outlineDisposables.clear())); } private _updateOutline(didChangeContent?: boolean): void { - this._outlineDisposables = dispose(this._outlineDisposables); + this._outlineDisposables.clear(); if (!didChangeContent) { this._updateOutlineElements([]); } @@ -162,7 +169,7 @@ export class EditorBreadcrumbsModel { const versionIdThen = buffer.getVersionId(); const timeout = new TimeoutTimer(); - this._outlineDisposables.push({ + this._outlineDisposables.add({ dispose: () => { source.cancel(); source.dispose(); @@ -180,7 +187,7 @@ export class EditorBreadcrumbsModel { model = model.adopt(); this._updateOutlineElements(this._getOutlineElements(model, editor.getPosition())); - this._outlineDisposables.push(editor.onDidChangeCursorPosition(_ => { + this._outlineDisposables.add(editor.onDidChangeCursorPosition(_ => { timeout.cancelAndSet(() => { if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && editor.getModel()) { this._updateOutlineElements(this._getOutlineElements(model, editor.getPosition())); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 42ca9e2d502..16a20027c9e 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -56,11 +56,11 @@ export abstract class BreadcrumbsPicker { protected readonly _disposables = new DisposableStore(); protected readonly _domNode: HTMLDivElement; - protected _arrow: HTMLDivElement; - protected _treeContainer: HTMLDivElement; - protected _tree: Tree; + protected _arrow!: HTMLDivElement; + protected _treeContainer!: HTMLDivElement; + protected _tree!: Tree; protected _fakeEvent = new UIEvent('fakeEvent'); - protected _layoutInfo: ILayoutInfo; + protected _layoutInfo!: ILayoutInfo; private readonly _onDidPickElement = new Emitter(); readonly onDidPickElement: Event = this._onDidPickElement.event; diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 354ef330025..31daf2483f5 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -36,7 +36,7 @@ import { SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction, JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction, EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, - NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction + NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -334,6 +334,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, J registry.registerWorkbenchAction(new SyncActionDescriptor(JoinAllGroupsAction, JoinAllGroupsAction.ID, JoinAllGroupsAction.LABEL), 'View: Join All Editor Groups', category); registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category); registry.registerWorkbenchAction(new SyncActionDescriptor(ResetGroupSizesAction, ResetGroupSizesAction.ID, ResetGroupSizesAction.LABEL), 'View: Reset Editor Group Sizes', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleGroupSizesAction, ToggleGroupSizesAction.ID, ToggleGroupSizesAction.LABEL), 'View: Toggle Editor Group Sizes', category); registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Side Bar', category); registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Maximize Editor Group', category); registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 6ad9ea42706..634812bfa84 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -74,19 +74,22 @@ export interface IEditorOpeningEvent extends IEditorIdentifier { * Allows to prevent the opening of an editor by providing a callback * that will be executed instead. By returning another editor promise * it is possible to override the opening with another editor. It is ok - * to return a promise that resolves to NULL to prevent the opening + * to return a promise that resolves to `undefined` to prevent the opening * alltogether. */ prevent(callback: () => undefined | Promise): void; } export interface IEditorGroupsAccessor { + readonly groups: IEditorGroupView[]; readonly activeGroup: IEditorGroupView; readonly partOptions: IEditorPartOptions; readonly onDidEditorPartOptionsChange: Event; + readonly onDidVisibilityChange: Event; + getGroup(identifier: GroupIdentifier): IEditorGroupView | undefined; getGroups(order: GroupsOrder): IEditorGroupView[]; @@ -106,6 +109,9 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito readonly whenRestored: Promise; readonly disposed: boolean; + readonly isEmpty: boolean; + readonly isMinimized: boolean; + readonly onDidFocus: Event; readonly onWillDispose: Event; readonly onWillOpenEditor: Event; @@ -113,9 +119,10 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito readonly onWillCloseEditor: Event; readonly onDidCloseEditor: Event; - isEmpty(): boolean; setActive(isActive: boolean): void; - setLabel(label: string): void; + + notifyIndexChanged(newIndex: number): void; + relayout(): void; } diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index ba90f283b0a..0464d3131be 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -880,6 +880,22 @@ export class ResetGroupSizesAction extends Action { } } +export class ToggleGroupSizesAction extends Action { + + static readonly ID = 'workbench.action.toggleEditorWidths'; + static readonly LABEL = nls.localize('toggleEditorWidths', "Toggle Editor Group Sizes"); + + constructor(id: string, label: string, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService) { + super(id, label); + } + + run(): Promise { + this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE); + + return Promise.resolve(false); + } +} + export class MaximizeGroupAction extends Action { static readonly ID = 'workbench.action.maximizeEditor'; diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 726b84c97db..70d57bba5ad 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -221,6 +221,12 @@ export class EditorControl extends Disposable { } } + setVisible(visible: boolean): void { + if (this._activeControl) { + this._activeControl.setVisible(visible, this.groupView); + } + } + layout(dimension: Dimension): void { this.dimension = dimension; diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 266f09275db..97cabc264b6 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -409,7 +409,7 @@ class DropOverlay extends Themable { } private getOverlayOffsetHeight(): number { - if (!this.groupView.isEmpty() && this.accessor.partOptions.showTabs) { + if (!this.groupView.isEmpty && this.accessor.partOptions.showTabs) { return EDITOR_TITLE_HEIGHT; // show overlay below title if group shows tabs } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index e5cc3843429..dcfc2b8e358 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -54,16 +54,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region factory - static createNew(accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView { - return instantiationService.createInstance(EditorGroupView, accessor, null, label); + static createNew(accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { + return instantiationService.createInstance(EditorGroupView, accessor, null, index); } - static createFromSerialized(serialized: ISerializedEditorGroup, accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView { - return instantiationService.createInstance(EditorGroupView, accessor, serialized, label); + static createFromSerialized(serialized: ISerializedEditorGroup, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { + return instantiationService.createInstance(EditorGroupView, accessor, serialized, index); } - static createCopy(copyFrom: IEditorGroupView, accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView { - return instantiationService.createInstance(EditorGroupView, accessor, copyFrom, label); + static createCopy(copyFrom: IEditorGroupView, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { + return instantiationService.createInstance(EditorGroupView, accessor, copyFrom, index); } //#endregion @@ -119,7 +119,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { constructor( private accessor: IEditorGroupsAccessor, from: IEditorGroupView | ISerializedEditorGroup, - private _label: string, + private _index: number, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @@ -250,7 +250,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Open new file via doubleclick on empty container this._register(addDisposableListener(this.element, EventType.DBLCLICK, e => { - if (this.isEmpty()) { + if (this.isEmpty) { EventHelper.stop(e); this.openEditor(this.untitledEditorService.createOrGet(), EditorOptions.create({ pinned: true })); @@ -259,7 +259,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Close empty editor group via middle mouse click this._register(addDisposableListener(this.element, EventType.MOUSE_UP, e => { - if (this.isEmpty() && e.button === 1 /* Middle Button */) { + if (this.isEmpty && e.button === 1 /* Middle Button */) { EventHelper.stop(e); this.accessor.removeGroup(this); @@ -308,7 +308,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } private onShowContainerContextMenu(menu: IMenu, e?: MouseEvent): void { - if (!this.isEmpty()) { + if (!this.isEmpty) { return; // only for empty editor groups } @@ -339,7 +339,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Container const containerFocusTracker = this._register(trackFocus(this.element)); this._register(containerFocusTracker.onDidFocus(() => { - if (this.isEmpty()) { + if (this.isEmpty) { this._onDidFocus.fire(); // only when empty to prevent accident focus } })); @@ -381,7 +381,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private updateContainer(): void { // Empty Container: add some empty container attributes - if (this.isEmpty()) { + if (this.isEmpty) { addClass(this.element, 'empty'); this.element.tabIndex = 0; this.element.setAttribute('aria-label', localize('emptyEditorGroup', "{0} (empty)", this.label)); @@ -468,6 +468,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Option Changes this._register(this.accessor.onDidEditorPartOptionsChange(e => this.onDidEditorPartOptionsChange(e))); + + // Visibility + this._register(this.accessor.onDidVisibilityChange(e => this.onDidVisibilityChange(e))); } private onDidEditorPin(editor: EditorInput): void { @@ -591,8 +594,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Title control Switch between showing tabs <=> not showing tabs if (event.oldPartOptions.showTabs !== event.newPartOptions.showTabs) { - this.createTitleAreaControl(); + // Recreate and layout control + this.createTitleAreaControl(); + this.layoutTitleAreaControl(); + + // Ensure to show active editor if any if (this._group.activeEditor) { this.titleAreaControl.openEditor(this._group.activeEditor); } @@ -635,6 +642,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_LABEL, editor }); } + private onDidVisibilityChange(visible: boolean): void { + + // Forward to editor control + this.editorControl.setVisible(visible); + } + //#endregion //region IEditorGroupView @@ -643,8 +656,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group; } + get index(): number { + return this._index; + } + get label(): string { - return this._label; + return localize('groupLabel', "Group {0}", this._index + 1); } get disposed(): boolean { @@ -655,10 +672,22 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._whenRestored; } - setLabel(label: string): void { - if (this._label !== label) { - this._label = label; - this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_LABEL }); + get isEmpty(): boolean { + return this._group.count === 0; + } + + get isMinimized(): boolean { + if (!this.dimension) { + return false; + } + + return this.dimension.width === this.minimumWidth || this.dimension.height === this.minimumHeight; + } + + notifyIndexChanged(newIndex: number): void { + if (this._index !== newIndex) { + this._index = newIndex; + this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_INDEX }); } } @@ -679,10 +708,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_ACTIVE }); } - isEmpty(): boolean { - return this._group.count === 0; - } - //#endregion //#region IEditorGroup @@ -773,7 +798,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region openEditor() - openEditor(editor: EditorInput, options?: EditorOptions): Promise { + async openEditor(editor: EditorInput, options?: EditorOptions): Promise { // Guard against invalid inputs if (!editor) { @@ -789,7 +814,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Proceed with opening - return this.doOpenEditor(editor, options).then(withUndefinedAsNull); + return withUndefinedAsNull(await this.doOpenEditor(editor, options)); } private doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise { @@ -1207,7 +1232,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region closeEditors() async closeEditors(args: EditorInput[] | ICloseEditorsFilter, options?: ICloseEditorOptions): Promise { - if (this.isEmpty()) { + if (this.isEmpty) { return; } @@ -1279,7 +1304,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region closeAllEditors() async closeAllEditors(): Promise { - if (this.isEmpty()) { + if (this.isEmpty) { // If the group is empty and the request is to close all editors, we still close // the editor group is the related setting to close empty groups is enabled for @@ -1391,7 +1416,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region Themable protected updateStyles(): void { - const isEmpty = this.isEmpty(); + const isEmpty = this.isEmpty; // Container if (isEmpty) { @@ -1440,10 +1465,14 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.editorContainer.style.height = `calc(100% - ${this.titleAreaControl.getPreferredHeight()}px)`; // Forward to controls - this.titleAreaControl.layout(new Dimension(this.dimension.width, this.titleAreaControl.getPreferredHeight())); + this.layoutTitleAreaControl(); this.editorControl.layout(new Dimension(this.dimension.width, this.dimension.height - this.titleAreaControl.getPreferredHeight())); } + private layoutTitleAreaControl(): void { + this.titleAreaControl.layout(new Dimension(this.dimension.width, this.titleAreaControl.getPreferredHeight())); + } + relayout(): void { if (this.dimension) { const { width, height } = this.dimension; @@ -1463,7 +1492,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onWillDispose.fire(); this.titleAreaControl.dispose(); - // this.editorControl = null; super.dispose(); } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 3d456d54ecc..8bb14cc38a3 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -24,7 +24,6 @@ import { assign } from 'vs/base/common/objects'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget'; -import { localize } from 'vs/nls'; import { Color } from 'vs/base/common/color'; import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -82,7 +81,7 @@ class GridWidgetView implements IView { export class EditorPart extends Part implements IEditorGroupsService, IEditorGroupsAccessor { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.state'; private static readonly EDITOR_PART_CENTERED_VIEW_STORAGE_KEY = 'editorpart.centeredview'; @@ -95,6 +94,9 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private readonly _onDidActiveGroupChange: Emitter = this._register(new Emitter()); readonly onDidActiveGroupChange: Event = this._onDidActiveGroupChange.event; + private readonly _onDidGroupIndexChange: Emitter = this._register(new Emitter()); + readonly onDidGroupIndexChange: Event = this._onDidGroupIndexChange.event; + private readonly _onDidActivateGroup: Emitter = this._register(new Emitter()); readonly onDidActivateGroup: Event = this._onDidActivateGroup.event; @@ -344,15 +346,36 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro return; // we have not been created yet } - // Even all group sizes - if (arrangement === GroupsArrangement.EVEN) { - this.gridWidget.distributeViewSizes(); + switch (arrangement) { + case GroupsArrangement.EVEN: + this.gridWidget.distributeViewSizes(); + break; + case GroupsArrangement.MINIMIZE_OTHERS: + this.gridWidget.maximizeViewSize(this.activeGroup); + break; + case GroupsArrangement.TOGGLE: + if (this.isGroupMaximized(this.activeGroup)) { + this.arrangeGroups(GroupsArrangement.EVEN); + } else { + this.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); + } + + break; + } + } + + private isGroupMaximized(targetGroup: IEditorGroupView): boolean { + for (const group of this.groups) { + if (group === targetGroup) { + continue; // ignore target group + } + + if (!group.isMinimized) { + return false; // target cannot be maximized if one group is not minimized + } } - // Maximize the current active group - else { - this.gridWidget.maximizeViewSize(this.activeGroup); - } + return true; } setGroupOrientation(orientation: GroupOrientation): void { @@ -424,8 +447,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } }); - // Update labels - this.updateGroupLabels(); + // Notify group index change given layout has changed + this.notifyGroupIndexChange(); // Restore focus as needed if (restoreFocus) { @@ -484,25 +507,22 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Event this._onDidAddGroup.fire(newGroupView); - // Update labels - this.updateGroupLabels(); + // Notify group index change given a new group was added + this.notifyGroupIndexChange(); return newGroupView; } private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroup | null): IEditorGroupView { - // Label: just use the number of existing groups as label - const label = this.getGroupLabel(this.count + 1); - // Create group view let groupView: IEditorGroupView; if (from instanceof EditorGroupView) { - groupView = EditorGroupView.createCopy(from, this, label, this.instantiationService); + groupView = EditorGroupView.createCopy(from, this, this.count, this.instantiationService); } else if (isSerializedEditorGroup(from)) { - groupView = EditorGroupView.createFromSerialized(from, this, label, this.instantiationService); + groupView = EditorGroupView.createFromSerialized(from, this, this.count, this.instantiationService); } else { - groupView = EditorGroupView.createNew(this, label, this.instantiationService); + groupView = EditorGroupView.createNew(this, this.count, this.instantiationService); } // Keep in map @@ -516,8 +536,13 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Track editor change groupDisposables.add(groupView.onDidGroupChange(e => { - if (e.kind === GroupChangeKind.EDITOR_ACTIVE) { - this.updateContainer(); + switch (e.kind) { + case GroupChangeKind.EDITOR_ACTIVE: + this.updateContainer(); + break; + case GroupChangeKind.GROUP_INDEX: + this._onDidGroupIndexChange.fire(groupView); + break; } })); @@ -600,7 +625,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } // Remove empty group - if (groupView.isEmpty()) { + if (groupView.isEmpty) { return this.doRemoveEmptyGroup(groupView); } @@ -643,8 +668,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this._activeGroup.focus(); } - // Update labels - this.updateGroupLabels(); + // Notify group index change given a group was removed + this.notifyGroupIndexChange(); // Update container this.updateContainer(); @@ -675,6 +700,9 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Event this._onDidMoveGroup.fire(sourceView); + // Notify group index change given a group was moved + this.notifyGroupIndexChange(); + return sourceView; } @@ -715,7 +743,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro }); // Remove source if the view is now empty and not already removed - if (sourceView.isEmpty() && !sourceView.disposed /* could have been disposed already via workbench.editor.closeEmptyGroups setting */) { + if (sourceView.isEmpty && !sourceView.disposed /* could have been disposed already via workbench.editor.closeEmptyGroups setting */) { this.removeGroup(sourceView); } @@ -784,6 +812,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro centerLayout(active: boolean): void { this.centeredLayoutWidget.activate(active); + this._activeGroup.focus(); } @@ -813,6 +842,9 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Update container this.updateContainer(); + + // Notify group index change we created the entire grid + this.notifyGroupIndexChange(); } private doCreateGridControlWithPreviousState(): boolean { @@ -906,26 +938,15 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } private updateContainer(): void { - toggleClass(this.container, 'empty', this.isEmpty()); + toggleClass(this.container, 'empty', this.isEmpty); } - private updateGroupLabels(): void { - - // Since our labels are created using the index of the - // group, adding/removing a group might produce gaps. - // So we iterate over all groups and reassign the label - // based on the index. - this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach((group, index) => { - group.setLabel(this.getGroupLabel(index + 1)); - }); + private notifyGroupIndexChange(): void { + this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach((group, index) => group.notifyIndexChanged(index)); } - private getGroupLabel(index: number): string { - return localize('groupLabel', "Group {0}", index); - } - - private isEmpty(): boolean { - return this.groupViews.size === 1 && this._activeGroup.isEmpty(); + private get isEmpty(): boolean { + return this.groupViews.size === 1 && this._activeGroup.isEmpty; } layout(width: number, height: number): void { @@ -957,7 +978,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro mostRecentActiveGroups: this.mostRecentActiveGroups }; - if (this.isEmpty()) { + if (this.isEmpty) { delete this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY]; } else { this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState; diff --git a/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css b/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css index d3850e29307..1e6e29ee935 100644 --- a/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css @@ -17,6 +17,10 @@ text-decoration-line: underline; } +.monaco-workbench .monaco-breadcrumb-item.shows-symbol-icon .symbol-icon.block { + padding-right: 6px; +} + /* todo@joh move somewhere else */ .monaco-workbench .monaco-breadcrumbs-picker .arrow { diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css b/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css index d5b23828af9..7f59f8471fb 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css @@ -66,4 +66,4 @@ .vs .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action { background-image: url('tree-expanded-light.svg'); -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css index f97e52b3507..719b6265b00 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css @@ -91,6 +91,7 @@ .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container { display: none; + height: 22px; } .monaco-workbench .notifications-list-container .notification-list-item:hover .notification-list-item-toolbar-container, @@ -142,4 +143,4 @@ .monaco-workbench .notifications-list-container .progress-bit { height: 2px; bottom: 0; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index adef1d21348..a0077787b11 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -285,4 +285,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (notificationBorderColor) { collector.addRule(`.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row[data-last-element="false"] > .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index acb18855f3b..69c224d207b 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -11,12 +11,12 @@ import { localize } from 'vs/nls'; export class NotificationsStatus extends Disposable { - private notificationsCenterStatusItem: IStatusbarEntryAccessor; + private notificationsCenterStatusItem: IStatusbarEntryAccessor | undefined; private currentNotifications = new Set(); private currentStatusMessage: [IStatusMessageViewItem, IDisposable] | undefined; - private isNotificationsCenterVisible: boolean; + private isNotificationsCenterVisible: boolean | undefined; constructor( private model: INotificationsModel, @@ -172,4 +172,4 @@ export class NotificationsStatus extends Disposable { // Remember as current status message this.currentStatusMessage = [item, statusMessageDispose]; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 1e52e099f54..03887faece7 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -62,7 +62,7 @@ export class NotificationsListDelegate implements IListVirtualDelegate { - const action = notification.actions.primary![index]; + const action = primaryActions[index]; button.label = action.label; this.inputDisposables.add(button.onDidClick(e => { diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 4839f8b0831..fb9a8f4bf2f 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -25,11 +25,19 @@ border-top-style: solid; } +.monaco-workbench.noeditorarea .part.panel.bottom .title { + border-top-width: 0; /* no border when editor area is hiden */ +} + .monaco-workbench .part.panel.right { border-left-width: 1px; border-left-style: solid; } +.monaco-workbench.noeditorarea .part.panel.right { + border-left-width: 0; /* no border when editor area is hiden */ +} + .monaco-workbench .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label { outline-offset: -2px; } @@ -181,4 +189,4 @@ .hc-black .monaco-workbench .panel.right .minimize-panel-action { background-image: url('chevron-right-hc.svg'); -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 1189df45c45..b864241fdea 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -48,7 +48,7 @@ export class PanelPart extends CompositePart implements IPanelService { private static readonly PINNED_PANELS = 'workbench.panel.pinnedPanels'; private static readonly MIN_COMPOSITE_BAR_WIDTH = 50; - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; //#region IView @@ -83,8 +83,8 @@ export class PanelPart extends CompositePart implements IPanelService { private compositeBar: CompositeBar; private compositeActions: Map = new Map(); - private blockOpeningPanel: boolean; - private _contentDimension: Dimension; + private blockOpeningPanel = false; + private _contentDimension: Dimension | undefined; constructor( @INotificationService notificationService: INotificationService, @@ -367,7 +367,7 @@ export class PanelPart extends CompositePart implements IPanelService { private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { if (e.key === PanelPart.PINNED_PANELS && e.scope === StorageScope.GLOBAL && this.cachedPanelsValue !== this.getStoredCachedPanelsValue() /* This checks if current window changed the value or not */) { - this._cachedPanelsValue = null; + this._cachedPanelsValue = undefined; const newCompositeItems: ICompositeBarItem[] = []; const compositeItems = this.compositeBar.getCompositeBarItems(); const cachedPanels = this.getCachedPanels(); @@ -422,7 +422,7 @@ export class PanelPart extends CompositePart implements IPanelService { return cachedPanels; } - private _cachedPanelsValue: string | null; + private _cachedPanelsValue: string | undefined; 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 08e3ff9a240..437bd45dfc6 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -880,7 +880,7 @@ class InputBox extends QuickInput implements IInputBox { export class QuickInputService extends Component implements IQuickInputService { - public _serviceBrand: ServiceIdentifier; + public _serviceBrand!: ServiceIdentifier; private static readonly ID = 'workbench.component.quickinput'; private static readonly MAX_WIDTH = 600; // Max total width of quick open widget diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 97bc546f7e8..b685f8ccabc 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -114,7 +114,7 @@ class ListElementRenderer implements IListRenderer; + _serviceBrand!: ServiceIdentifier; private readonly _onShow: Emitter = this._register(new Emitter()); readonly onShow: Event = this._onShow.event; @@ -861,4 +861,4 @@ export class RemoveFromEditorHistoryAction extends Action { } } -registerSingleton(IQuickOpenService, QuickOpenController, true); \ No newline at end of file +registerSingleton(IQuickOpenService, QuickOpenController, true); diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 8b50a07b662..9e17bf39cc4 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -31,10 +31,11 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { LayoutPriority } from 'vs/base/browser/ui/grid/grid'; export class SidebarPart extends CompositePart implements IViewletService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; static readonly activeViewletSettingsKey = 'workbench.sidebar.activeviewletid'; @@ -45,6 +46,8 @@ export class SidebarPart extends CompositePart implements IViewletServi readonly minimumHeight: number = 0; readonly maximumHeight: number = Number.POSITIVE_INFINITY; + readonly priority: LayoutPriority = LayoutPriority.Low; + readonly snap = true; get preferredWidth(): number | undefined { @@ -60,7 +63,7 @@ export class SidebarPart extends CompositePart implements IViewletServi return; } - return width; + return Math.max(width, 300); } //#endregion @@ -79,7 +82,7 @@ export class SidebarPart extends CompositePart implements IViewletServi private viewletRegistry: ViewletRegistry; private sideBarFocusContextKey: IContextKey; private activeViewletContextKey: IContextKey; - private blockOpeningViewlet: boolean; + private blockOpeningViewlet = false; constructor( @INotificationService notificationService: INotificationService, diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 089cbd3003e..6813f378920 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -323,7 +323,7 @@ class HideStatusbarEntryAction extends Action { export class StatusbarPart extends Part implements IStatusbarService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; //#region IView diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 7b7ca5972b1..0a45e07062e 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IMenubarMenu, IMenubarMenuItemAction, IMenubarMenuItemSubmenu, IMenubarKeybinding, IMenubarService, IMenubarData, MenubarMenuItem } from 'vs/platform/menubar/common/menubar'; import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; import { IWindowService, MenuBarVisibility, IWindowsService, getTitleBarStyle, IURIToOpen } from 'vs/platform/windows/common/windows'; @@ -33,7 +32,6 @@ import { attachMenuStyler } from 'vs/platform/theme/common/styler'; import { assign } from 'vs/base/common/objects'; import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { isFullscreen } from 'vs/base/browser/browser'; @@ -133,7 +131,7 @@ export abstract class MenubarControl extends Disposable { this.menuUpdater.schedule(); } - protected calculateActionLabel(action: IAction | IMenubarMenuItemAction): string { + protected calculateActionLabel(action: { id: string; label: string; }): string { let label = action.label; switch (action.id) { default: @@ -251,195 +249,6 @@ export abstract class MenubarControl extends Disposable { } } -export class NativeMenubarControl extends MenubarControl { - constructor( - @IMenuService menuService: IMenuService, - @IWindowService windowService: IWindowService, - @IWindowsService windowsService: IWindowsService, - @IContextKeyService contextKeyService: IContextKeyService, - @IKeybindingService keybindingService: IKeybindingService, - @IConfigurationService configurationService: IConfigurationService, - @ILabelService labelService: ILabelService, - @IUpdateService updateService: IUpdateService, - @IStorageService storageService: IStorageService, - @INotificationService notificationService: INotificationService, - @IPreferencesService preferencesService: IPreferencesService, - @IEnvironmentService environmentService: IEnvironmentService, - @IAccessibilityService accessibilityService: IAccessibilityService, - @IMenubarService private readonly menubarService: IMenubarService - ) { - super( - menuService, - windowService, - windowsService, - contextKeyService, - keybindingService, - configurationService, - labelService, - updateService, - storageService, - notificationService, - preferencesService, - environmentService, - accessibilityService); - - if (isMacintosh && !isWeb) { - this.menus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService)); - this.topLevelTitles['Preferences'] = nls.localize('mPreferences', "Preferences"); - } - - for (const topLevelMenuName of Object.keys(this.topLevelTitles)) { - const menu = this.menus[topLevelMenuName]; - if (menu) { - this._register(menu.onDidChange(() => this.updateMenubar())); - } - } - - this.windowService.getRecentlyOpened().then((recentlyOpened) => { - this.recentlyOpened = recentlyOpened; - - this.doUpdateMenubar(true); - }); - - this.registerListeners(); - } - - protected doUpdateMenubar(firstTime: boolean): void { - // Send menus to main process to be rendered by Electron - const menubarData = { menus: {}, keybindings: {} }; - if (this.getMenubarMenus(menubarData)) { - this.menubarService.updateMenubar(this.windowService.windowId, menubarData); - } - } - - private getMenubarMenus(menubarData: IMenubarData): boolean { - if (!menubarData) { - return false; - } - - menubarData.keybindings = this.getAdditionalKeybindings(); - for (const topLevelMenuName of Object.keys(this.topLevelTitles)) { - const menu = this.menus[topLevelMenuName]; - if (menu) { - const menubarMenu: IMenubarMenu = { items: [] }; - this.populateMenuItems(menu, menubarMenu, menubarData.keybindings); - if (menubarMenu.items.length === 0) { - // Menus are incomplete - return false; - } - menubarData.menus[topLevelMenuName] = menubarMenu; - } - } - - return true; - } - - private populateMenuItems(menu: IMenu, menuToPopulate: IMenubarMenu, keybindings: { [id: string]: IMenubarKeybinding | undefined }) { - let groups = menu.getActions(); - for (let group of groups) { - const [, actions] = group; - - actions.forEach(menuItem => { - - if (menuItem instanceof SubmenuItemAction) { - const submenu = { items: [] }; - - if (!this.menus[menuItem.item.submenu]) { - this.menus[menuItem.item.submenu] = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService); - this._register(this.menus[menuItem.item.submenu]!.onDidChange(() => this.updateMenubar())); - } - - const menuToDispose = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService); - this.populateMenuItems(menuToDispose, submenu, keybindings); - - let menubarSubmenuItem: IMenubarMenuItemSubmenu = { - id: menuItem.id, - label: menuItem.label, - submenu: submenu - }; - - menuToPopulate.items.push(menubarSubmenuItem); - menuToDispose.dispose(); - } else { - if (menuItem.id === 'workbench.action.openRecent') { - const actions = this.getOpenRecentActions().map(this.transformOpenRecentAction); - menuToPopulate.items.push(...actions); - } - - let menubarMenuItem: IMenubarMenuItemAction = { - id: menuItem.id, - label: menuItem.label - }; - - if (menuItem.checked) { - menubarMenuItem.checked = true; - } - - if (!menuItem.enabled) { - menubarMenuItem.enabled = false; - } - - menubarMenuItem.label = this.calculateActionLabel(menubarMenuItem); - keybindings[menuItem.id] = this.getMenubarKeybinding(menuItem.id); - menuToPopulate.items.push(menubarMenuItem); - } - }); - - menuToPopulate.items.push({ id: 'vscode.menubar.separator' }); - } - - if (menuToPopulate.items.length > 0) { - menuToPopulate.items.pop(); - } - } - - private transformOpenRecentAction(action: Separator | (IAction & { uri: URI })): MenubarMenuItem { - if (action instanceof Separator) { - return { id: 'vscode.menubar.separator' }; - } - - return { - id: action.id, - uri: action.uri, - enabled: action.enabled, - label: action.label - }; - } - - private getAdditionalKeybindings(): { [id: string]: IMenubarKeybinding } { - const keybindings: { [id: string]: IMenubarKeybinding } = {}; - if (isMacintosh) { - const keybinding = this.getMenubarKeybinding('workbench.action.quit'); - if (keybinding) { - keybindings['workbench.action.quit'] = keybinding; - } - } - - return keybindings; - } - - private getMenubarKeybinding(id: string): IMenubarKeybinding | undefined { - const binding = this.keybindingService.lookupKeybinding(id); - if (!binding) { - return undefined; - } - - // first try to resolve a native accelerator - const electronAccelerator = binding.getElectronAccelerator(); - if (electronAccelerator) { - return { label: electronAccelerator, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) }; - } - - // we need this fallback to support keybindings that cannot show in electron menus (e.g. chords) - const acceleratorLabel = binding.getLabel(); - if (acceleratorLabel) { - return { label: acceleratorLabel, isNative: false, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) }; - } - - return undefined; - } -} - export class CustomMenubarControl extends MenubarControl { private menubar: MenuBar; private container: HTMLElement; @@ -586,7 +395,7 @@ export class CustomMenubarControl extends MenubarControl { this.updateService.checkForUpdates({ windowId })); case StateType.CheckingForUpdates: - return new Action('update.checking', nls.localize('checkingForUpdates', "Checking For Updates..."), undefined, false); + return new Action('update.checking', nls.localize('checkingForUpdates', "Checking for Updates..."), undefined, false); case StateType.AvailableForDownload: return new Action('update.downloadNow', nls.localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Now"), undefined, true, () => diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index f6b0a6849f9..fbf3423014f 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -27,7 +27,7 @@ import { URI } from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; import { trim } from 'vs/base/common/strings'; import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; -import { MenubarControl, NativeMenubarControl, CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; +import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { template, getBaseLabel } from 'vs/base/common/labels'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -58,14 +58,14 @@ export class TitlebarPart extends Part implements ITitleService { private _onMenubarVisibilityChange = this._register(new Emitter()); readonly onMenubarVisibilityChange: Event = this._onMenubarVisibilityChange.event; - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private title: HTMLElement; private dragRegion: HTMLElement; private windowControls: HTMLElement; private maxRestoreControl: HTMLElement; private appIcon: HTMLElement; - private menubarPart: MenubarControl; + private customMenubar: CustomMenubarControl | undefined; private menubar: HTMLElement; private resizer: HTMLElement; private lastLayoutDimensions: Dimension; @@ -332,19 +332,16 @@ export class TitlebarPart extends Part implements ITitleService { }))); } - // Menubar: the menubar part which is responsible for populating both the custom and native menubars - if ((isMacintosh && !isWeb) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native') { - this.menubarPart = this.instantiationService.createInstance(NativeMenubarControl); - } else { - const customMenubarControl = this.instantiationService.createInstance(CustomMenubarControl); - this.menubarPart = customMenubarControl; + // Menubar: install a custom menu bar depending on configuration + if (getTitleBarStyle(this.configurationService, this.environmentService) !== 'native' && (!isMacintosh || isWeb)) { + this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl)); this.menubar = append(this.element, $('div.menubar')); this.menubar.setAttribute('role', 'menubar'); - customMenubarControl.create(this.menubar); + this.customMenubar.create(this.menubar); - this._register(customMenubarControl.onVisibilityChange(e => this.onMenubarVisibilityChanged(e))); - this._register(customMenubarControl.onFocusStateChange(e => this.onMenubarFocusChanged(e))); + this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e))); + this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e))); } // Title @@ -547,7 +544,7 @@ export class TitlebarPart extends Part implements ITitleService { } private adjustTitleMarginToCenter(): void { - if (this.menubarPart instanceof CustomMenubarControl) { + if (this.customMenubar) { const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10; const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10; @@ -588,9 +585,9 @@ export class TitlebarPart extends Part implements ITitleService { runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter()); - if (this.menubarPart instanceof CustomMenubarControl) { + if (this.customMenubar) { const menubarDimension = new Dimension(0, dimension.height); - this.menubarPart.layout(menubarDimension); + this.customMenubar.layout(menubarDimension); } } } @@ -639,4 +636,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } }); -registerSingleton(ITitleService, TitlebarPart); \ No newline at end of file +registerSingleton(ITitleService, TitlebarPart); diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 9dedb437fcf..2fcbf196e0d 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -33,18 +33,14 @@ import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/v import { localize } from 'vs/nls'; import { timeout } from 'vs/base/common/async'; import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { isString } from 'vs/base/common/types'; -import { renderMarkdown, RenderOptions } from 'vs/base/browser/htmlContentRenderer'; -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 { Registry } from 'vs/platform/registry/common/platform'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; +import { isFalsyOrWhitespace } from 'vs/base/common/strings'; export class CustomTreeViewPanel extends ViewletPanel { @@ -73,11 +69,13 @@ export class CustomTreeViewPanel extends ViewletPanel { } renderBody(container: HTMLElement): void { - this.treeView.show(container); + if (this.treeView instanceof CustomTreeView) { + this.treeView.show(container); + } } - layoutBody(size: number): void { - this.treeView.layout(size); + layoutBody(height: number, width: number): void { + this.treeView.layout(height, width); } getActions(): IAction[] { @@ -165,19 +163,18 @@ export class CustomTreeView extends Disposable implements ITreeView { private _showCollapseAllAction = false; private focused: boolean = false; - private domNode: HTMLElement; - private treeContainer: HTMLElement; - private _messageValue: string | IMarkdownString | undefined; - private messageElement: HTMLDivElement; - private tree: WorkbenchAsyncDataTree; - private treeLabels: ResourceLabels; + private domNode!: HTMLElement; + private treeContainer!: HTMLElement; + private _messageValue: string | undefined; + private _canSelectMany: boolean = false; + private messageElement!: HTMLDivElement; + private tree: WorkbenchAsyncDataTree | undefined; + private treeLabels: ResourceLabels | undefined; + private root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; private menus: TitleMenus; - private markdownRenderer: MarkdownRenderer; - private markdownResult: IMarkdownRenderResult | null; - private readonly _onDidExpandItem: Emitter = this._register(new Emitter()); readonly onDidExpandItem: Event = this._onDidExpandItem.event; @@ -217,12 +214,6 @@ export class CustomTreeView extends Disposable implements ITreeView { this.doRefresh([this.root]); /** soft refresh **/ } })); - this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer); - this._register(toDisposable(() => { - if (this.markdownResult) { - this.markdownResult.dispose(); - } - })); this._register(Registry.as(Extensions.ViewsRegistry).onDidChangeContainer(({ views, from, to }) => { if (from === this.viewContainer && views.some(v => v.id === this.id)) { this.viewContainer = to; @@ -231,12 +222,12 @@ export class CustomTreeView extends Disposable implements ITreeView { this.create(); } - private _dataProvider: ITreeViewDataProvider | null; - get dataProvider(): ITreeViewDataProvider | null { + private _dataProvider: ITreeViewDataProvider | undefined; + get dataProvider(): ITreeViewDataProvider | undefined { return this._dataProvider; } - set dataProvider(dataProvider: ITreeViewDataProvider | null) { + set dataProvider(dataProvider: ITreeViewDataProvider | undefined) { if (dataProvider) { this._dataProvider = new class implements ITreeViewDataProvider { async getChildren(node: ITreeItem): Promise { @@ -251,21 +242,29 @@ export class CustomTreeView extends Disposable implements ITreeView { this.updateMessage(); this.refresh(); } else { - this._dataProvider = null; + this._dataProvider = undefined; this.updateMessage(); } } - private _message: string | IMarkdownString | undefined; - get message(): string | IMarkdownString | undefined { + private _message: string | undefined; + get message(): string | undefined { return this._message; } - set message(message: string | IMarkdownString | undefined) { + set message(message: string | undefined) { this._message = message; this.updateMessage(); } + get canSelectMany(): boolean { + return this._canSelectMany; + } + + set canSelectMany(canSelectMany: boolean) { + this._canSelectMany = canSelectMany; + } + get hasIconForParentNode(): boolean { return this._hasIconForParentNode; } @@ -385,12 +384,14 @@ export class CustomTreeView extends Disposable implements ITreeView { collapseByDefault: (e: ITreeItem): boolean => { return e.collapsibleState !== TreeItemCollapsibleState.Expanded; }, - multipleSelectionSupport: false - })); + multipleSelectionSupport: this.canSelectMany, + }) as WorkbenchAsyncDataTree); aligner.tree = this.tree; + const actionRunner = new MultipleSelectionActionRunner(() => this.tree!.getSelection()); + renderer.actionRunner = actionRunner; this.tree.contextKeyService.createKey(this.id, true); - this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(treeMenus, e, actionRunner))); this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.elements))); this._register(this.tree.onDidChangeCollapseState(e => { if (!e.node.element) { @@ -412,14 +413,14 @@ export class CustomTreeView extends Disposable implements ITreeView { if (!e.browserEvent) { return; } - const selection = this.tree.getSelection(); + const selection = this.tree!.getSelection(); if ((selection.length === 1) && selection[0].command) { this.commandService.executeCommand(selection[0].command.id, ...(selection[0].command.arguments || [])); } })); } - private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent): void { + private onContextMenu(treeMenus: TreeMenus, treeEvent: ITreeContextMenuEvent, actionRunner: MultipleSelectionActionRunner): void { const node: ITreeItem | null = treeEvent.element; if (node === null) { return; @@ -429,7 +430,7 @@ export class CustomTreeView extends Disposable implements ITreeView { event.preventDefault(); event.stopPropagation(); - this.tree.setFocus([node]); + this.tree!.setFocus([node]); const actions = treeMenus.getResourceContextActions(node); if (!actions.length) { return; @@ -449,13 +450,13 @@ export class CustomTreeView extends Disposable implements ITreeView { onHide: (wasCancelled?: boolean) => { if (wasCancelled) { - this.tree.domFocus(); + this.tree!.domFocus(); } }, getActionsContext: () => ({ $treeViewId: this.id, $treeItemHandle: node.handle }), - actionRunner: new MultipleSelectionActionRunner(() => this.tree.getSelection()) + actionRunner }); } @@ -470,43 +471,38 @@ export class CustomTreeView extends Disposable implements ITreeView { this.updateContentAreas(); } - private showMessage(message: string | IMarkdownString): void { + private showMessage(message: string): void { DOM.removeClass(this.messageElement, 'hide'); if (this._messageValue !== message) { this.resetMessageElement(); this._messageValue = message; - if (isString(this._messageValue)) { + if (!isFalsyOrWhitespace(this._message)) { this.messageElement.textContent = this._messageValue; - } else { - this.markdownResult = this.markdownRenderer.render(this._messageValue); - DOM.append(this.messageElement, this.markdownResult.element); } - this.layout(this._size); + this.layout(this._height, this._width); } } private hideMessage(): void { this.resetMessageElement(); DOM.addClass(this.messageElement, 'hide'); - this.layout(this._size); + this.layout(this._height, this._width); } private resetMessageElement(): void { - if (this.markdownResult) { - this.markdownResult.dispose(); - this.markdownResult = null; - } DOM.clearNode(this.messageElement); } - private _size: number; - layout(size: number) { - if (size) { - this._size = size; - const treeSize = size - DOM.getTotalHeight(this.messageElement); - this.treeContainer.style.height = treeSize + 'px'; + private _height: number = 0; + private _width: number = 0; + layout(height: number, width: number) { + if (height && width) { + this._height = height; + this._width = width; + const treeHeight = height - DOM.getTotalHeight(this.messageElement); + this.treeContainer.style.height = treeHeight + 'px'; if (this.tree) { - this.tree.layout(treeSize); + this.tree.layout(treeHeight, width); } } } @@ -550,10 +546,11 @@ export class CustomTreeView extends Disposable implements ITreeView { } async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise { - if (this.tree) { + const tree = this.tree; + if (tree) { itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems]; await Promise.all(itemOrItems.map(element => { - return this.tree.expand(element, false); + return tree.expand(element, false); })); } return Promise.resolve(undefined); @@ -593,10 +590,19 @@ export class CustomTreeView extends Disposable implements ITreeView { private refreshing: boolean = false; private async doRefresh(elements: ITreeItem[]): Promise { - if (this.tree) { + const tree = this.tree; + if (tree) { this.refreshing = true; - await Promise.all(elements.map(element => this.tree.updateChildren(element, true))); - elements.map(element => this.tree.rerender(element)); + const parents: Set = new Set(); + elements.forEach(element => { + if (element !== this.root) { + const parent = tree.getParentElement(element); + parents.add(parent); + } else { + parents.add(element); + } + }); + await Promise.all(Array.from(parents.values()).map(element => tree.updateChildren(element, true))); this.refreshing = false; this.updateContentAreas(); if (this.focused) { @@ -694,6 +700,8 @@ class TreeRenderer extends Disposable implements ITreeRenderer{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }); + templateData.actionBar.context = { $treeViewId: this.treeViewId, $treeItemHandle: node.handle }; templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); + if (this._actionRunner) { + templateData.actionBar.actionRunner = this._actionRunner; + } this.setAlignment(templateData.container, node); templateData.elementDisposable = (this.themeService.onDidFileIconThemeChange(() => this.setAlignment(templateData.container, node))); } @@ -782,7 +797,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer; + private _tree: WorkbenchAsyncDataTree | undefined; constructor(private themeService: IWorkbenchThemeService) { super(); @@ -800,11 +815,15 @@ class Aligner extends Disposable { return false; } - const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput(); - if (this.hasIcon(parent)) { + if (this._tree) { + const parent: ITreeItem = this._tree.getParentElement(treeItem) || this._tree.getInput(); + if (this.hasIcon(parent)) { + return false; + } + return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); + } else { return false; } - return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); } private hasIcon(node: ITreeItem): boolean { @@ -826,23 +845,23 @@ class Aligner extends Disposable { class MultipleSelectionActionRunner extends ActionRunner { - constructor(private getSelectedResources: (() => any[])) { + constructor(private getSelectedResources: (() => ITreeItem[])) { super(); } - runAction(action: IAction, context: any): Promise { - if (action instanceof MenuItemAction) { - const selection = this.getSelectedResources(); - const filteredSelection = selection.filter(s => s !== context); - - if (selection.length === filteredSelection.length || selection.length === 1) { - return action.run(context); - } - - return action.run(context, ...filteredSelection); + runAction(action: IAction, context: TreeViewItemHandleArg): Promise { + const selection = this.getSelectedResources(); + let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; + if (selection.length > 1) { + selectionHandleArgs = []; + selection.forEach(selected => { + if (selected.handle !== context.$treeItemHandle) { + selectionHandleArgs!.push({ $treeViewId: context.$treeViewId, $treeItemHandle: selected.handle }); + } + }); } - return super.runAction(action, context); + return action.run(...[context, selectionHandleArgs]); } } @@ -883,38 +902,3 @@ class TreeMenus extends Disposable implements IDisposable { } } -class MarkdownRenderer { - - constructor( - @IOpenerService private readonly _openerService: IOpenerService - ) { - } - - private getOptions(disposeables: DisposableStore): RenderOptions { - return { - actionHandler: { - callback: (content) => { - let uri: URI | undefined; - try { - uri = URI.parse(content); - } catch { - // ignore - } - if (uri && this._openerService) { - this._openerService.open(uri).catch(onUnexpectedError); - } - }, - disposeables - } - }; - } - - render(markdown: IMarkdownString): IMarkdownRenderResult { - const disposeables = new DisposableStore(); - const element: HTMLElement = markdown ? renderMarkdown(markdown, this.getOptions(disposeables)) : document.createElement('span'); - return { - element, - dispose: () => disposeables.dispose() - }; - } -} diff --git a/src/vs/workbench/browser/parts/views/media/panelviewlet.css b/src/vs/workbench/browser/parts/views/media/panelviewlet.css index e1ce2ee1b60..e022f97beb8 100644 --- a/src/vs/workbench/browser/parts/views/media/panelviewlet.css +++ b/src/vs/workbench/browser/parts/views/media/panelviewlet.css @@ -7,6 +7,10 @@ border-top: none !important; /* less clutter: do not show any border for first views in a panel */ } +.monaco-panel-view .panel > .panel-header > .actions.show { + display: initial; +} + .monaco-panel-view .panel > .panel-header h3.title { white-space: nowrap; text-overflow: ellipsis; diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index ef9dbe0d75f..4a5a2a0cbc3 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -41,6 +41,7 @@ export interface IViewletPanelOptions extends IPanelOptions { actionRunner?: IActionRunner; id: string; title: string; + showActionsAlways?: boolean; } export abstract class ViewletPanel extends Panel implements IView { @@ -67,6 +68,7 @@ export abstract class ViewletPanel extends Panel implements IView { protected actionRunner?: IActionRunner; protected toolbar: ToolBar; + private readonly showActionsAlways: boolean = false; private headerContainer: HTMLElement; private titleContainer: HTMLElement; @@ -82,6 +84,7 @@ export abstract class ViewletPanel extends Panel implements IView { this.id = options.id; this.title = options.title; this.actionRunner = options.actionRunner; + this.showActionsAlways = !!options.showActionsAlways; this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService); } @@ -133,6 +136,7 @@ export abstract class ViewletPanel extends Panel implements IView { this.renderHeaderTitle(container, this.title); const actions = append(container, $('.actions')); + toggleClass(actions, 'show', this.showActionsAlways); this.toolbar = new ToolBar(actions, this.contextMenuService, { orientation: ActionsOrientation.HORIZONTAL, actionViewItemProvider: action => this.getActionViewItem(action), diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 0cd0c4bb50b..23accec76fa 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -382,7 +382,19 @@ export class ContributableViewsModel extends Disposable { return 0; } - return (this.getViewOrder(a) - this.getViewOrder(b)) || (a.id < b.id ? -1 : 1); + return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a, b) || (a.id < b.id ? -1 : 1); + } + + private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) { + if (!a.group || !b.group) { + return 0; + } + + if (a.group === b.group) { + return 0; + } + + return a.group < b.group ? -1 : 1; } private getViewOrder(viewDescriptor: IViewDescriptor): number { @@ -626,7 +638,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { export class ViewsService extends Disposable implements IViewsService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly viewDescriptorCollections: Map; private readonly viewDisposable: Map; diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index befbcf39cf6..368e56b41c7 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -76,7 +76,7 @@ export const Extensions = { }; export class ViewletRegistry extends CompositeRegistry { - private defaultViewletId: string; + private defaultViewletId!: string; /** * Registers a viewlet to the platform. diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index f37f298931d..dcb44e8c137 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -14,7 +14,7 @@ import { Workbench } from 'vs/workbench/browser/workbench'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService, IProductConfiguration } from 'vs/platform/product/common/product'; import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentServiceImpl'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -33,21 +33,18 @@ import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/browser/signService'; import { hash } from 'vs/base/common/hash'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; -import { ProductService } from 'vs/platform/product/browser/productService'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { BACKUPS } from 'vs/platform/environment/common/environment'; import { joinPath } from 'vs/base/common/resources'; import { BrowserStorageService } from 'vs/platform/storage/browser/storageService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { RequestService } from 'vs/workbench/services/request/browser/requestService'; import { InMemoryUserDataProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; +import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; +import { StaticExtensionsService, IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; class CodeRendererMain extends Disposable { - private workbench: Workbench; - constructor( private readonly domElement: HTMLElement, private readonly configuration: IWorkbenchConstructionOptions @@ -65,24 +62,35 @@ class CodeRendererMain extends Disposable { this.restoreBaseTheme(); // Create Workbench - this.workbench = new Workbench( + const workbench = new Workbench( this.domElement, services.serviceCollection, services.logService ); // Layout - this._register(addDisposableListener(window, EventType.RESIZE, () => this.workbench.layout())); + this._register(addDisposableListener(window, EventType.RESIZE, () => workbench.layout())); // Workbench Lifecycle - this._register(this.workbench.onShutdown(() => this.dispose())); - this._register(this.workbench.onWillShutdown(() => { + this._register(workbench.onBeforeShutdown(event => { + if (services.storageService.hasPendingUpdate) { + console.warn('Unload prevented: pending storage update'); + event.veto(true); // prevent data loss from pending storage update + } + })); + this._register(workbench.onWillShutdown(() => { services.storageService.close(); this.saveBaseTheme(); })); + this._register(workbench.onShutdown(() => this.dispose())); + + // Driver + if (this.configuration.driver) { + registerWindowDriver().then(d => this._register(d)); + } // Startup - this.workbench.startup(); + workbench.startup(); } private restoreBaseTheme(): void { @@ -105,7 +113,7 @@ class CodeRendererMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE. - // CONTRIBUTE IT VIA WORKBENCH.MAIN.TS AND registerSingleton(). + // CONTRIBUTE IT VIA WORKBENCH.WEB.MAIN.TS AND registerSingleton(). // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Log @@ -115,15 +123,11 @@ class CodeRendererMain extends Disposable { const payload = await this.resolveWorkspaceInitializationPayload(); // Environment - const environmentService = new BrowserWorkbenchEnvironmentService({ - workspaceId: payload.id, - remoteAuthority: this.configuration.remoteAuthority, - webviewEndpoint: this.configuration.webviewEndpoint - }); + const environmentService = new BrowserWorkbenchEnvironmentService(payload.id, this.configuration); serviceCollection.set(IWorkbenchEnvironmentService, environmentService); // Product - const productService = new ProductService(); + const productService = this.createProductService(); serviceCollection.set(IProductService, productService); // Remote @@ -131,7 +135,7 @@ class CodeRendererMain extends Disposable { serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); // Signing - const signService = new SignService(this.configuration.connectionToken); + const signService = new SignService(environmentService.configuration.connectionToken); serviceCollection.set(ISignService, signService); // Remote Agent @@ -142,6 +146,10 @@ class CodeRendererMain extends Disposable { const fileService = this._register(new FileService(logService)); serviceCollection.set(IFileService, fileService); + // Static Extensions + const staticExtensions = new StaticExtensionsService(this.configuration.staticExtensions || []); + serviceCollection.set(IStaticExtensionsService, staticExtensions); + let userDataProvider: IFileSystemProvider | undefined = this.configuration.userDataProvider; const connection = remoteAgentService.getConnection(); if (connection) { @@ -165,7 +173,7 @@ class CodeRendererMain extends Disposable { // User Data Provider fileService.registerProvider(Schemas.userData, userDataProvider); - const [configurationService, storageService] = await Promise.all([ + const services = await Promise.all([ this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, logService).then(service => { // Workspace @@ -186,10 +194,19 @@ class CodeRendererMain extends Disposable { }) ]); - // Request Service - serviceCollection.set(IRequestService, new RequestService(this.configuration.requestHandler, remoteAgentService, configurationService, logService)); + return { serviceCollection, logService, storageService: services[1] }; + } - return { serviceCollection, logService, storageService }; + private createProductService(): IProductService { + const element = document.getElementById('vscode-remote-product-configuration'); + const productConfiguration: IProductConfiguration = { + ...element ? JSON.parse(element.getAttribute('data-settings')!) : { + version: '1.38.0-unknown', + nameLong: 'Unknown', + extensionAllowedProposedApi: [], + }, ...{ urlProtocol: '' } + }; + return { _serviceBrand: undefined, ...productConfiguration }; } private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise { diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 3a5be568517..ce24f3df7bf 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -8,9 +8,8 @@ import * as browser from 'vs/base/browser/browser'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IGalleryExtension, IExtensionIdentifier, IReportedExtension, IExtensionManagementService, ILocalExtension, IGalleryMetadata } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionType, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; import { ConsoleLogService, ILogService } from 'vs/platform/log/common/log'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -22,21 +21,24 @@ import { IRecentlyOpened, IRecent, isRecentFile, isRecentFolder } from 'vs/platf import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; // tslint:disable-next-line: import-patterns -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { addDisposableListener, EventType, windowOpenNoOpener } from 'vs/base/browser/dom'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { pathsToEditors } from 'vs/workbench/common/editor'; import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProductService } from 'vs/platform/product/common/product'; +import Severity from 'vs/base/common/severity'; +import { localize } from 'vs/nls'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; // tslint:disable-next-line: import-patterns -import { IExperimentService, IExperiment, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService'; -import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; //#region Extension Tips @@ -77,63 +79,6 @@ registerSingleton(IExtensionTipsService, SimpleExtensionTipsService, true); //#endregion -export class SimpleExtensionManagementService implements IExtensionManagementService { - - _serviceBrand: any; - - onInstallExtension = Event.None; - onDidInstallExtension = Event.None; - onUninstallExtension = Event.None; - onDidUninstallExtension = Event.None; - - zip(extension: ILocalExtension): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - unzip(zipLocation: URI, type: ExtensionType): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - install(vsix: URI): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - installFromGallery(extension: IGalleryExtension): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - uninstall(extension: ILocalExtension, force?: boolean): Promise { - return Promise.resolve(undefined); - } - - reinstallFromGallery(extension: ILocalExtension): Promise { - return Promise.resolve(undefined); - } - - getInstalled(type?: ExtensionType): Promise { - // @ts-ignore - return Promise.resolve([]); - } - - getExtensionsReport(): Promise { - // @ts-ignore - return Promise.resolve([]); - } - - updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { - // @ts-ignore - return Promise.resolve(local); - } -} - -registerSingleton(IExtensionManagementService, SimpleExtensionManagementService); - -//#endregion - //#region Extension URL Handler export const IExtensionUrlHandler = createDecorator('inactiveExtensionUrlHandler'); @@ -163,63 +108,6 @@ export class SimpleLogService extends ConsoleLogService { } //#endregion -//#region Multi Extension Management - -export class SimpleMultiExtensionsManagementService implements IExtensionManagementService { - - _serviceBrand: any; - - onInstallExtension = Event.None; - onDidInstallExtension = Event.None; - onUninstallExtension = Event.None; - onDidUninstallExtension = Event.None; - - zip(extension: ILocalExtension): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - unzip(zipLocation: URI, type: ExtensionType): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - install(vsix: URI): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - installFromGallery(extension: IGalleryExtension): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - uninstall(extension: ILocalExtension, force?: boolean): Promise { - return Promise.resolve(undefined); - } - - reinstallFromGallery(extension: ILocalExtension): Promise { - return Promise.resolve(undefined); - } - - getInstalled(type?: ExtensionType): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - getExtensionsReport(): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } -} - -//#endregion - //#region Update export class SimpleUpdateService implements IUpdateService { @@ -293,7 +181,8 @@ export class SimpleWindowService extends Disposable implements IWindowService { @IConfigurationService private readonly configurationService: IConfigurationService, @IStorageService private readonly storageService: IStorageService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { super(); @@ -489,7 +378,7 @@ export class SimpleWindowService extends Disposable implements IWindowService { for (let i = 0; i < _uris.length; i++) { const uri = _uris[i]; if ('folderUri' in uri) { - const newAddress = `${document.location.origin}/?folder=${uri.folderUri.path}`; + const newAddress = `${document.location.origin}/?folder=${uri.folderUri.path}${this.workbenchEnvironmentService.configuration.connectionToken ? `&tkn=${this.workbenchEnvironmentService.configuration.connectionToken}` : ''}`; if (openFolderInNewWindow) { window.open(newAddress); } else { @@ -523,6 +412,8 @@ export class SimpleWindowService extends Disposable implements IWindowService { } closeWindow(): Promise { + window.close(); + return Promise.resolve(); } @@ -559,41 +450,6 @@ registerSingleton(IWindowService, SimpleWindowService); //#endregion -//#region ExtensionHostDebugService - -export class SimpleExtensionHostDebugService extends ExtensionHostDebugChannelClient { - - constructor( - @IRemoteAgentService remoteAgentService: IRemoteAgentService, - //@IWindowService windowService: IWindowService, - @IEnvironmentService environmentService: IEnvironmentService - ) { - const connection = remoteAgentService.getConnection(); - - if (!connection) { - throw new Error('Missing agent connection'); - } - - super(connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); - - this._register(this.onReload(event => { - if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { - //windowService.reloadWindow(); - window.location.reload(); - } - })); - this._register(this.onClose(event => { - if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { - //this._windowService.closeWindow(); - window.close(); - } - })); - } -} -registerSingleton(IExtensionHostDebugService, SimpleExtensionHostDebugService); - -//#endregion - //#region Window export class SimpleWindowsService implements IWindowsService { @@ -608,6 +464,13 @@ export class SimpleWindowsService implements IWindowsService { readonly onWindowUnmaximize: Event = Event.None; readonly onRecentlyOpenedChange: Event = Event.None; + constructor( + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IDialogService private readonly dialogService: IDialogService, + @IProductService private readonly productService: IProductService, + @IClipboardService private readonly clipboardService: IClipboardService + ) { + } isFocused(_windowId: number): Promise { return Promise.resolve(true); } @@ -712,6 +575,8 @@ export class SimpleWindowsService implements IWindowsService { } relaunch(_options: { addArgs?: string[], removeArgs?: string[] }): Promise { + window.location.reload(); + return Promise.resolve(); } @@ -737,6 +602,15 @@ export class SimpleWindowsService implements IWindowsService { // we pass the "ParsedArgs" as query parameters of the URL let newAddress = `${document.location.origin}/?`; + let gotFolder = false; + + const addQueryParameter = (key: string, value: string) => { + const lastChar = newAddress.charAt(newAddress.length - 1); + if (lastChar !== '?' && lastChar !== '&') { + newAddress += '&'; + } + newAddress += `${key}=${encodeURIComponent(value)}`; + }; const f = args['folder-uri']; if (f) { @@ -749,9 +623,14 @@ export class SimpleWindowsService implements IWindowsService { u = URI.parse(f); } if (u) { - newAddress += `folder=${encodeURIComponent(u.path)}`; + gotFolder = true; + addQueryParameter('folder', u.path); } } + if (!gotFolder) { + // request empty window + addQueryParameter('ew', 'true'); + } const ep = args['extensionDevelopmentPath']; if (ep) { @@ -764,18 +643,23 @@ export class SimpleWindowsService implements IWindowsService { u = ep; } if (u) { - newAddress += `&edp=${encodeURIComponent(u)}`; + addQueryParameter('edp', u); } } const di = args['debugId']; if (di) { - newAddress += `&di=${encodeURIComponent(di)}`; + addQueryParameter('di', di); } const ibe = args['inspect-brk-extensions']; if (ibe) { - newAddress += `&ibe=${encodeURIComponent(ibe)}`; + addQueryParameter('ibe', ibe); + } + + // add connection token + if (this.workbenchEnvironmentService.configuration.connectionToken) { + addQueryParameter('tkn', this.workbenchEnvironmentService.configuration.connectionToken); } window.open(newAddress); @@ -834,6 +718,8 @@ export class SimpleWindowsService implements IWindowsService { // This needs to be handled from browser process to prevent // foreground ordering issues on Windows openExternal(_url: string): Promise { + windowOpenNoOpener(_url); + return Promise.resolve(true); } @@ -854,8 +740,20 @@ export class SimpleWindowsService implements IWindowsService { throw new Error('not implemented'); } - openAboutDialog(): Promise { - return Promise.resolve(); + async openAboutDialog(): Promise { + const detail = localize('aboutDetail', + "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", + this.productService.version || 'Unknown', + this.productService.commit || 'Unknown', + this.productService.date || 'Unknown', + navigator.userAgent + ); + + const result = await this.dialogService.show(Severity.Info, this.productService.nameLong, [localize('copy', "Copy"), localize('ok', "OK")], { detail }); + + if (result === 0) { + this.clipboardService.writeText(detail); + } } resolveProxy(windowId: number, url: string): Promise { @@ -949,33 +847,26 @@ registerSingleton(ITunnelService, SimpleTunnelService); //#endregion -//#region experiments +//#region workspace stats + +class SimpleWorkspaceStatsService implements IWorkspaceStatsService { -class ExperimentService implements IExperimentService { _serviceBrand: any; - async getExperimentById(id: string): Promise { - return { - enabled: false, - id: '', - state: ExperimentState.NoRun - }; + getTags(): Promise { + return Promise.resolve({}); } - async getExperimentsByType(type: ExperimentActionType): Promise { - return []; + getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { + return undefined; } - async getCuratedExtensionsList(curatedExtensionsKey: string): Promise { - return []; + getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit?: boolean): Promise { + return Promise.resolve([]); } - markAsCompleted(experimentId: string): void { } - - onExperimentEnabled: Event = Event.None; - } -registerSingleton(IExperimentService, ExperimentService); +registerSingleton(IWorkspaceStatsService, SimpleWorkspaceStatsService); //#endregion diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 54496ea933a..5504ef50307 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -239,6 +239,11 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform' 'description': nls.localize('workbench.useExperimentalGridLayout', "Enables the grid layout for the workbench. This setting may enable additional layout options for workbench components."), 'default': true, 'scope': ConfigurationScope.APPLICATION + }, + 'workbench.octiconsUpdate.enabled': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('workbench.octiconsUpdate.enabled', "Controls the visibility of the new Octicons style in the workbench.") } } }); @@ -246,7 +251,7 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform' // Window let windowTitleDescription = nls.localize('windowTitle', "Controls the window title based on the active editor. Variables are substituted based on the context:"); - windowTitleDescription += [ + windowTitleDescription += '\n- ' + [ nls.localize('activeEditorShort', "`\${activeEditorShort}`: the file name (e.g. myFile.txt)."), nls.localize('activeEditorMedium', "`\${activeEditorMedium}`: the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)."), nls.localize('activeEditorLong', "`\${activeEditorLong}`: the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)."), @@ -360,4 +365,4 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform' } } }); -})(); \ No newline at end of file +})(); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 634655ffa84..2506a50c8c3 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -25,7 +25,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { LifecyclePhase, ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; +import { LifecyclePhase, ILifecycleService, WillShutdownEvent, BeforeShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { NotificationService } from 'vs/workbench/services/notification/common/notificationService'; import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter'; @@ -47,12 +47,15 @@ import { Layout } from 'vs/workbench/browser/layout'; export class Workbench extends Layout { - private readonly _onShutdown = this._register(new Emitter()); - readonly onShutdown: Event = this._onShutdown.event; + private readonly _onBeforeShutdown = this._register(new Emitter()); + readonly onBeforeShutdown: Event = this._onBeforeShutdown.event; private readonly _onWillShutdown = this._register(new Emitter()); readonly onWillShutdown: Event = this._onWillShutdown.event; + private readonly _onShutdown = this._register(new Emitter()); + readonly onShutdown: Event = this._onShutdown.event; + constructor( parent: HTMLElement, private readonly serviceCollection: ServiceCollection, @@ -79,11 +82,25 @@ export class Workbench extends Layout { setUnexpectedErrorHandler(error => this.handleUnexpectedError(error, logService)); // Inform user about loading issues from the loader + interface AnnotatedLoadingError extends Error { + phase: 'loading'; + moduleId: string; + neededBy: string[]; + } + interface AnnotatedFactoryError extends Error { + phase: 'factory'; + moduleId: string; + } + interface AnnotatedValidationError extends Error { + phase: 'configuration'; + } + type AnnotatedError = AnnotatedLoadingError | AnnotatedFactoryError | AnnotatedValidationError; (window).require.config({ - onError: (err: { errorCode: string; }) => { - if (err.errorCode === 'load') { + onError: (err: AnnotatedError) => { + if (err.phase === 'loading') { onUnexpectedError(new Error(localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err)))); } + console.error(err); } }); } @@ -168,7 +185,7 @@ export class Workbench extends Layout { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE. - // CONTRIBUTE IT VIA WORKBENCH.MAIN.TS AND registerSingleton(). + // CONTRIBUTE IT VIA WORKBENCH.DESKTOP.MAIN.TS AND registerSingleton(). // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // All Contributed Services @@ -211,6 +228,7 @@ export class Workbench extends Layout { ): void { // Lifecycle + this._register(lifecycleService.onBeforeShutdown(event => this._onBeforeShutdown.fire(event))); this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event))); this._register(lifecycleService.onShutdown(() => { this._onShutdown.fire(); @@ -224,7 +242,7 @@ export class Workbench extends Layout { this._register(storageService.onWillSaveState(e => this.storeFontInfo(e, storageService))); } - private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto'; + private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto' | undefined; private setFontAliasing(configurationService: IConfigurationService) { const aliasing = configurationService.getValue<'default' | 'antialiased' | 'none' | 'auto'>('workbench.fontAliasing'); if (this.fontAliasing === aliasing) { @@ -280,10 +298,7 @@ export class Workbench extends Layout { 'monaco-workbench', platformClass, isWeb ? 'web' : undefined, - this.state.sideBar.hidden ? 'nosidebar' : undefined, - this.state.panel.hidden ? 'nopanel' : undefined, - this.state.statusBar.hidden ? 'nostatusbar' : undefined, - this.state.fullscreen ? 'fullscreen' : undefined + ...this.getLayoutClasses() ]); addClasses(this.container, ...workbenchClasses); @@ -414,7 +429,7 @@ export class Workbench extends Layout { // Restore Editor Center Mode if (this.state.editor.restoreCentered) { - this.centerEditorLayout(true); + this.centerEditorLayout(true, true); } // Emit a warning after 10s if restore does not complete diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index cc28bacaf7c..0f5d7b038ca 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -38,8 +38,8 @@ export interface IWorkbenchContributionsRegistry { } class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry { - private instantiationService: IInstantiationService; - private lifecycleService: ILifecycleService; + private instantiationService: IInstantiationService | undefined; + private lifecycleService: ILifecycleService | undefined; private readonly toBeInstantiated: Map[]> = new Map[]>(); @@ -67,7 +67,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry this.lifecycleService = accessor.get(ILifecycleService); [LifecyclePhase.Starting, LifecyclePhase.Ready, LifecyclePhase.Restored, LifecyclePhase.Eventually].forEach(phase => { - this.instantiateByPhase(this.instantiationService, this.lifecycleService, phase); + this.instantiateByPhase(this.instantiationService!, this.lifecycleService!, phase); }); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 4cd3bb02e5d..97f5aa1e79d 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -30,6 +30,8 @@ export const NoEditorsVisibleContext: ContextKeyExpr = EditorsVisibleContext.toN export const TextCompareEditorVisibleContext = new RawContextKey('textCompareEditorVisible', false); export const TextCompareEditorActiveContext = new RawContextKey('textCompareEditorActive', false); export const ActiveEditorGroupEmptyContext = new RawContextKey('activeEditorGroupEmpty', false); +export const ActiveEditorGroupIndexContext = new RawContextKey('activeEditorGroupIndex', -1); +export const ActiveEditorGroupLastContext = new RawContextKey('activeEditorGroupLast', false); export const MultipleEditorGroupsContext = new RawContextKey('multipleEditorGroups', false); export const SingleEditorGroupsContext = MultipleEditorGroupsContext.toNegated(); export const InEditorZenModeContext = new RawContextKey('inZenMode', false); @@ -1127,4 +1129,4 @@ export async function pathsToEditors(paths: IPathData[] | undefined, fileService })); return coalesce(editors); -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index 239fd95cc28..da75c1b3e9c 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -17,8 +17,8 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; * The base text editor model leverages the code editor model. This class is only intended to be subclassed and not instantiated. */ export abstract class BaseTextEditorModel extends EditorModel implements ITextEditorModel, IModeSupport { - protected textEditorModelHandle: URI | null; - private createdEditorModel: boolean; + protected textEditorModelHandle: URI | null = null; + private createdEditorModel: boolean | undefined; private readonly modelDisposeListener = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index b83f9599436..9546898f8c9 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -243,7 +243,7 @@ export interface INotificationViewItem { readonly silent: boolean; readonly message: INotificationMessage; readonly source: string | undefined; - readonly actions: INotificationActions; + readonly actions: INotificationActions | undefined; readonly progress: INotificationViewItemProgress; readonly expanded: boolean; @@ -391,10 +391,10 @@ export class NotificationViewItem extends Disposable implements INotificationVie // RegEx: [, anything not ], ], (, http://|https://|command:, no whitespace) private static LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi; - private _expanded: boolean; + private _expanded: boolean | undefined; - private _actions: INotificationActions; - private _progress: NotificationViewItemProgress; + private _actions: INotificationActions | undefined; + private _progress: NotificationViewItemProgress | undefined; private readonly _onDidExpansionChange: Emitter = this._register(new Emitter()); readonly onDidExpansionChange: Event = this._onDidExpansionChange.event; @@ -505,7 +505,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie } get expanded(): boolean { - return this._expanded; + return !!this._expanded; } get severity(): Severity { @@ -534,6 +534,10 @@ export class NotificationViewItem extends Disposable implements INotificationVie } hasPrompt(): boolean { + if (!this._actions) { + return false; + } + if (!this._actions.primary) { return false; } @@ -562,7 +566,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie return this._source; } - get actions(): INotificationActions { + get actions(): INotificationActions | undefined { return this._actions; } @@ -635,8 +639,8 @@ export class NotificationViewItem extends Disposable implements INotificationVie return false; } - const primaryActions = this._actions.primary || []; - const otherPrimaryActions = other.actions.primary || []; + const primaryActions = (this._actions && this._actions.primary) || []; + const otherPrimaryActions = (other.actions && other.actions.primary) || []; if (primaryActions.length !== otherPrimaryActions.length) { return false; } @@ -704,4 +708,4 @@ class StatusMessageViewItem { return { message, options }; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 6f5386ee4a8..7306d530fcb 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -17,7 +17,6 @@ import { values, keys } from 'vs/base/common/map'; import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IAction } from 'vs/base/common/actions'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; @@ -52,7 +51,7 @@ export interface IViewContainersRegistry { * * @returns the registered ViewContainer. */ - registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier): ViewContainer; + registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer; /** * Deregisters the given view container @@ -68,8 +67,12 @@ export interface IViewContainersRegistry { get(id: string): ViewContainer | undefined; } +interface ViewOrderDelegate { + getOrder(group?: string): number | undefined; +} + export class ViewContainer { - protected constructor(readonly id: string, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier) { } + protected constructor(readonly id: string, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier, readonly orderDelegate?: ViewOrderDelegate) { } } class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry { @@ -86,7 +89,7 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe return values(this.viewContainers); } - registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier): ViewContainer { + registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer { const existing = this.viewContainers.get(id); if (existing) { return existing; @@ -94,7 +97,7 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe const viewContainer = new class extends ViewContainer { constructor() { - super(id, !!hideIfEmpty, extensionId); + super(id, !!hideIfEmpty, extensionId, viewOrderDelegate); } }; this.viewContainers.set(id, viewContainer); @@ -127,6 +130,8 @@ export interface IViewDescriptor { readonly when?: ContextKeyExpr; + readonly group?: string; + readonly order?: number; readonly weight?: number; @@ -301,11 +306,13 @@ export interface IViewsService { export interface ITreeView extends IDisposable { - dataProvider: ITreeViewDataProvider | null; + dataProvider: ITreeViewDataProvider | undefined; showCollapseAllAction: boolean; - message?: string | IMarkdownString; + canSelectMany: boolean; + + message?: string; readonly visible: boolean; @@ -325,9 +332,7 @@ export interface ITreeView extends IDisposable { focus(): void; - layout(height: number): void; - - show(container: HTMLElement): void; + layout(height: number, width: number): void; getOptimalWidth(): number; diff --git a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts index 854d26b935c..3ac6bb520d5 100644 --- a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts @@ -16,7 +16,7 @@ const AUTO_SAVE_AFTER_DELAY_DISABLED_TIME = CONTENT_CHANGE_EVENT_BUFFER_DELAY + export class BackupModelTracker extends Disposable implements IWorkbenchContribution { - private configuredAutoSaveAfterDelay: boolean; + private configuredAutoSaveAfterDelay = false; constructor( @IBackupFileService private readonly backupFileService: IBackupFileService, @@ -47,11 +47,11 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu private onConfigurationChange(configuration: IFilesConfiguration): void { if (!configuration || !configuration.files) { this.configuredAutoSaveAfterDelay = false; + return; } - this.configuredAutoSaveAfterDelay = - (configuration.files.autoSave === AutoSaveConfiguration.AFTER_DELAY && - configuration.files.autoSaveDelay <= AUTO_SAVE_AFTER_DELAY_DISABLED_TIME); + + this.configuredAutoSaveAfterDelay = (configuration.files.autoSave === AutoSaveConfiguration.AFTER_DELAY && configuration.files.autoSaveDelay <= AUTO_SAVE_AFTER_DELAY_DISABLED_TIME); } private onTextFileModelChanged(event: TextFileModelChangeEvent): void { @@ -81,4 +81,4 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu private discardBackup(resource: Uri): void { this.backupFileService.discardResourceBackup(resource); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 677ee37a697..1166da97f9c 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -91,15 +91,15 @@ class LayoutInfo { export class CallHierarchyTreePeekWidget extends PeekViewWidget { - private _changeDirectionAction: ChangeHierarchyDirectionAction; - private _parent: HTMLElement; - private _message: HTMLElement; - private _splitView: SplitView; - private _tree: WorkbenchAsyncDataTree; + private _changeDirectionAction?: ChangeHierarchyDirectionAction; + private _parent!: HTMLElement; + private _message!: HTMLElement; + private _splitView!: SplitView; + private _tree!: WorkbenchAsyncDataTree; private _treeViewStates = new Map(); - private _editor: EmbeddedCodeEditorWidget; - private _dim: Dimension; - private _layoutInfo: LayoutInfo; + private _editor!: EmbeddedCodeEditorWidget; + private _dim!: Dimension; + private _layoutInfo!: LayoutInfo; constructor( editor: ICodeEditor, @@ -386,7 +386,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { }; this._changeDirectionAction = new ChangeHierarchyDirectionAction(this._direction, changeDirection); this._disposables.add(this._changeDirectionAction); - this._actionbarWidget.push(this._changeDirectionAction, { icon: true, label: false }); + this._actionbarWidget!.push(this._changeDirectionAction, { icon: true, label: false }); } } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index 5bc78e806fa..becdc2dc3ed 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -58,7 +58,9 @@ export class IdentityProvider implements IIdentityProvider { } class CallRenderingTemplate { - readonly iconLabel: IconLabel; + constructor( + readonly iconLabel: IconLabel + ) { } } export class CallRenderer implements ITreeRenderer { @@ -69,7 +71,7 @@ export class CallRenderer implements ITreeRenderer, _index: number, template: CallRenderingTemplate): void { diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 9d6b5c95ba2..a45ab650031 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -7,7 +7,7 @@ import 'vs/css!./accessibility'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; -import { renderFormattedText } from 'vs/base/browser/htmlContentRenderer'; +import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { Widget } from 'vs/base/browser/ui/widget'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-dark.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-dark.svg new file mode 100644 index 00000000000..dbe70d742de --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-light.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-light.svg new file mode 100644 index 00000000000..ec824f41cc0 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-next-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-dark.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-dark.svg new file mode 100644 index 00000000000..5db4f79da85 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-light.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-light.svg new file mode 100644 index 00000000000..aac3a5020cd --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/chevron-previous-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/close-dark.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/close-dark.svg new file mode 100644 index 00000000000..75644595d19 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/close-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/images/close-light.svg b/src/vs/workbench/contrib/codeEditor/browser/find/images/close-light.svg new file mode 100644 index 00000000000..cf5f28ca35c --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/find/images/close-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/editor/contrib/find/simpleFindWidget.css b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css similarity index 100% rename from src/vs/editor/contrib/find/simpleFindWidget.css rename to src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css diff --git a/src/vs/editor/contrib/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts similarity index 98% rename from src/vs/editor/contrib/find/simpleFindWidget.ts rename to src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index e3723366157..8ba218619a2 100644 --- a/src/vs/editor/contrib/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -29,13 +29,14 @@ export abstract class SimpleFindWidget extends Widget { private readonly _findInput: FindInput; private readonly _domNode: HTMLElement; private readonly _innerDomNode: HTMLElement; - private _isVisible: boolean = false; private readonly _focusTracker: dom.IFocusTracker; private readonly _findInputFocusTracker: dom.IFocusTracker; private readonly _updateHistoryDelayer: Delayer; - private prevBtn: SimpleButton; - private nextBtn: SimpleButton; - private foundMatch: boolean; + private readonly prevBtn: SimpleButton; + private readonly nextBtn: SimpleButton; + + private _isVisible: boolean = false; + private foundMatch: boolean = false; constructor( @IContextViewService private readonly _contextViewService: IContextViewService, @@ -90,6 +91,7 @@ export abstract class SimpleFindWidget extends Widget { this._findInput.setRegex(this._state.isRegex); this._findInput.setWholeWords(this._state.wholeWord); this._findInput.setCaseSensitive(this._state.matchCase); + this.findFirst(); })); this._register(this._findInput.onKeyDown((e) => { @@ -165,6 +167,7 @@ export abstract class SimpleFindWidget extends Widget { protected abstract onInputChanged(): boolean; protected abstract find(previous: boolean): void; + protected abstract findFirst(): void; protected abstract onFocusTrackerFocus(): void; protected abstract onFocusTrackerBlur(): void; protected abstract onFindInputFocusTrackerFocus(): void; diff --git a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts index 17369ff0b6c..551c3e4b86a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts @@ -11,7 +11,6 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; /** * Shows a message when opening a large file which has been memory optimized (and features disabled). @@ -20,26 +19,19 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC private static readonly ID = 'editor.contrib.largeFileOptimizationsWarner'; - private _isDisabled: boolean; - constructor( private readonly _editor: ICodeEditor, @INotificationService private readonly _notificationService: INotificationService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IStorageService private readonly _storageService: IStorageService, ) { super(); - this._isDisabled = Boolean(this._storageService.getBoolean('editor.neverPromptForLargeFiles', StorageScope.GLOBAL, false)); this._register(this._editor.onDidChangeModel((e) => { const model = this._editor.getModel(); if (!model) { return; } - if (this._isDisabled) { - return; - } if (model.isTooLargeForTokenization()) { const message = nls.localize( @@ -54,13 +46,6 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC ); this._notificationService.prompt(Severity.Info, message, [ - { - label: nls.localize('dontShowAgain', "Don't Show Again"), - run: () => { - this._isDisabled = true; - this._storageService.store('editor.neverPromptForLargeFiles', true, StorageScope.GLOBAL); - } - }, { label: nls.localize('removeOptimizations', "Forcefully enable features"), run: () => { @@ -71,7 +56,7 @@ export class LargeFileOptimizationsWarner extends Disposable implements IEditorC }); } } - ]); + ], { neverShowAgain: { id: 'editor.contrib.largeFileOptimizationsWarner' } }); } })); } diff --git a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts index 362d32e5edd..1b7a6d7ae2e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts @@ -14,7 +14,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; export class CommentFormActions implements IDisposable { private _buttonElements: HTMLElement[] = []; private readonly _toDispose = new DisposableStore(); - private _actions: IAction[]; + private _actions: IAction[] = []; constructor( private container: HTMLElement, @@ -59,4 +59,4 @@ export class CommentFormActions implements IDisposable { dispose() { this._toDispose.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts b/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts index a268f68a24c..12682d319d9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts @@ -16,7 +16,7 @@ const overviewRulerDefault = new Color(new RGBA(197, 197, 197, 1)); export const overviewRulerCommentingRangeForeground = registerColor('editorGutter.commentRangeForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('editorGutterCommentRangeForeground', 'Editor gutter decoration color for commenting ranges.')); export class CommentGlyphWidget { - private _lineNumber: number; + private _lineNumber!: number; private _editor: ICodeEditor; private commentsDecorations: string[] = []; private _commentsOptions: ModelDecorationOptions; diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index e8d7d850123..e0fce1e0ba0 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -40,22 +40,22 @@ export class CommentNode extends Disposable { private _md: HTMLElement; private _clearTimeout: any; - private _editAction: Action; - private _commentEditContainer: HTMLElement; + private _editAction: Action | null = null; + private _commentEditContainer: HTMLElement | null = null; private _commentDetailsContainer: HTMLElement; - private _actionsToolbarContainer: HTMLElement; + private _actionsToolbarContainer!: HTMLElement; private _reactionsActionBar?: ActionBar; private _reactionActionsContainer?: HTMLElement; - private _commentEditor: SimpleCommentEditor | null; + private _commentEditor: SimpleCommentEditor | null = null; private _commentEditorDisposables: IDisposable[] = []; - private _commentEditorModel: ITextModel; - private _isPendingLabel: HTMLElement; + private _commentEditorModel: ITextModel | null = null; + private _isPendingLabel!: HTMLElement; private _contextKeyService: IContextKeyService; private _commentContextValue: IContextKey; protected actionRunner?: IActionRunner; protected toolbar: ToolBar | undefined; - private _commentFormActions: CommentFormActions; + private _commentFormActions: CommentFormActions | null = null; private _onDidDelete = new Emitter(); @@ -63,7 +63,7 @@ export class CommentNode extends Disposable { return this._domNode; } - public isEditing: boolean; + public isEditing: boolean = false; constructor( private commentThread: modes.CommentThread, @@ -326,8 +326,8 @@ export class CommentNode extends Disposable { } } - private createCommentEditor(): void { - const container = dom.append(this._commentEditContainer, dom.$('.edit-textarea')); + private createCommentEditor(editContainer: HTMLElement): void { + const container = dom.append(editContainer, dom.$('.edit-textarea')); this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(), this.parentEditor, this.parentThread); const resource = URI.parse(`comment:commentinput-${this.comment.uniqueIdInThread}-${Date.now()}.md`); this._commentEditorModel = this.modelService.createModel('', this.modeService.createByFilepathOrFirstLine(resource), resource, false); @@ -390,7 +390,7 @@ export class CommentNode extends Disposable { this._commentEditor = null; } - this._commentEditContainer.remove(); + this._commentEditContainer!.remove(); } public switchToEditMode() { @@ -401,7 +401,7 @@ export class CommentNode extends Disposable { this.isEditing = true; this._body.classList.add('hidden'); this._commentEditContainer = dom.append(this._commentDetailsContainer, dom.$('.edit-container')); - this.createCommentEditor(); + this.createCommentEditor(this._commentEditContainer); const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); const menus = this.commentService.getCommentMenus(this.owner); @@ -409,7 +409,9 @@ export class CommentNode extends Disposable { this._register(menu); this._register(menu.onDidChange(() => { - this._commentFormActions.setActions(menu); + if (this._commentFormActions) { + this._commentFormActions.setActions(menu); + } })); this._commentFormActions = new CommentFormActions(formActions, (action: IAction): void => { diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 89dd182ebe2..d5cfcfb4f19 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -36,6 +36,7 @@ import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/bro import { COMMENTEDITOR_DECORATION_KEY, ReviewZoneWidget } from 'vs/workbench/contrib/comments/browser/commentThreadWidget'; import { ctxCommentEditorFocused, SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; export const ID = 'editor.contrib.review'; @@ -173,7 +174,6 @@ export class ReviewController implements IEditorContribution { @IContextMenuService readonly contextMenuService: IContextMenuService, @IQuickInputService private readonly quickInputService: IQuickInputService ) { - this.editor = editor; this.globalToDispose = []; this.localToDispose = []; this._commentInfos = []; @@ -181,6 +181,12 @@ export class ReviewController implements IEditorContribution { this._pendingCommentCache = {}; this._computePromise = null; + if (editor instanceof EmbeddedCodeEditorWidget) { + return; + } + + this.editor = editor; + this._commentingRangeDecorator = new CommentingRangeDecorator(); this.globalToDispose.push(this.commentService.onDidDeleteDataProvider(ownerId => { diff --git a/src/vs/workbench/contrib/comments/browser/commentsPanel.ts b/src/vs/workbench/contrib/comments/browser/commentsPanel.ts index 27440e3e996..5f0e33758a4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsPanel.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsPanel.ts @@ -4,34 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/panel'; +import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { IAction } from 'vs/base/common/actions'; -import { Event } from 'vs/base/common/event'; -import { CollapseAllAction, DefaultAccessibilityProvider, DefaultController, DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; +import { IAction, Action } from 'vs/base/common/actions'; +import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Panel } from 'vs/workbench/browser/panel'; import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { ReviewController } from 'vs/workbench/contrib/comments/browser/commentsEditorContribution'; -import { CommentsDataFilter, CommentsDataSource, CommentsModelRenderer } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { ICommentService, IWorkspaceCommentThreadsEvent } from 'vs/workbench/contrib/comments/browser/commentService'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { CommentsList, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; -export const COMMENTS_PANEL_ID = 'workbench.panel.comments'; -export const COMMENTS_PANEL_TITLE = 'Comments'; export class CommentsPanel extends Panel { private treeLabels: ResourceLabels; - private tree: WorkbenchTree; + private tree: CommentsList; private treeContainer: HTMLElement; private messageBoxContainer: HTMLElement; private messageBox: HTMLElement; @@ -42,7 +39,6 @@ export class CommentsPanel extends Panel { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommentService private readonly commentService: ICommentService, @IEditorService private readonly editorService: IEditorService, - @IOpenerService private readonly openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService @@ -113,7 +109,7 @@ export class CommentsPanel extends Panel { public getActions(): IAction[] { if (!this.collapseAllAction) { - this.collapseAllAction = this.instantiationService.createInstance(CollapseAllAction, this.tree, this.commentsModel.hasCommentThreads()); + this.collapseAllAction = new Action('vs.tree.collapse', nls.localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); this._register(this.collapseAllAction); } @@ -141,22 +137,11 @@ export class CommentsPanel extends Panel { private createTree(): void { this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); + this.tree = this._register(this.instantiationService.createInstance(CommentsList, this.treeLabels, this.treeContainer)); - this.tree = this._register(this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { - dataSource: new CommentsDataSource(), - renderer: new CommentsModelRenderer(this.treeLabels, this.openerService), - accessibilityProvider: new DefaultAccessibilityProvider, - controller: new DefaultController(), - dnd: new DefaultDragAndDrop(), - filter: new CommentsDataFilter() - }, { - twistiePixels: 20, - ariaLabel: COMMENTS_PANEL_TITLE - })); - - const commentsNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); - this._register(Event.debounce(commentsNavigator.openResource, (last, event) => event, 100, true)(options => { - this.openFile(options.element, options.editorOptions.pinned, options.editorOptions.preserveFocus, options.sideBySide); + const commentsNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: true })); + this._register(commentsNavigator.onDidOpenResource(e => { + this.openFile(e.element, e.editorOptions.pinned, e.editorOptions.preserveFocus, e.sideBySide); })); } @@ -213,7 +198,7 @@ export class CommentsPanel extends Panel { this.collapseAllAction.enabled = this.commentsModel.hasCommentThreads(); dom.toggleClass(this.treeContainer, 'hidden', !this.commentsModel.hasCommentThreads()); - this.tree.refresh().then(() => { + this.tree.updateChildren().then(() => { this.renderMessage(); }, (e) => { console.log(e); @@ -243,4 +228,4 @@ CommandsRegistry.registerCommand({ panelService.openPanel(COMMENTS_PANEL_ID, true); } } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 090ffa54650..5baf982dd93 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -5,34 +5,32 @@ import * as dom from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; -import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IDataSource, IFilter, IRenderer as ITreeRenderer, ITree } from 'vs/base/parts/tree/browser/tree'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel'; +import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -export class CommentsDataSource implements IDataSource { - public getId(tree: ITree, element: any): string { - if (element instanceof CommentsModel) { - return 'root'; - } - if (element instanceof ResourceWithCommentThreads) { - return `${element.owner}-${element.id}`; - } - if (element instanceof CommentNode) { - return `${element.owner}-${element.resource.toString()}-${element.threadId}-${element.comment.uniqueIdInThread}` + (element.isRoot ? '-root' : ''); - } - return ''; - } +export const COMMENTS_PANEL_ID = 'workbench.panel.comments'; +export const COMMENTS_PANEL_TITLE = 'Comments'; - public hasChildren(tree: ITree, element: any): boolean { +export class CommentsAsyncDataSource implements IAsyncDataSource { + hasChildren(element: any): boolean { return element instanceof CommentsModel || element instanceof ResourceWithCommentThreads || (element instanceof CommentNode && !!element.replies.length); } - public getChildren(tree: ITree, element: any): Promise { + getChildren(element: any): any[] | Promise { if (element instanceof CommentsModel) { return Promise.resolve(element.resourceCommentThreads); } @@ -44,14 +42,6 @@ export class CommentsDataSource implements IDataSource { } return Promise.resolve([]); } - - public getParent(tree: ITree, element: any): Promise { - return Promise.resolve(undefined); - } - - public shouldAutoexpand(tree: ITree, element: any): boolean { - return true; - } } interface IResourceTemplateData { @@ -65,61 +55,36 @@ interface ICommentThreadTemplateData { disposables: IDisposable[]; } -export class CommentsModelRenderer implements ITreeRenderer { +export class CommentsModelVirualDelegate implements IListVirtualDelegate { private static RESOURCE_ID = 'resource-with-comments'; private static COMMENT_ID = 'comment-node'; - constructor( - private labels: ResourceLabels, - @IOpenerService private readonly openerService: IOpenerService - ) { - } - public getHeight(tree: ITree, element: any): number { + getHeight(element: any): number { return 22; } - public getTemplateId(tree: ITree, element: any): string { + public getTemplateId(element: any): string { if (element instanceof ResourceWithCommentThreads) { - return CommentsModelRenderer.RESOURCE_ID; + return CommentsModelVirualDelegate.RESOURCE_ID; } if (element instanceof CommentNode) { - return CommentsModelRenderer.COMMENT_ID; + return CommentsModelVirualDelegate.COMMENT_ID; } return ''; } +} - public renderTemplate(ITree: ITree, templateId: string, container: HTMLElement): any { - switch (templateId) { - case CommentsModelRenderer.RESOURCE_ID: - return this.renderResourceTemplate(container); - case CommentsModelRenderer.COMMENT_ID: - return this.renderCommentTemplate(container); - } +export class ResourceWithCommentsRenderer implements IListRenderer, IResourceTemplateData> { + templateId: string = 'resource-with-comments'; + + constructor( + private labels: ResourceLabels + ) { } - public disposeTemplate(tree: ITree, templateId: string, templateData: any): void { - switch (templateId) { - case CommentsModelRenderer.RESOURCE_ID: - (templateData).resourceLabel.dispose(); - break; - case CommentsModelRenderer.COMMENT_ID: - (templateData).disposables.forEach(disposeable => disposeable.dispose()); - break; - } - } - - public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void { - switch (templateId) { - case CommentsModelRenderer.RESOURCE_ID: - return this.renderResourceElement(tree, element, templateData); - case CommentsModelRenderer.COMMENT_ID: - return this.renderCommentElement(tree, element, templateData); - } - } - - private renderResourceTemplate(container: HTMLElement): IResourceTemplateData { + renderTemplate(container: HTMLElement) { const data = Object.create(null); const labelContainer = dom.append(container, dom.$('.resource-container')); data.resourceLabel = this.labels.create(labelContainer); @@ -127,7 +92,23 @@ export class CommentsModelRenderer implements ITreeRenderer { return data; } - private renderCommentTemplate(container: HTMLElement): ICommentThreadTemplateData { + renderElement(node: ITreeNode, index: number, templateData: IResourceTemplateData, height: number | undefined): void { + templateData.resourceLabel.setFile(node.element.resource); + } + + disposeTemplate(templateData: IResourceTemplateData): void { + templateData.resourceLabel.dispose(); + } +} + +export class CommentNodeRenderer implements IListRenderer, ICommentThreadTemplateData> { + templateId: string = 'comment-node'; + + constructor( + @IOpenerService private readonly openerService: IOpenerService + ) { } + + renderTemplate(container: HTMLElement) { const data = Object.create(null); const labelContainer = dom.append(container, dom.$('.comment-container')); data.userName = dom.append(labelContainer, dom.$('.user')); @@ -137,16 +118,12 @@ export class CommentsModelRenderer implements ITreeRenderer { return data; } - private renderResourceElement(tree: ITree, element: ResourceWithCommentThreads, templateData: IResourceTemplateData) { - templateData.resourceLabel.setFile(element.resource); - } - - private renderCommentElement(tree: ITree, element: CommentNode, templateData: ICommentThreadTemplateData) { - templateData.userName.textContent = element.comment.userName; + renderElement(node: ITreeNode, index: number, templateData: ICommentThreadTemplateData, height: number | undefined): void { + templateData.userName.textContent = node.element.comment.userName; templateData.commentText.innerHTML = ''; const disposables = new DisposableStore(); templateData.disposables.push(disposables); - const renderedComment = renderMarkdown(element.comment.body, { + const renderedComment = renderMarkdown(node.element.comment.body, { inline: true, actionHandler: { callback: (content) => { @@ -171,16 +148,71 @@ export class CommentsModelRenderer implements ITreeRenderer { templateData.commentText.appendChild(renderedComment); } -} -export class CommentsDataFilter implements IFilter { - public isVisible(tree: ITree, element: any): boolean { - if (element instanceof CommentsModel) { - return element.resourceCommentThreads.length > 0; - } - if (element instanceof ResourceWithCommentThreads) { - return element.commentThreads.length > 0; - } - return true; + disposeTemplate(templateData: ICommentThreadTemplateData): void { + templateData.disposables.forEach(disposeable => disposeable.dispose()); + } +} + +export class CommentsList extends WorkbenchAsyncDataTree { + constructor( + labels: ResourceLabels, + container: HTMLElement, + @IContextKeyService contextKeyService: IContextKeyService, + @IListService listService: IListService, + @IThemeService themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @IAccessibilityService accessibilityService: IAccessibilityService + ) { + const delegate = new CommentsModelVirualDelegate(); + const dataSource = new CommentsAsyncDataSource(); + + const renderers = [ + instantiationService.createInstance(ResourceWithCommentsRenderer, labels), + instantiationService.createInstance(CommentNodeRenderer) + ]; + + super( + container, + delegate, + renderers, + dataSource, + { + ariaLabel: COMMENTS_PANEL_TITLE, + keyboardSupport: true, + identityProvider: { + getId: (element: any) => { + if (element instanceof CommentsModel) { + return 'root'; + } + if (element instanceof ResourceWithCommentThreads) { + return `${element.owner}-${element.id}`; + } + if (element instanceof CommentNode) { + return `${element.owner}-${element.resource.toString()}-${element.threadId}-${element.comment.uniqueIdInThread}` + (element.isRoot ? '-root' : ''); + } + return ''; + } + }, + expandOnlyOnTwistieClick: (element: any) => { + if (element instanceof CommentsModel || element instanceof ResourceWithCommentThreads) { + return false; + } + + return true; + }, + collapseByDefault: () => { + return false; + } + }, + contextKeyService, + listService, + themeService, + configurationService, + keybindingService, + accessibilityService + ); } } diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 47f8feb5fc9..fb8c2b0d6ca 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -433,6 +433,10 @@ z-index: 10; } +div.preview.inline .monaco-editor .comment-range-glyph { + display: none !important; +} + .monaco-editor .comment-range-glyph:before { position: absolute; content: ""; diff --git a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts index c14030dab80..a06e6ce4017 100644 --- a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts +++ b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts @@ -11,11 +11,10 @@ import { URI, UriComponents } from 'vs/base/common/uri'; export class ToggleReactionsAction extends Action { static readonly ID = 'toolbar.toggle.pickReactions'; - private _menuActions: IAction[]; + 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); + super(ToggleReactionsAction.ID, title || nls.localize('pickReactions', "Pick Reactions..."), 'toggle-reactions', true); this.toggleDropdownMenu = toggleDropdownMenu; } run(): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 7200bad0a0a..136afbbf63d 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -45,10 +45,10 @@ export interface IPrivateBreakpointWidgetService { const DECORATION_KEY = 'breakpointwidgetdecoration'; export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWidgetService { - public _serviceBrand: any; + _serviceBrand: any; - private selectContainer: HTMLElement; - private input: IActiveCodeEditor; + private selectContainer!: HTMLElement; + private input!: IActiveCodeEditor; private toDispose: lifecycle.IDisposable[]; private conditionInput = ''; private hitCountInput = ''; @@ -130,12 +130,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } } - public show(rangeOrPos: IRange | IPosition, heightInLines: number) { + show(rangeOrPos: IRange | IPosition, heightInLines: number) { const lineNum = this.input.getModel().getLineCount(); super.show(rangeOrPos, lineNum + 1); } - public fitHeightToContent() { + fitHeightToContent() { const lineNum = this.input.getModel().getLineCount(); this._relayout(lineNum + 1); } @@ -165,50 +165,6 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi setTimeout(() => this.input.focus(), 150); } - public close(success: boolean): void { - if (success) { - // if there is already a breakpoint on this location - remove it. - - let condition = this.breakpoint && this.breakpoint.condition; - let hitCondition = this.breakpoint && this.breakpoint.hitCondition; - let logMessage = this.breakpoint && this.breakpoint.logMessage; - this.rememberInput(); - - if (this.conditionInput || this.context === Context.CONDITION) { - condition = this.conditionInput; - } - if (this.hitCountInput || this.context === Context.HIT_COUNT) { - hitCondition = this.hitCountInput; - } - if (this.logMessageInput || this.context === Context.LOG_MESSAGE) { - logMessage = this.logMessageInput; - } - - if (this.breakpoint) { - const data = new Map(); - data.set(this.breakpoint.getId(), { - condition, - hitCondition, - logMessage - }); - this.debugService.updateBreakpoints(this.breakpoint.uri, data, false).then(undefined, onUnexpectedError); - } else { - const model = this.editor.getModel(); - if (model) { - this.debugService.addBreakpoints(model.uri, [{ - lineNumber: this.lineNumber, - enabled: true, - condition, - hitCondition, - logMessage - }], `breakpointWidget`); - } - } - } - - this.dispose(); - } - protected _doLayout(heightInPixel: number, widthInPixel: number): void { this.input.layout({ height: heightInPixel, width: widthInPixel - 113 }); } @@ -305,7 +261,51 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi return false; } - public dispose(): void { + close(success: boolean): void { + if (success) { + // if there is already a breakpoint on this location - remove it. + + let condition = this.breakpoint && this.breakpoint.condition; + let hitCondition = this.breakpoint && this.breakpoint.hitCondition; + let logMessage = this.breakpoint && this.breakpoint.logMessage; + this.rememberInput(); + + if (this.conditionInput || this.context === Context.CONDITION) { + condition = this.conditionInput; + } + if (this.hitCountInput || this.context === Context.HIT_COUNT) { + hitCondition = this.hitCountInput; + } + if (this.logMessageInput || this.context === Context.LOG_MESSAGE) { + logMessage = this.logMessageInput; + } + + if (this.breakpoint) { + const data = new Map(); + data.set(this.breakpoint.getId(), { + condition, + hitCondition, + logMessage + }); + this.debugService.updateBreakpoints(this.breakpoint.uri, data, false).then(undefined, onUnexpectedError); + } else { + const model = this.editor.getModel(); + if (model) { + this.debugService.addBreakpoints(model.uri, [{ + lineNumber: this.lineNumber, + enabled: true, + condition, + hitCondition, + logMessage + }], `breakpointWidget`); + } + } + } + + this.dispose(); + } + + dispose(): void { super.dispose(); this.input.dispose(); lifecycle.dispose(this.toDispose); @@ -327,7 +327,7 @@ class AcceptBreakpointWidgetInputAction extends EditorCommand { }); } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { accessor.get(IPrivateBreakpointWidgetService).close(true); } } @@ -347,7 +347,7 @@ class CloseBreakpointWidgetCommand extends EditorCommand { }); } - public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { const debugContribution = editor.getContribution(EDITOR_CONTRIBUTION_ID); if (debugContribution) { // if focus is in outer editor we need to use the debug contribution to close diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 87094b93ebc..e1f0c4496a0 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -8,7 +8,7 @@ import * as resources from 'vs/base/common/resources'; import * as dom from 'vs/base/browser/dom'; import { IAction, Action } from 'vs/base/common/actions'; import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, EDITOR_CONTRIBUTION_ID, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, IDebugEditorContribution } from 'vs/workbench/contrib/debug/common/debug'; -import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -45,8 +45,8 @@ function createCheckbox(): HTMLInputElement { export class BreakpointsView extends ViewletPanel { private static readonly MAX_VISIBLE_FILES = 9; - private list: WorkbenchList; - private needsRefresh: boolean; + private list!: WorkbenchList; + private needsRefresh = false; constructor( options: IViewletViewOptions, @@ -74,6 +74,7 @@ export class BreakpointsView extends ViewletPanel { this.instantiationService.createInstance(BreakpointsRenderer), new ExceptionBreakpointsRenderer(this.debugService), this.instantiationService.createInstance(FunctionBreakpointsRenderer), + this.instantiationService.createInstance(DataBreakpointsRenderer), new FunctionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService) ], { identityProvider: { getId: (element: IEnablement) => element.getId() }, @@ -91,7 +92,7 @@ export class BreakpointsView extends ViewletPanel { this._register(this.list.onContextMenu(this.onListContextMenu, this)); - this._register(this.list.onDidOpen(e => { + this._register(this.list.onDidOpen(async e => { let isSingleClick = false; let isDoubleClick = false; let isMiddleClick = false; @@ -110,9 +111,11 @@ export class BreakpointsView extends ViewletPanel { if (isMiddleClick) { if (element instanceof Breakpoint) { - this.debugService.removeBreakpoints(element.getId()); + await this.debugService.removeBreakpoints(element.getId()); } else if (element instanceof FunctionBreakpoint) { - this.debugService.removeFunctionBreakpoints(element.getId()); + await this.debugService.removeFunctionBreakpoints(element.getId()); + } else if (element instanceof DataBreakpoint) { + await this.debugService.removeDataBreakpoints(element.getId()); } return; } @@ -222,14 +225,14 @@ export class BreakpointsView extends ViewletPanel { private get elements(): IEnablement[] { const model = this.debugService.getModel(); - const elements = (>model.getExceptionBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getBreakpoints()); + const elements = (>model.getExceptionBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getDataBreakpoints()).concat(model.getBreakpoints()); return elements; } private getExpandedBodySize(): number { const model = this.debugService.getModel(); - const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length; + const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length + model.getDataBreakpoints().length; return Math.min(BreakpointsView.MAX_VISIBLE_FILES, length) * 22; } } @@ -259,6 +262,9 @@ class BreakpointsDelegate implements IListVirtualDelegate { if (element instanceof ExceptionBreakpoint) { return ExceptionBreakpointsRenderer.ID; } + if (element instanceof DataBreakpoint) { + return DataBreakpointsRenderer.ID; + } return ''; } @@ -454,6 +460,61 @@ class FunctionBreakpointsRenderer implements IListRenderer { + + constructor( + @IDebugService private readonly debugService: IDebugService + ) { + // noop + } + + static readonly ID = 'databreakpoints'; + + get templateId() { + return DataBreakpointsRenderer.ID; + } + + renderTemplate(container: HTMLElement): IBaseBreakpointWithIconTemplateData { + const data: IBreakpointTemplateData = Object.create(null); + data.breakpoint = dom.append(container, $('.breakpoint')); + + data.icon = $('.icon'); + data.checkbox = createCheckbox(); + data.toDispose = []; + data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => { + this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context); + })); + + dom.append(data.breakpoint, data.icon); + dom.append(data.breakpoint, data.checkbox); + + data.name = dom.append(data.breakpoint, $('span.name')); + + return data; + } + + renderElement(dataBreakpoint: DataBreakpoint, index: number, data: IBaseBreakpointWithIconTemplateData): void { + data.context = dataBreakpoint; + data.name.textContent = dataBreakpoint.label; + const { className, message } = getBreakpointMessageAndClassName(this.debugService, dataBreakpoint); + data.icon.className = className + ' icon'; + data.icon.title = message ? message : ''; + data.checkbox.checked = dataBreakpoint.enabled; + data.breakpoint.title = dataBreakpoint.label; + + // Mark function breakpoints as disabled if deactivated or if debug type does not support them #9099 + const session = this.debugService.getViewModel().focusedSession; + dom.toggleClass(data.breakpoint, 'disabled', (session && !session.capabilities.supportsDataBreakpoints) || !this.debugService.getModel().areBreakpointsActivated()); + if (session && !session.capabilities.supportsDataBreakpoints) { + data.breakpoint.title = nls.localize('dataBreakpointsNotSupported', "Data breakpoints are not supported by this debug type"); + } + } + + disposeTemplate(templateData: IBaseBreakpointWithIconTemplateData): void { + dispose(templateData.toDispose); + } +} + class FunctionBreakpointInputRenderer implements IListRenderer { constructor( @@ -543,9 +604,9 @@ class FunctionBreakpointInputRenderer implements IListRenderer { +export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): Promise { if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) { - return Promise.resolve(null); + return Promise.resolve(undefined); } const selection = breakpoint.endLineNumber ? { @@ -572,7 +633,7 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } -export function getBreakpointMessageAndClassName(debugService: IDebugService, breakpoint: IBreakpoint | FunctionBreakpoint): { message?: string, className: string } { +export function getBreakpointMessageAndClassName(debugService: IDebugService, breakpoint: IBreakpoint | FunctionBreakpoint | DataBreakpoint): { message?: string, className: string } { const state = debugService.state; const debugActive = state === State.Running || state === State.Stopped; @@ -584,7 +645,7 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br } const appendMessage = (text: string): string => { - return !(breakpoint instanceof FunctionBreakpoint) && breakpoint.message ? text.concat(', ' + breakpoint.message) : text; + return !(breakpoint instanceof FunctionBreakpoint) && !(breakpoint instanceof DataBreakpoint) && breakpoint.message ? text.concat(', ' + breakpoint.message) : text; }; if (debugActive && !breakpoint.verified) { return { @@ -607,6 +668,19 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br }; } + if (breakpoint instanceof DataBreakpoint) { + if (session && !session.capabilities.supportsDataBreakpoints) { + return { + className: 'debug-data-breakpoint-unverified', + message: nls.localize('dataBreakpointUnsupported', "Data breakpoints not supported by this debug type"), + }; + } + + return { + className: 'debug-data-breakpoint', + }; + } + if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition) { const messages: string[] = []; if (breakpoint.logMessage) { diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 142460a504c..4786ae44870 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -37,15 +37,15 @@ type CallStackItem = IStackFrame | IThread | IDebugSession | string | ThreadAndS export class CallStackView extends ViewletPanel { - private pauseMessage: HTMLSpanElement; - private pauseMessageLabel: HTMLSpanElement; + private pauseMessage!: HTMLSpanElement; + private pauseMessageLabel!: HTMLSpanElement; private onCallStackChangeScheduler: RunOnceScheduler; - private needsRefresh: boolean; - private ignoreSelectionChangedEvent: boolean; - private ignoreFocusStackFrameEvent: boolean; + private needsRefresh = false; + private ignoreSelectionChangedEvent = false; + private ignoreFocusStackFrameEvent = false; private callStackItemType: IContextKey; - private dataSource: CallStackDataSource; - private tree: WorkbenchAsyncDataTree; + private dataSource!: CallStackDataSource; + private tree!: WorkbenchAsyncDataTree; private contributedContextMenu: IMenu; private parentSessionToExpand = new Set(); @@ -144,7 +144,8 @@ export class CallStackView extends ViewletPanel { return nls.localize('showMoreStackFrames2', "Show More Stack Frames"); } - } + }, + expandOnlyOnTwistieClick: true }); this.tree.setInput(this.debugService.getModel()).then(undefined, onUnexpectedError); @@ -576,7 +577,7 @@ function isDebugModel(obj: any): obj is IDebugModel { } function isDebugSession(obj: any): obj is IDebugSession { - return typeof obj.getAllThreads === 'function'; + return obj && typeof obj.getAllThreads === 'function'; } function isDeemphasized(frame: IStackFrame): boolean { @@ -584,7 +585,7 @@ function isDeemphasized(frame: IStackFrame): boolean { } class CallStackDataSource implements IAsyncDataSource { - deemphasizedStackFramesToShow: IStackFrame[]; + deemphasizedStackFramesToShow: IStackFrame[] = []; constructor(private debugService: IDebugService) { } @@ -607,11 +608,13 @@ class CallStackDataSource implements IAsyncDataSourcethreads[0]) : Promise.resolve(threads); } else if (isDebugSession(element)) { const childSessions = this.debugService.getModel().getSessions().filter(s => s.parentSession === element); - if (childSessions.length) { - return Promise.resolve(childSessions); + const threads: CallStackItem[] = element.getAllThreads(); + if (threads.length === 1 && childSessions.length === 0) { + // Do not show thread when there is only one to be compact. + return this.getThreadChildren(threads[0]); } - return Promise.resolve(element.getAllThreads()); + return Promise.resolve(threads.concat(childSessions)); } else { return this.getThreadChildren(element); } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 9a1431da67e..f3dd7181695 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -259,6 +259,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces."), default: { configurations: [], compounds: [] }, $ref: launchSchemaId + }, + 'debug.focusWindowOnBreak': { + type: 'boolean', + description: nls.localize('debug.focusWindowOnBreak', "Controls whether the workbench window should be focused when the debugger breaks."), + default: true } } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 0e160e29040..8e4b12fde6c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -27,13 +27,13 @@ export class StartDebugActionViewItem implements IActionViewItem { private static readonly SEPARATOR = '─────────'; - public actionRunner: IActionRunner; - private container: HTMLElement; - private start: HTMLElement; + actionRunner!: IActionRunner; + private container!: HTMLElement; + private start!: HTMLElement; private selectBox: SelectBox; - private options: { label: string, handler?: (() => boolean) }[]; + private options: { label: string, handler?: (() => boolean) }[] = []; private toDispose: IDisposable[]; - private selected: number; + private selected = 0; constructor( private context: any, @@ -66,7 +66,7 @@ export class StartDebugActionViewItem implements IActionViewItem { })); } - public render(container: HTMLElement): void { + render(container: HTMLElement): void { this.container = container; dom.addClass(container, 'start-debug-action-item'); this.start = dom.append(container, $('.icon')); @@ -129,15 +129,15 @@ export class StartDebugActionViewItem implements IActionViewItem { this.updateOptions(); } - public setActionContext(context: any): void { + setActionContext(context: any): void { this.context = context; } - public isEnabled(): boolean { + isEnabled(): boolean { return true; } - public focus(fromRight?: boolean): void { + focus(fromRight?: boolean): void { if (fromRight) { this.selectBox.focus(); } else { @@ -145,11 +145,11 @@ export class StartDebugActionViewItem implements IActionViewItem { } } - public blur(): void { + blur(): void { this.container.blur(); } - public dispose(): void { + dispose(): void { this.toDispose = dispose(this.toDispose); } diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index f2ed8a479ac..8f926b2a35a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -9,7 +9,7 @@ import * as lifecycle from 'vs/base/common/lifecycle'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; -import { Variable, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -191,7 +191,7 @@ export class RemoveBreakpointAction extends AbstractDebugAction { public run(breakpoint: IBreakpoint): Promise { return breakpoint instanceof Breakpoint ? this.debugService.removeBreakpoints(breakpoint.getId()) - : this.debugService.removeFunctionBreakpoints(breakpoint.getId()); + : breakpoint instanceof FunctionBreakpoint ? this.debugService.removeFunctionBreakpoints(breakpoint.getId()) : this.debugService.removeDataBreakpoints(breakpoint.getId()); } } @@ -205,7 +205,7 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction { } public run(): Promise { - return Promise.all([this.debugService.removeBreakpoints(), this.debugService.removeFunctionBreakpoints()]); + return Promise.all([this.debugService.removeBreakpoints(), this.debugService.removeFunctionBreakpoints(), this.debugService.removeDataBreakpoints()]); } protected isEnabled(state: State): boolean { diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 95cd771b7f3..7acca069182 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -10,7 +10,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { IListService } from 'vs/platform/list/browser/listService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, REPL_ID, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug'; -import { Expression, Variable, Breakpoint, FunctionBreakpoint, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Expression, Variable, Breakpoint, FunctionBreakpoint, Thread, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -184,7 +184,7 @@ export function registerCommands(): void { id: STEP_INTO_ID, weight: KeybindingWeight.WorkbenchContrib + 10, // Have a stronger weight to have priority over full screen when debugging primary: KeyCode.F11, - when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), + when: CONTEXT_IN_DEBUG_MODE, handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => { getThreadAndRun(accessor, thread, thread => thread.stepIn()); } @@ -410,6 +410,8 @@ export function registerCommands(): void { debugService.removeBreakpoints(element.getId()); } else if (element instanceof FunctionBreakpoint) { debugService.removeFunctionBreakpoints(element.getId()); + } else if (element instanceof DataBreakpoint) { + debugService.removeDataBreakpoints(element.getId()); } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index df8a68cbc67..3e71419c14a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -34,6 +34,8 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { withUndefinedAsNull } from 'vs/base/common/types'; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema(launchSchemaId, launchSchema); @@ -44,7 +46,7 @@ const DEBUG_SELECTED_ROOT = 'debug.selectedroot'; export class ConfigurationManager implements IConfigurationManager { private debuggers: Debugger[]; private breakpointModeIdsSet = new Set(); - private launches: ILaunch[]; + private launches!: ILaunch[]; private selectedName: string | undefined; private selectedLaunch: ILaunch | undefined; private toDispose: IDisposable[]; @@ -180,7 +182,7 @@ export class ConfigurationManager implements IConfigurationManager { return providers.length > 0; } - resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): Promise { + resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise { return this.activateDebuggers('onDebugResolve', type).then(() => { // pipe the config through the promises sequentially. Append at the end the '*' types const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) @@ -189,7 +191,7 @@ export class ConfigurationManager implements IConfigurationManager { return providers.reduce((promise, provider) => { return promise.then(config => { if (config) { - return provider.resolveDebugConfiguration!(folderUri, config); + return provider.resolveDebugConfiguration!(folderUri, config, token); } else { return Promise.resolve(config); } @@ -198,9 +200,9 @@ export class ConfigurationManager implements IConfigurationManager { }); } - provideDebugConfigurations(folderUri: uri | undefined, type: string): Promise { + provideDebugConfigurations(folderUri: uri | undefined, type: string, token: CancellationToken): Promise { return this.activateDebuggers('onDebugInitialConfigurations') - .then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri))) + .then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri, token))) .then(results => results.reduce((first, second) => first.concat(second), []))); } @@ -531,7 +533,7 @@ class Launch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolder; } - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> { + openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }> { const resource = this.uri; let created = false; @@ -539,7 +541,7 @@ class Launch extends AbstractLaunch implements ILaunch { // launch.json not found: create one by collecting launch configs from debugConfigProviders return this.configurationManager.guessDebugger(type).then(adapter => { if (adapter) { - return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type).then(initialConfigs => { + return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type, token || CancellationToken.None).then(initialConfigs => { return adapter.getInitialConfigurationContent(initialConfigs); }); } else { @@ -576,7 +578,7 @@ class Launch extends AbstractLaunch implements ILaunch { pinned: created, revealIfVisible: true }, - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor, created }))); + }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor: withUndefinedAsNull(editor), created }))); }, (error: Error) => { throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message)); }); @@ -612,7 +614,7 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch { return this.editorService.openEditor({ resource: this.contextService.getWorkspace().configuration!, options: { preserveFocus } - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor, created: false })); + }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor: withUndefinedAsNull(editor), created: false })); } } @@ -646,6 +648,6 @@ class UserLaunch extends AbstractLaunch implements ILaunch { } openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> { - return this.preferencesService.openGlobalSettings(false, { preserveFocus }).then(editor => ({ editor, created: false })); + return this.preferencesService.openGlobalSettings(false, { preserveFocus }).then(editor => ({ editor: withUndefinedAsNull(editor), created: false })); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 060f9197074..b7f89249ad9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -35,19 +35,23 @@ class ToggleBreakpointAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const debugService = accessor.get(IDebugService); - - const position = editor.getPosition(); - if (editor.hasModel() && position) { + if (editor.hasModel()) { + const debugService = accessor.get(IDebugService); const modelUri = editor.getModel().uri; - const bps = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri }); + const canSet = debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel()); + // Does not account for multi line selections, Set to remove multiple cursor on the same line + const lineNumbers = [...new Set(editor.getSelections().map(s => s.getPosition().lineNumber))]; - if (bps.length) { - return Promise.all(bps.map(bp => debugService.removeBreakpoints(bp.getId()))); - } - if (debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { - return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber }], 'debugEditorActions.toggleBreakpointAction'); - } + return Promise.all(lineNumbers.map(line => { + const bps = debugService.getModel().getBreakpoints({ lineNumber: line, uri: modelUri }); + if (bps.length) { + return Promise.all(bps.map(bp => debugService.removeBreakpoints(bp.getId()))); + } else if (canSet) { + return (debugService.addBreakpoints(modelUri, [{ lineNumber: line }], 'debugEditorActions.toggleBreakpointAction')); + } else { + return Promise.resolve([]); + } + })); } return Promise.resolve(); @@ -165,7 +169,7 @@ class SelectionToReplAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const debugService = accessor.get(IDebugService); const panelService = accessor.get(IPanelService); const viewModel = debugService.getViewModel(); @@ -175,9 +179,8 @@ class SelectionToReplAction extends EditorAction { } const text = editor.getModel().getValueInRange(editor.getSelection()); - return session.addReplExpression(viewModel.focusedStackFrame!, text) - .then(() => panelService.openPanel(REPL_ID, true)) - .then(_ => undefined); + await session.addReplExpression(viewModel.focusedStackFrame!, text); + await panelService.openPanel(REPL_ID, true); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index de8fe1e188d..2074a14ab74 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -57,8 +57,8 @@ export class DebugEditorContribution implements IDebugEditorContribution { private toDispose: lifecycle.IDisposable[]; private hoverWidget: DebugHoverWidget; - private nonDebugHoverPosition: Position; - private hoverRange: Range; + private nonDebugHoverPosition: Position | undefined; + private hoverRange: Range | null = null; private mouseDown = false; private breakpointHintDecoration: string[]; @@ -68,7 +68,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private exceptionWidget: ExceptionWidget | undefined; - private configurationWidget: FloatingClickWidget; + private configurationWidget: FloatingClickWidget | undefined; constructor( private editor: ICodeEditor, @@ -377,7 +377,11 @@ export class DebugEditorContribution implements IDebugEditorContribution { @memoize private get showHoverScheduler(): RunOnceScheduler { - const scheduler = new RunOnceScheduler(() => this.showHover(this.hoverRange, false), HOVER_DELAY); + const scheduler = new RunOnceScheduler(() => { + if (this.hoverRange) { + this.showHover(this.hoverRange, false); + } + }, HOVER_DELAY); this.toDispose.push(scheduler); return scheduler; @@ -385,7 +389,11 @@ export class DebugEditorContribution implements IDebugEditorContribution { @memoize private get hideHoverScheduler(): RunOnceScheduler { - const scheduler = new RunOnceScheduler(() => this.hoverWidget.hide(), 2 * HOVER_DELAY); + const scheduler = new RunOnceScheduler(() => { + if (!this.hoverWidget.isHovered()) { + this.hoverWidget.hide(); + } + }, 2 * HOVER_DELAY); this.toDispose.push(scheduler); return scheduler; @@ -394,7 +402,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { @memoize private get provideNonDebugHoverScheduler(): RunOnceScheduler { const scheduler = new RunOnceScheduler(() => { - if (this.editor.hasModel()) { + if (this.editor.hasModel() && this.nonDebugHoverPosition) { getHover(this.editor.getModel(), this.nonDebugHoverPosition, CancellationToken.None); } }, HOVER_DELAY); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts index 9a4cdf0bb6e..84dd90e7056 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts @@ -36,7 +36,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { static readonly STICKINESS = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; private modelDataMap: Map; private toDispose: lifecycle.IDisposable[]; - private ignoreDecorationsChangedEvent: boolean; + private ignoreDecorationsChangedEvent = false; constructor( @IModelService private readonly modelService: IModelService, diff --git a/src/vs/workbench/contrib/debug/browser/debugHelperService.ts b/src/vs/workbench/contrib/debug/browser/debugHelperService.ts deleted file mode 100644 index 37d46f87b45..00000000000 --- a/src/vs/workbench/contrib/debug/browser/debugHelperService.ts +++ /dev/null @@ -1,21 +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 { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; -import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -export class BrowserDebugHelperService implements IDebugHelperService { - - _serviceBrand: ServiceIdentifier; - - createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined { - return undefined; - } -} - -registerSingleton(IDebugHelperService, BrowserDebugHelperService); \ No newline at end of file diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index b198f1d9e15..f06919be1d9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -41,17 +41,16 @@ export class DebugHoverWidget implements IContentWidget { allowEditorOverflow = true; private _isVisible: boolean; - private domNode: HTMLElement; - private tree: AsyncDataTree; + private domNode!: HTMLElement; + private tree!: AsyncDataTree; private showAtPosition: Position | null; private highlightDecorations: string[]; - private complexValueContainer: HTMLElement; - private complexValueTitle: HTMLElement; - private valueContainer: HTMLElement; - private treeContainer: HTMLElement; + private complexValueContainer!: HTMLElement; + private complexValueTitle!: HTMLElement; + private valueContainer!: HTMLElement; + private treeContainer!: HTMLElement; private toDispose: lifecycle.IDisposable[]; - private scrollbar: DomScrollableElement; - private dataSource: DebugHoverDataSource; + private scrollbar!: DomScrollableElement; constructor( private editor: ICodeEditor, @@ -72,10 +71,10 @@ export class DebugHoverWidget implements IContentWidget { this.complexValueTitle = dom.append(this.complexValueContainer, $('.title')); this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree')); this.treeContainer.setAttribute('role', 'tree'); - this.dataSource = new DebugHoverDataSource(); + const dataSource = new DebugHoverDataSource(); this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], - this.dataSource, { + dataSource, { ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"), accessibilityProvider: new DebugHoverAccessibilityProvider(), mouseSupport: false, @@ -122,6 +121,10 @@ export class DebugHoverWidget implements IContentWidget { })); } + isHovered(): boolean { + return this.domNode.matches(':hover'); + } + isVisible(): boolean { return this._isVisible; } diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts index d75f5a9b0df..64a399d212f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts @@ -77,7 +77,7 @@ export class DebugQuickOpenHandler extends QuickOpenHandler { public static readonly ID = 'workbench.picker.launch'; - private autoFocusIndex: number; + private autoFocusIndex: number | undefined; constructor( @IDebugService private readonly debugService: IDebugService, diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 1ae35d5a248..7a0b0fe82c4 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expression } from 'vs/workbench/contrib/debug/common/debugModel'; +import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expression, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel'; import * as debugactions from 'vs/workbench/contrib/debug/browser/debugActions'; import { ConfigurationManager } from 'vs/workbench/contrib/debug/browser/debugConfigurationManager'; @@ -47,10 +47,12 @@ import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/error import { RunOnceScheduler } from 'vs/base/common/async'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_BREAKPOINTS_ACTIVATED_KEY = 'debug.breakpointactivated'; const DEBUG_FUNCTION_BREAKPOINTS_KEY = 'debug.functionbreakpoint'; +const DEBUG_DATA_BREAKPOINTS_KEY = 'debug.databreakpoint'; const DEBUG_EXCEPTION_BREAKPOINTS_KEY = 'debug.exceptionbreakpoint'; const DEBUG_WATCH_EXPRESSIONS_KEY = 'debug.watchexpressions'; @@ -87,7 +89,8 @@ export class DebugService implements IDebugService { private inDebugMode: IContextKey; private breakpointsToSendOnResourceSaved: Set; private initializing = false; - private previousState: State; + private previousState: State | undefined; + private initCancellationToken: CancellationTokenSource | undefined; constructor( @IStorageService private readonly storageService: IStorageService, @@ -127,7 +130,7 @@ export class DebugService implements IDebugService { this.inDebugMode = CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService); this.model = new DebugModel(this.loadBreakpoints(), this.storageService.getBoolean(DEBUG_BREAKPOINTS_ACTIVATED_KEY, StorageScope.WORKSPACE, true), this.loadFunctionBreakpoints(), - this.loadExceptionBreakpoints(), this.loadWatchExpressions(), this.textFileService); + this.loadExceptionBreakpoints(), this.loadDataBreakpoints(), this.loadWatchExpressions(), this.textFileService); this.toDispose.push(this.model); this.viewModel = new ViewModel(contextKeyService); @@ -211,6 +214,10 @@ export class DebugService implements IDebugService { } private endInitializingState() { + if (this.initCancellationToken) { + this.initCancellationToken.cancel(); + this.initCancellationToken = undefined; + } if (this.initializing) { this.initializing = false; this.onStateChange(); @@ -355,8 +362,9 @@ export class DebugService implements IDebugService { } const debuggerThenable: Promise = type ? Promise.resolve() : this.configurationManager.guessDebugger().then(dbgr => { type = dbgr && dbgr.type; }); - return debuggerThenable.then(() => - this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!).then(config => { + return debuggerThenable.then(() => { + this.initCancellationToken = new CancellationTokenSource(); + return this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!, this.initCancellationToken.token).then(config => { // a falsy config indicates an aborted launch if (config && config.type) { return this.substituteVariables(launch, config).then(resolvedConfig => { @@ -396,17 +404,17 @@ export class DebugService implements IDebugService { .then(() => false); } - return launch && launch.openConfigFile(false, true).then(() => false); + return !!launch && launch.openConfigFile(false, true, undefined, this.initCancellationToken ? this.initCancellationToken.token : undefined).then(() => false); }); } if (launch && type && config === null) { // show launch.json only for "config" being "null". - return launch.openConfigFile(false, true, type).then(() => false); + return launch.openConfigFile(false, true, type, this.initCancellationToken ? this.initCancellationToken.token : undefined).then(() => false); } return false; - }) - ); + }); + }); } /** @@ -534,6 +542,10 @@ export class DebugService implements IDebugService { if (this.layoutService.isVisible(Parts.SIDEBAR_PART) && this.configurationService.getValue('debug').openExplorerOnEnd) { this.viewletService.openViewlet(EXPLORER_VIEWLET_ID); } + + // Data breakpoints that can not be persisted should be cleared when a session ends + const dataBreakpoints = this.model.getDataBreakpoints().filter(dbp => !dbp.canPersist); + dataBreakpoints.forEach(dbp => this.model.removeDataBreakpoints(dbp.getId())); } })); @@ -587,7 +599,8 @@ export class DebugService implements IDebugService { let substitutionThenable: Promise = Promise.resolve(session.configuration); if (launch && needsToSubstitute && unresolved) { - substitutionThenable = this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved) + this.initCancellationToken = new CancellationTokenSource(); + substitutionThenable = this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved, this.initCancellationToken.token) .then(resolved => { if (resolved) { // start debugging @@ -843,6 +856,8 @@ export class DebugService implements IDebugService { await this.sendBreakpoints(breakpoint.uri); } else if (breakpoint instanceof FunctionBreakpoint) { await this.sendFunctionBreakpoints(); + } else if (breakpoint instanceof DataBreakpoint) { + await this.sendDataBreakpoints(); } else { await this.sendExceptionBreakpoints(); } @@ -912,6 +927,19 @@ export class DebugService implements IDebugService { this.storeBreakpoints(); } + async addDataBreakpoint(label: string, dataId: string, canPersist: boolean): Promise { + this.model.addDataBreakpoint(label, dataId, canPersist); + await this.sendDataBreakpoints(); + + this.storeBreakpoints(); + } + + async removeDataBreakpoints(id?: string): Promise { + this.model.removeDataBreakpoints(id); + await this.sendDataBreakpoints(); + this.storeBreakpoints(); + } + sendAllBreakpoints(session?: IDebugSession): Promise { return Promise.all(distinct(this.model.getBreakpoints(), bp => bp.uri.toString()).map(bp => this.sendBreakpoints(bp.uri, false, session))) .then(() => this.sendFunctionBreakpoints(session)) @@ -935,6 +963,14 @@ export class DebugService implements IDebugService { }); } + private sendDataBreakpoints(session?: IDebugSession): Promise { + const breakpointsToSend = this.model.getDataBreakpoints().filter(fbp => fbp.enabled && this.model.areBreakpointsActivated()); + + return this.sendToOneOrAllSessions(session, s => { + return s.capabilities.supportsDataBreakpoints ? s.sendDataBreakpoints(breakpointsToSend) : Promise.resolve(undefined); + }); + } + private sendExceptionBreakpoints(session?: IDebugSession): Promise { const enabledExceptionBps = this.model.getExceptionBreakpoints().filter(exb => exb.enabled); @@ -998,6 +1034,17 @@ export class DebugService implements IDebugService { return result || []; } + private loadDataBreakpoints(): DataBreakpoint[] { + let result: DataBreakpoint[] | undefined; + try { + result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: any) => { + return new DataBreakpoint(dbp.label, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage); + }); + } catch (e) { } + + return result || []; + } + private loadWatchExpressions(): Expression[] { let result: Expression[] | undefined; try { @@ -1033,6 +1080,13 @@ export class DebugService implements IDebugService { this.storageService.remove(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE); } + const dataBreakpoints = this.model.getDataBreakpoints().filter(dbp => dbp.canPersist); + if (dataBreakpoints.length) { + this.storageService.store(DEBUG_DATA_BREAKPOINTS_KEY, JSON.stringify(dataBreakpoints), StorageScope.WORKSPACE); + } else { + this.storageService.remove(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE); + } + const exceptionBreakpoints = this.model.getExceptionBreakpoints(); if (exceptionBreakpoints.length) { this.storageService.store(DEBUG_EXCEPTION_BREAKPOINTS_KEY, JSON.stringify(exceptionBreakpoints), StorageScope.WORKSPACE); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 603797e9d04..01151248238 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -12,7 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { CompletionItem, completionKindFromString } from 'vs/editor/common/modes'; import { Position } from 'vs/editor/common/core/position'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { mixin } from 'vs/base/common/objects'; import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; @@ -31,6 +31,8 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; import { onUnexpectedError } from 'vs/base/common/errors'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView'; export class DebugSession implements IDebugSession { @@ -42,7 +44,7 @@ export class DebugSession implements IDebugSession { private sources = new Map(); private threads = new Map(); private rawListeners: IDisposable[] = []; - private fetchThreadsScheduler: RunOnceScheduler; + private fetchThreadsScheduler: RunOnceScheduler | undefined; private repl: ReplModel; private readonly _onDidChangeState = new Emitter(); @@ -66,7 +68,8 @@ export class DebugSession implements IDebugSession { @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @INotificationService private readonly notificationService: INotificationService, @IProductService private readonly productService: IProductService, - @IWindowsService private readonly windowsService: IWindowsService + @IWindowsService private readonly windowsService: IWindowsService, + @IOpenerService private readonly openerService: IOpenerService ) { this.id = generateUuid(); this.repl = new ReplModel(this); @@ -167,9 +170,9 @@ export class DebugSession implements IDebugSession { return dbgr.createDebugAdapter(this).then(debugAdapter => { - this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.windowsService); + this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.windowsService, this.openerService); - return this.raw!.start().then(() => { + return this.raw.start().then(() => { this.registerListeners(); @@ -327,6 +330,34 @@ export class DebugSession implements IDebugSession { return Promise.reject(new Error('no debug adapter')); } + dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }> { + if (this.raw) { + if (this.raw.readyForBreakpoints) { + return this.raw.dataBreakpointInfo({ name, variablesReference }).then(response => response.body); + } + return Promise.reject(new Error(nls.localize('sessionNotReadyForBreakpoints', "Session is not ready for breakpoints"))); + } + return Promise.reject(new Error('no debug adapter')); + } + + sendDataBreakpoints(dataBreakpoints: IDataBreakpoint[]): Promise { + if (this.raw) { + if (this.raw.readyForBreakpoints) { + return this.raw.setDataBreakpoints({ breakpoints: dataBreakpoints }).then(response => { + if (response && response.body) { + const data = new Map(); + for (let i = 0; i < dataBreakpoints.length; i++) { + data.set(dataBreakpoints[i].getId(), response.body.breakpoints[i]); + } + this.model.setBreakpointSessionData(this.getId(), data); + } + }); + } + return Promise.resolve(undefined); + } + return Promise.reject(new Error('no debug adapter')); + } + customRequest(request: string, args: any): Promise { if (this.raw) { return this.raw.custom(request, args); @@ -524,7 +555,8 @@ export class DebugSession implements IDebugSession { insertText: item.text || item.label, kind: completionKindFromString(item.type || 'property'), filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined, - range: Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position) + range: Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position), + sortText: item.sortText }); } }); @@ -674,7 +706,10 @@ export class DebugSession implements IDebugSession { if (this.configurationService.getValue('debug').openDebug === 'openOnDebugBreak') { this.viewletService.openViewlet(VIEWLET_ID); } - this.windowService.focusWindow(); + + if (this.configurationService.getValue('debug').focusWindowOnBreak) { + this.windowService.focusWindow(); + } } } }; @@ -872,12 +907,13 @@ export class DebugSession implements IDebugSession { this._onDidChangeREPLElements.fire(); } - addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise { + async addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise { const viewModel = this.debugService.getViewModel(); - return this.repl.addReplExpression(stackFrame, name) - .then(() => this._onDidChangeREPLElements.fire()) - // Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some. - .then(() => this.debugService.focusStackFrame(viewModel.focusedStackFrame, viewModel.focusedThread, viewModel.focusedSession)); + await this.repl.addReplExpression(stackFrame, name); + this._onDidChangeREPLElements.fire(); + // Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some. + this.debugService.focusStackFrame(viewModel.focusedStackFrame, viewModel.focusedThread, viewModel.focusedSession); + variableSetEmitter.fire(); } appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugStatus.ts b/src/vs/workbench/contrib/debug/browser/debugStatus.ts index 1ea94d43674..20f73614519 100644 --- a/src/vs/workbench/contrib/debug/browser/debugStatus.ts +++ b/src/vs/workbench/contrib/debug/browser/debugStatus.ts @@ -13,7 +13,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; export class DebugStatusContribution implements IWorkbenchContribution { - private showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; + private showInStatusBar!: 'never' | 'always' | 'onFirstSessionStart'; private toDispose: IDisposable[] = []; private entryAccessor: IStatusbarEntryAccessor | undefined; diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index dc603f4eedb..5146eac7f78 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -55,10 +55,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { private activeActions: IAction[]; private updateScheduler: RunOnceScheduler; private debugToolBarMenu: IMenu; - private disposeOnUpdate: IDisposable; + private disposeOnUpdate: IDisposable | undefined; - private isVisible: boolean; - private isBuilt: boolean; + private isVisible = false; + private isBuilt = false; constructor( @INotificationService private readonly notificationService: INotificationService, @@ -126,7 +126,6 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.registerListeners(); this.hide(); - this.isBuilt = false; } private registerListeners(): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index c4bf84c04a0..10681dbeda0 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -36,12 +36,12 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; export class DebugViewlet extends ViewContainerViewlet { - private startDebugActionViewItem: StartDebugActionViewItem; + private startDebugActionViewItem: StartDebugActionViewItem | undefined; private progressResolve: (() => void) | undefined; - private breakpointView: ViewletPanel; + private breakpointView: ViewletPanel | undefined; private panelListeners = new Map(); - private debugToolBarMenu: IMenu; - private disposeOnTitleUpdate: IDisposable; + private debugToolBarMenu: IMenu | undefined; + private disposeOnTitleUpdate: IDisposable | undefined; constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index 889cef64500..0d038ec13c9 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -63,8 +63,8 @@ export class ExceptionWidget extends ZoneWidget { this.setCssClass('exception-widget'); // Set the font size and line height to the one from the editor configuration. const fontInfo = this.editor.getConfiguration().fontInfo; - this.container.style.fontSize = `${fontInfo.fontSize}px`; - this.container.style.lineHeight = `${fontInfo.lineHeight}px`; + container.style.fontSize = `${fontInfo.fontSize}px`; + container.style.lineHeight = `${fontInfo.lineHeight}px`; let title = $('.title'); title.textContent = this.exceptionInfo.id ? nls.localize('exceptionThrownWithId', 'Exception has occurred: {0}', this.exceptionInfo.id) : nls.localize('exceptionThrown', 'Exception has occurred.'); @@ -87,11 +87,11 @@ export class ExceptionWidget extends ZoneWidget { protected _doLayout(_heightInPixel: number | undefined, _widthInPixel: number | undefined): void { // Reload the height with respect to the exception text content and relayout it to match the line count. - this.container.style.height = 'initial'; + this.container!.style.height = 'initial'; const lineHeight = this.editor.getConfiguration().lineHeight; const arrowHeight = Math.round(lineHeight / 3); - const computedLinesNumber = Math.ceil((this.container.offsetHeight + arrowHeight) / lineHeight); + const computedLinesNumber = Math.ceil((this.container!.offsetHeight + arrowHeight) / lineHeight); this._relayout(computedLinesNumber); } diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts new file mode 100644 index 00000000000..5be0fa24b5c --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; +import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; + +class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient { + + constructor( + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + // @IWindowService windowService: IWindowService, // TODO@weinand TODO@isidorn cyclic dependency? + @IEnvironmentService environmentService: IEnvironmentService + ) { + const connection = remoteAgentService.getConnection(); + + if (!connection) { + throw new Error('Missing agent connection'); + } + + super(connection.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); + + this._register(this.onReload(event => { + if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { + window.location.reload(); + } + })); + this._register(this.onClose(event => { + if (environmentService.isExtensionDevelopment && environmentService.debugExtensionHost.debugId === event.sessionId) { + window.close(); + } + })); + } +} + +registerSingleton(IExtensionHostDebugService, BrowserExtensionHostDebugService); + +class BrowserDebugHelperService implements IDebugHelperService { + + _serviceBrand!: ServiceIdentifier; + + createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined { + return undefined; + } +} + +registerSingleton(IDebugHelperService, BrowserDebugHelperService); diff --git a/src/vs/workbench/contrib/debug/browser/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts index 58cbb394e5f..df911b7d7c9 100644 --- a/src/vs/workbench/contrib/debug/browser/linkDetector.ts +++ b/src/vs/workbench/contrib/debug/browser/linkDetector.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import strings = require('vs/base/common/strings'); +import * as strings from 'vs/base/common/strings'; import { isAbsolute } from 'vs/base/common/path'; import { URI as uri } from 'vs/base/common/uri'; import { isMacintosh } from 'vs/base/common/platform'; diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 70a962e46ec..6a70b78319b 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -46,7 +46,7 @@ class BaseTreeItem { private _showedMoreThanOne: boolean; private _children = new Map(); - private _source: Source; + private _source: Source | undefined; constructor(private _parent: BaseTreeItem | undefined, private _label: string) { this._showedMoreThanOne = false; @@ -184,7 +184,7 @@ class BaseTreeItem { } // skips intermediate single-child nodes - getSource(): Source { + getSource(): Source | undefined { const child = this.oneChild(); if (child) { return child.getSource(); @@ -381,13 +381,13 @@ class SessionTreeItem extends BaseTreeItem { export class LoadedScriptsView extends ViewletPanel { - private treeContainer: HTMLElement; + private treeContainer!: HTMLElement; private loadedScriptsItemType: IContextKey; - private tree: WorkbenchAsyncDataTree; - private treeLabels: ResourceLabels; - private changeScheduler: RunOnceScheduler; - private treeNeedsRefreshOnVisible: boolean; - private filter: LoadedScriptsFilter; + private tree!: WorkbenchAsyncDataTree; + private treeLabels!: ResourceLabels; + private changeScheduler!: RunOnceScheduler; + private treeNeedsRefreshOnVisible = false; + private filter!: LoadedScriptsFilter; constructor( options: IViewletViewOptions, @@ -635,7 +635,7 @@ class LoadedSciptsAccessibilityProvider implements IAccessibilityProvider { - private filterText: string; + private filterText: string | undefined; setFilter(filterText: string) { this.filterText = filterText; diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index a0f2120b920..d8ad2212ace 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -16,6 +16,8 @@ import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import { IProcessEnvironment } from 'vs/base/common/platform'; +import { env as processEnv } from 'vs/base/common/process'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; /** * This interface represents a single command line argument split into a "prefix" and a "path" half. @@ -37,19 +39,19 @@ interface ILaunchVSCodeArguments { */ export class RawDebugSession { - private allThreadsContinued: boolean; - private _readyForBreakpoints: boolean; + private allThreadsContinued = true; + private _readyForBreakpoints = false; private _capabilities: DebugProtocol.Capabilities; // shutdown - private debugAdapterStopped: boolean; - private inShutdown: boolean; - private terminated: boolean; - private firedAdapterExitEvent: boolean; + private debugAdapterStopped = false; + private inShutdown = false; + private terminated = false; + private firedAdapterExitEvent = false; // telemetry - private startTime: number; - private didReceiveStoppedEvent: boolean; + private startTime = 0; + private didReceiveStoppedEvent = false; // DAP events private readonly _onDidInitialize: Emitter; @@ -73,18 +75,12 @@ export class RawDebugSession { dbgr: IDebugger, private readonly telemetryService: ITelemetryService, public readonly customTelemetryService: ITelemetryService | undefined, - private readonly windowsService: IWindowsService + private readonly windowsService: IWindowsService, + private readonly openerService: IOpenerService ) { this.debugAdapter = debugAdapter; this._capabilities = Object.create(null); - this._readyForBreakpoints = false; - this.inShutdown = false; - this.debugAdapterStopped = false; - this.firedAdapterExitEvent = false; - this.didReceiveStoppedEvent = false; - - this.allThreadsContinued = true; this._onDidInitialize = new Emitter(); this._onDidStop = new Emitter(); @@ -363,6 +359,20 @@ export class RawDebugSession { return Promise.reject(new Error('setFunctionBreakpoints not supported')); } + dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise { + if (this.capabilities.supportsDataBreakpoints) { + return this.send('dataBreakpointInfo', args); + } + return Promise.reject(new Error('dataBreakpointInfo not supported')); + } + + setDataBreakpoints(args: DebugProtocol.SetDataBreakpointsArguments): Promise { + if (this.capabilities.supportsDataBreakpoints) { + return this.send('setDataBreakpoints', args); + } + return Promise.reject(new Error('setDataBreakpoints not supported')); + } + setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): Promise { return this.send('setExceptionBreakpoints', args); } @@ -601,10 +611,7 @@ export class RawDebugSession { let env: IProcessEnvironment = {}; if (vscodeArgs.env) { // merge environment variables into a copy of the process.env - if (typeof process === 'object' && process.env) { - env = objects.mixin(env, process.env); - } - env = objects.mixin(env, vscodeArgs.env); + env = objects.mixin(processEnv, vscodeArgs.env); // and delete some if necessary Object.keys(env).filter(k => env[k] === null).forEach(key => delete env[key]); } @@ -647,7 +654,7 @@ export class RawDebugSession { const label = error.urlLabel ? error.urlLabel : nls.localize('moreInfo', "More Info"); return createErrorWithActions(userMessage, { actions: [new Action('debug.moreInfo', label, undefined, true, () => { - window.open(error.url); + this.openerService.open(URI.parse(error.url)); return Promise.resolve(null); })] }); diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 922de4fe53a..96bb2f8da9d 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -62,7 +62,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { VariablesRenderer, variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; const $ = dom.$; @@ -92,18 +92,19 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private static readonly REPL_INPUT_MAX_HEIGHT = 170; private history: HistoryNavigator; - private tree: WorkbenchAsyncDataTree; - private replDelegate: ReplDelegate; - private container: HTMLElement; - private replInput: CodeEditorWidget; - private replInputContainer: HTMLElement; - private dimension: dom.Dimension; + private tree!: WorkbenchAsyncDataTree; + private replDelegate!: ReplDelegate; + private container!: HTMLElement; + private replInput!: CodeEditorWidget; + private replInputContainer!: HTMLElement; + private dimension!: dom.Dimension; private replInputHeight: number; - private model: ITextModel; - private historyNavigationEnablement: IContextKey; - private scopedInstantiationService: IInstantiationService; - private replElementsChangeListener: IDisposable; - private styleElement: HTMLStyleElement; + private model!: ITextModel; + private historyNavigationEnablement!: IContextKey; + private scopedInstantiationService!: IInstantiationService; + private replElementsChangeListener: IDisposable | undefined; + private styleElement: HTMLStyleElement | undefined; + private completionItemProvider: IDisposable | undefined; constructor( @IDebugService private readonly debugService: IDebugService, @@ -131,7 +132,33 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this._register(this.debugService.getViewModel().onDidFocusSession(session => { if (session) { sessionsToIgnore.delete(session); + if (this.completionItemProvider) { + this.completionItemProvider.dispose(); + } + if (session.capabilities.supportsCompletionsRequest) { + this.completionItemProvider = CompletionProviderRegistry.register({ scheme: DEBUG_SCHEME, pattern: '**/replinput', hasAccessToAllModels: true }, { + triggerCharacters: session.capabilities.completionTriggerCharacters || ['.'], + provideCompletionItems: async (_: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise => { + // Disable history navigation because up and down are used to navigate through the suggest widget + this.historyNavigationEnablement.set(false); + + const model = this.replInput.getModel(); + if (model) { + const word = model.getWordAtPosition(position); + const overwriteBefore = word ? word.word.length : 0; + const text = model.getLineContent(position.lineNumber); + const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; + const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined; + const suggestions = await session.completions(frameId, text, position, overwriteBefore); + return { suggestions }; + } + + return Promise.resolve({ suggestions: [] }); + } + }); + } } + this.selectSession(); })); this._register(this.debugService.onWillNewSession(newSession => { @@ -274,7 +301,6 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati revealLastElement(this.tree); this.history.add(this.replInput.getValue()); this.replInput.setValue(''); - variableSetEmitter.fire(); const shouldRelayout = this.replInputHeight > Repl.REPL_INPUT_INITIAL_HEIGHT; this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; if (shouldRelayout) { @@ -418,6 +444,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private createReplInput(container: HTMLElement): void { this.replInputContainer = dom.append(container, $('.repl-input-wrapper')); + this.replInputContainer.title = nls.localize('debugConsole', "Debug Console"); const { scopedContextKeyService, historyNavigationEnablement } = createAndBindHistoryNavigationWidgetScopedContextKeyService(this.contextKeyService, { target: this.replInputContainer, historyNavigator: this }); this.historyNavigationEnablement = historyNavigationEnablement; @@ -430,34 +457,6 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati options.readOnly = true; this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); - CompletionProviderRegistry.register({ scheme: DEBUG_SCHEME, pattern: '**/replinput', hasAccessToAllModels: true }, { - triggerCharacters: ['.'], - provideCompletionItems: (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise => { - // Disable history navigation because up and down are used to navigate through the suggest widget - this.historyNavigationEnablement.set(false); - - const focusedSession = this.debugService.getViewModel().focusedSession; - if (focusedSession && focusedSession.capabilities.supportsCompletionsRequest) { - - const model = this.replInput.getModel(); - if (model) { - const word = model.getWordAtPosition(position); - const overwriteBefore = word ? word.word.length : 0; - const text = model.getLineContent(position.lineNumber); - const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; - const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined; - - return focusedSession.completions(frameId, text, position, overwriteBefore).then(suggestions => { - return { suggestions }; - }, err => { - return { suggestions: [] }; - }); - } - } - return Promise.resolve({ suggestions: [] }); - } - }); - this._register(this.replInput.onDidScrollChange(e => { if (!e.scrollHeightChanged) { return; diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index b2926f0be76..1819f552ec3 100644 --- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -34,7 +34,7 @@ export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBor }, localize('statusBarDebuggingBorder', "Status bar border color separating to the sidebar and editor when a program is being debugged. The status bar is shown in the bottom of the window")); export class StatusBarColorProvider extends Themable implements IWorkbenchContribution { - private styleElement: HTMLStyleElement; + private styleElement: HTMLStyleElement | undefined; constructor( @IThemeService themeService: IThemeService, diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 71559c50d3c..052f2506db1 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -39,8 +39,8 @@ export const variableSetEmitter = new Emitter(); export class VariablesView extends ViewletPanel { private onFocusStackFrameScheduler: RunOnceScheduler; - private needsRefresh: boolean; - private tree: WorkbenchAsyncDataTree; + private needsRefresh = false; + private tree!: WorkbenchAsyncDataTree; private savedViewState: IAsyncDataTreeViewState | undefined; constructor( @@ -123,7 +123,7 @@ export class VariablesView extends ViewletPanel { this.tree.updateChildren(); })); this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); - this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); + this._register(this.tree.onContextMenu(async e => await this.onContextMenu(e))); this._register(this.onDidChangeBodyVisibility(visible => { if (visible && this.needsRefresh) { @@ -152,7 +152,7 @@ export class VariablesView extends ViewletPanel { } } - private onContextMenu(e: ITreeContextMenuEvent): void { + private async onContextMenu(e: ITreeContextMenuEvent): Promise { const variable = e.element; if (variable instanceof Variable && !!variable.value) { const actions: IAction[] = []; @@ -174,6 +174,16 @@ export class VariablesView extends ViewletPanel { return Promise.resolve(undefined); })); } + if (session && session.capabilities.supportsDataBreakpoints) { + const response = await session.dataBreakpointInfo(variable.name, variable.parent.reference); + const dataid = response.dataId; + if (dataid) { + actions.push(new Separator()); + actions.push(new Action('debug.addDataBreakpoint', nls.localize('setDataBreakpoint', "Set Data Breakpoint"), undefined, true, () => { + return this.debugService.addDataBreakpoint(response.description, dataid, !!response.canPersist); + })); + } + } this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 7b0430d2504..ecc9524f016 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -36,8 +36,8 @@ const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export class WatchExpressionsView extends ViewletPanel { private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; - private needsRefresh: boolean; - private tree: WorkbenchAsyncDataTree; + private needsRefresh = false; + private tree!: WorkbenchAsyncDataTree; constructor( options: IViewletViewOptions, diff --git a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts index 17dfdf746ad..74d087e06b7 100644 --- a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts +++ b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts @@ -14,9 +14,9 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { private sequence: number; private pendingRequests = new Map void>(); - private requestCallback: (request: DebugProtocol.Request) => void; - private eventCallback: (request: DebugProtocol.Event) => void; - private messageCallback: (message: DebugProtocol.ProtocolMessage) => void; + private requestCallback: ((request: DebugProtocol.Request) => void) | undefined; + private eventCallback: ((request: DebugProtocol.Event) => void) | undefined; + private messageCallback: ((message: DebugProtocol.ProtocolMessage) => void) | undefined; protected readonly _onError: Emitter; protected readonly _onExit: Emitter; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 1ac7b96593d..42a35769797 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -25,6 +25,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const VIEWLET_ID = 'workbench.view.debug'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); @@ -102,6 +103,7 @@ export interface IReplElementSource { export interface IExpressionContainer extends ITreeElement { readonly hasChildren: boolean; getChildren(): Promise; + readonly reference?: number; } export interface IExpression extends IReplElement, IExpressionContainer { @@ -133,7 +135,7 @@ export function getStateLabel(state: State): string { } } -export class AdapterEndEvent { +export interface AdapterEndEvent { error?: Error; sessionLengthInSeconds: number; emittedStopped: boolean; @@ -200,6 +202,8 @@ export interface IDebugSession extends ITreeElement { sendBreakpoints(modelUri: uri, bpts: IBreakpoint[], sourceModified: boolean): Promise; sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise; + dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }>; + sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise; sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise; stackTrace(threadId: number, startFrame: number, levels: number): Promise; @@ -356,6 +360,12 @@ export interface IExceptionBreakpoint extends IEnablement { readonly label: string; } +export interface IDataBreakpoint extends IBaseBreakpoint { + readonly label: string; + readonly dataId: string; + readonly canPersist: boolean; +} + export interface IExceptionInfo { readonly id?: string; readonly description?: string; @@ -403,6 +413,7 @@ export interface IDebugModel extends ITreeElement { getBreakpoints(filter?: { uri?: uri, lineNumber?: number, column?: number, enabledOnly?: boolean }): ReadonlyArray; areBreakpointsActivated(): boolean; getFunctionBreakpoints(): ReadonlyArray; + getDataBreakpoints(): ReadonlyArray; getExceptionBreakpoints(): ReadonlyArray; getWatchExpressions(): ReadonlyArray; @@ -415,9 +426,9 @@ export interface IDebugModel extends ITreeElement { * An event describing a change to the set of [breakpoints](#debug.Breakpoint). */ export interface IBreakpointsChangeEvent { - added?: Array; - removed?: Array; - changed?: Array; + added?: Array; + removed?: Array; + changed?: Array; sessionOnly?: boolean; } @@ -440,6 +451,7 @@ export interface IDebugConfiguration { lineHeight: number; wordWrap: boolean; }; + focusWindowOnBreak: boolean; } export interface IGlobalConfig { @@ -556,8 +568,8 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut export interface IDebugConfigurationProvider { readonly type: string; - resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): Promise; - provideDebugConfigurations?(folderUri: uri | undefined): Promise; + resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig, token: CancellationToken): Promise; + provideDebugConfigurations?(folderUri: uri | undefined, token: CancellationToken): Promise; debugAdapterExecutable?(folderUri: uri | undefined): Promise; // TODO@AW legacy } @@ -609,7 +621,7 @@ export interface IConfigurationManager { registerDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): IDisposable; unregisterDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): void; - resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): Promise; + resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any, token: CancellationToken): Promise; getDebugAdapterDescriptor(session: IDebugSession): Promise; registerDebugAdapterFactory(debugTypes: string[], debugAdapterFactory: IDebugAdapterFactory): IDisposable; @@ -662,7 +674,7 @@ export interface ILaunch { /** * Opens the launch.json file. Creates if it does not exist. */ - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }>; + openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditor | null, created: boolean }>; } // Debug service interfaces @@ -752,6 +764,17 @@ export interface IDebugService { */ removeFunctionBreakpoints(id?: string): Promise; + /** + * Adds a new data breakpoint. + */ + addDataBreakpoint(label: string, dataId: string, canPersist: boolean): Promise; + + /** + * Removes all data breakpoints. If id is passed only removes the data breakpoint with the passed id. + * Notifies debug adapter of breakpoint changes. + */ + removeDataBreakpoints(id?: string): Promise; + /** * Sends all breakpoints to the passed session. * If session is not passed, sends all breakpoints to each session. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 598c3b95a17..599c496dcd9 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -16,7 +16,7 @@ import { distinct, lastIndex } from 'vs/base/common/arrays'; import { Range, IRange } from 'vs/editor/common/core/range'; import { ITreeElement, IExpression, IExpressionContainer, IDebugSession, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IDebugModel, IReplElementSource, - IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IReplElement, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint, State + IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IReplElement, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint, State, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; import { Source, UNKNOWN_SOURCE_LABEL } from 'vs/workbench/contrib/debug/common/debugSource'; import { commonSuffixLength } from 'vs/base/common/strings'; @@ -94,8 +94,8 @@ export class ExpressionContainer implements IExpressionContainer { // Use chunks to support variable paging #9537 private static readonly BASE_CHUNK_SIZE = 100; - public valueChanged: boolean; - private _value: string; + public valueChanged = false; + private _value: string = ''; protected children?: Promise; constructor( @@ -201,7 +201,7 @@ export class Expression extends ExpressionContainer implements IExpression { static DEFAULT_VALUE = nls.localize('notAvailable', "not available"); public available: boolean; - public type: string; + public type: string | undefined; constructor(public name: string, id = generateUuid()) { super(undefined, 0, id); @@ -227,14 +227,14 @@ export class Expression extends ExpressionContainer implements IExpression { const response = await session.evaluate(this.name, stackFrame ? stackFrame.frameId : undefined, context); this.available = !!(response && response.body); if (response && response.body) { - this.value = response.body.result; + this.value = response.body.result || ''; this.reference = response.body.variablesReference; this.namedVariables = response.body.namedVariables; this.indexedVariables = response.body.indexedVariables; this.type = response.body.type || this.type; } } catch (e) { - this.value = e.message; + this.value = e.message || ''; this.available = false; this.reference = 0; } @@ -256,7 +256,7 @@ export class Variable extends ExpressionContainer implements IExpression { reference: number | undefined, public name: string, public evaluateName: string | undefined, - value: string, + value: string | undefined, namedVariables: number | undefined, indexedVariables: number | undefined, public presentationHint: DebugProtocol.VariablePresentationHint | undefined, @@ -265,7 +265,7 @@ export class Variable extends ExpressionContainer implements IExpression { startOfVariables = 0 ) { super(session, reference, `variable:${parent.getId()}:${name}`, namedVariables, indexedVariables, startOfVariables); - this.value = value; + this.value = value || ''; } async setVariable(value: string): Promise { @@ -276,7 +276,7 @@ export class Variable extends ExpressionContainer implements IExpression { try { const response = await this.session.setVariable((this.parent).reference, this.name, value); if (response && response.body) { - this.value = response.body.value; + this.value = response.body.value || ''; this.type = response.body.type || this.type; this.reference = response.body.variablesReference; this.namedVariables = response.body.namedVariables; @@ -735,6 +735,34 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak } } +export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { + + constructor( + public label: string, + public dataId: string, + public canPersist: boolean, + enabled: boolean, + hitCondition: string | undefined, + condition: string | undefined, + logMessage: string | undefined, + id = generateUuid() + ) { + super(enabled, hitCondition, condition, logMessage, id); + } + + toJSON(): any { + const result = super.toJSON(); + result.label = this.label; + result.dataid = this.dataId; + + return result; + } + + toString(): string { + return this.label; + } +} + export class ExceptionBreakpoint extends Enablement implements IExceptionBreakpoint { constructor(public filter: string, public label: string, enabled: boolean) { @@ -778,6 +806,7 @@ export class DebugModel implements IDebugModel { private breakpointsActivated: boolean, private functionBreakpoints: FunctionBreakpoint[], private exceptionBreakpoints: ExceptionBreakpoint[], + private dataBreakopints: DataBreakpoint[], private watchExpressions: Expression[], private textFileService: ITextFileService ) { @@ -918,6 +947,10 @@ export class DebugModel implements IDebugModel { return this.functionBreakpoints; } + getDataBreakpoints(): IDataBreakpoint[] { + return this.dataBreakopints; + } + getExceptionBreakpoints(): IExceptionBreakpoint[] { return this.exceptionBreakpoints; } @@ -991,6 +1024,12 @@ export class DebugModel implements IDebugModel { fbp.setSessionData(sessionId, fbpData); } }); + this.dataBreakopints.forEach(dbp => { + const dbpData = data.get(dbp.getId()); + if (dbpData) { + dbp.setSessionData(sessionId, dbpData); + } + }); this._onDidChangeBreakpoints.fire({ sessionOnly: true @@ -1001,6 +1040,7 @@ export class DebugModel implements IDebugModel { this.breakpointsSessionId = sessionId; this.breakpoints.forEach(bp => bp.setSessionId(sessionId)); this.functionBreakpoints.forEach(fbp => fbp.setSessionId(sessionId)); + this.dataBreakopints.forEach(dbp => dbp.setSessionId(sessionId)); this._onDidChangeBreakpoints.fire({ sessionOnly: true @@ -1038,7 +1078,7 @@ export class DebugModel implements IDebugModel { } enableOrDisableAllBreakpoints(enable: boolean): void { - const changed: Array = []; + const changed: Array = []; this.breakpoints.forEach(bp => { if (bp.enabled !== enable) { @@ -1052,6 +1092,12 @@ export class DebugModel implements IDebugModel { } fbp.enabled = enable; }); + this.dataBreakopints.forEach(dbp => { + if (dbp.enabled !== enable) { + changed.push(dbp); + } + dbp.enabled = enable; + }); this._onDidChangeBreakpoints.fire({ changed: changed }); } @@ -1073,7 +1119,6 @@ export class DebugModel implements IDebugModel { } removeFunctionBreakpoints(id?: string): void { - let removed: FunctionBreakpoint[]; if (id) { removed = this.functionBreakpoints.filter(fbp => fbp.getId() === id); @@ -1082,7 +1127,25 @@ export class DebugModel implements IDebugModel { removed = this.functionBreakpoints; this.functionBreakpoints = []; } - this._onDidChangeBreakpoints.fire({ removed: removed }); + this._onDidChangeBreakpoints.fire({ removed }); + } + + addDataBreakpoint(label: string, dataId: string, canPersist: boolean): void { + const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined); + this.dataBreakopints.push(newDataBreakpoint); + this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint] }); + } + + removeDataBreakpoints(id?: string): void { + let removed: DataBreakpoint[]; + if (id) { + removed = this.dataBreakopints.filter(fbp => fbp.getId() === id); + this.dataBreakopints = this.dataBreakopints.filter(fbp => fbp.getId() !== id); + } else { + removed = this.dataBreakopints; + this.dataBreakopints = []; + } + this._onDidChangeBreakpoints.fire({ removed }); } getWatchExpressions(): Expression[] { diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index 39fbff2964f..8854146a484 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -168,7 +168,7 @@ declare module DebugProtocol { category?: string; /** The output to report. */ output: string; - /** If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing 'variablesReference' to the 'variables' request. */ + /** If an attribute 'variablesReference' exists and its value is > 0, the output contains objects which can be retrieved by passing 'variablesReference' to the 'variables' request. The value should be less than or equal to 2147483647 (2^31 - 1). */ variablesReference?: number; /** An optional source location where the output was produced. */ source?: Source; @@ -284,9 +284,9 @@ declare module DebugProtocol { /** Response to 'runInTerminal' request. */ export interface RunInTerminalResponse extends Response { body: { - /** The process ID. */ + /** The process ID. The value should be less than or equal to 2147483647 (2^31 - 1). */ processId?: number; - /** The process ID of the terminal shell. */ + /** The process ID of the terminal shell. The value should be less than or equal to 2147483647 (2^31 - 1). */ shellProcessId?: number; }; } @@ -757,7 +757,7 @@ declare module DebugProtocol { } /** Pause request; value of command field is 'pause'. - The request suspenses the debuggee. + The request suspends the debuggee. The debug adapter first sends the response and then a 'stopped' event (with reason 'pause') after the thread has been paused successfully. */ export interface PauseRequest extends Request { @@ -842,7 +842,7 @@ declare module DebugProtocol { export interface VariablesArguments { /** The Variable reference. */ variablesReference: number; - /** Optional filter to limit the child variables to either named or indexed. If ommited, both types are fetched. */ + /** Optional filter to limit the child variables to either named or indexed. If omitted, both types are fetched. */ filter?: 'indexed' | 'named'; /** The index of the first variable to return; if omitted children start at 0. */ start?: number; @@ -887,14 +887,14 @@ declare module DebugProtocol { value: string; /** The type of the new value. Typically shown in the UI when hovering over the value. */ type?: string; - /** If variablesReference is > 0, the new value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */ + /** If variablesReference is > 0, the new value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ variablesReference?: number; /** The number of named child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). */ namedVariables?: number; /** The number of indexed child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). */ indexedVariables?: number; }; @@ -1041,14 +1041,14 @@ declare module DebugProtocol { type?: string; /** Properties of a evaluate result that can be used to determine how to render the result in the UI. */ presentationHint?: VariablePresentationHint; - /** If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */ + /** If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ variablesReference: number; /** The number of named child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). */ namedVariables?: number; /** The number of indexed child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). */ indexedVariables?: number; /** Memory reference to a location appropriate for this result. For pointer type eval results, this is generally a reference to the memory address contained in the pointer. */ @@ -1086,14 +1086,14 @@ declare module DebugProtocol { type?: string; /** Properties of a value that can be used to determine how to render the result in the UI. */ presentationHint?: VariablePresentationHint; - /** If variablesReference is > 0, the value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */ + /** If variablesReference is > 0, the value is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. The value should be less than or equal to 2147483647 (2^31 - 1). */ variablesReference?: number; /** The number of named child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). */ namedVariables?: number; /** The number of indexed child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. The value should be less than or equal to 2147483647 (2^31 - 1). */ indexedVariables?: number; }; @@ -1294,6 +1294,8 @@ declare module DebugProtocol { supportsStepInTargetsRequest?: boolean; /** The debug adapter supports the 'completions' request. */ supportsCompletionsRequest?: boolean; + /** The set of characters that should trigger completion in a REPL. If not specified, the UI should assume the '.' character. */ + completionTriggerCharacters?: string[]; /** The debug adapter supports the 'modules' request. */ supportsModulesRequest?: boolean; /** The set of additional module information exposed by the debug adapter. */ @@ -1433,7 +1435,7 @@ declare module DebugProtocol { name?: string; /** The path of the source to be shown in the UI. It is only used to locate and load the content of the source if no sourceReference is specified (or its value is 0). */ path?: string; - /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source. */ + /** If sourceReference > 0 the contents of the source must be retrieved through the SourceRequest (even if a path is specified). A sourceReference is only valid for a session, so it must not be used to persist a source. The value should be less than or equal to 2147483647 (2^31 - 1). */ sourceReference?: number; /** An optional hint for how to present the source in the UI. A value of 'deemphasize' can be used to indicate that the source is not available or that it is skipped on stepping. */ presentationHint?: 'normal' | 'emphasize' | 'deemphasize'; @@ -1668,6 +1670,8 @@ declare module DebugProtocol { label: string; /** If text is not falsy then it is inserted instead of the label. */ text?: string; + /** A string that should be used when comparing this item with other items. When `falsy` the label is used. */ + sortText?: string; /** The item's type. Typically the client uses this information to render the item in the UI with an icon. */ type?: CompletionItemType; /** This value determines the location (in the CompletionsRequest's 'text' attribute) where the completion text is added. @@ -1729,7 +1733,7 @@ declare module DebugProtocol { /** This enumeration defines all possible conditions when a thrown exception should result in a break. never: never breaks, always: always breaks, - unhandled: breaks when excpetion unhandled, + unhandled: breaks when exception unhandled, userUnhandled: breaks if the exception is not handled by user code. */ export type ExceptionBreakMode = 'never' | 'always' | 'unhandled' | 'userUnhandled'; @@ -1766,7 +1770,7 @@ declare module DebugProtocol { instructionBytes?: string; /** Text representing the instruction and its operands, in an implementation-defined format. */ instruction: string; - /** Name of the symbol that correponds with the location of this instruction, if any. */ + /** Name of the symbol that corresponds with the location of this instruction, if any. */ symbol?: string; /** Source location that corresponds to this instruction, if any. Should always be set (if available) on the first instruction returned, but can be omitted afterwards if this instruction maps to the same source file as the previous instruction. */ location?: Source; diff --git a/src/vs/workbench/contrib/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts index 133e3f8bec8..fa0376d936e 100644 --- a/src/vs/workbench/contrib/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -13,6 +13,7 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/ import { Schemas } from 'vs/base/common/network'; import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; import { ITextEditor } from 'vs/workbench/common/editor'; +import { withUndefinedAsNull } from 'vs/base/common/types'; export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source"); @@ -104,7 +105,7 @@ export class Source { revealInCenterIfOutsideViewport: true, pinned: pinned || (!preserveFocus && !this.inMemory) } - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(withUndefinedAsNull); } static getEncodedDebugData(modelUri: uri): { name: string, path: string, sessionId?: string, sourceReference?: number } { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts similarity index 100% rename from src/vs/workbench/services/extensions/electron-browser/extensionHostDebugService.ts rename to src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index 360a27a9329..3a8e1feb93a 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -27,9 +27,9 @@ export abstract class StreamDebugAdapter extends AbstractDebugAdapter { private static readonly HEADER_LINESEPARATOR = /\r?\n/; // allow for non-RFC 2822 conforming line separators private static readonly HEADER_FIELDSEPARATOR = /: */; - private outputStream: stream.Writable; - private rawData: Buffer; - private contentLength: number; + private outputStream!: stream.Writable; + private rawData = Buffer.allocUnsafe(0); + private contentLength = -1; constructor() { super(); @@ -145,7 +145,7 @@ export class SocketDebugAdapter extends StreamDebugAdapter { */ export class ExecutableDebugAdapter extends StreamDebugAdapter { - private serverProcess: cp.ChildProcess; + private serverProcess: cp.ChildProcess | undefined; constructor(private adapterExecutable: IDebugAdapterExecutable, private debugType: string, private readonly outputService?: IOutputService) { super(); @@ -266,7 +266,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { // processes. Therefore we use TASKKILL.EXE if (platform.isWindows) { return new Promise((c, e) => { - const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess.pid}`, function (err, stdout, stderr) { + const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess!.pid}`, function (err, stdout, stderr) { if (err) { return e(err); } diff --git a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts index 6b313596ca6..d967dce1868 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts @@ -13,9 +13,10 @@ import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; import { IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; +import { NullOpenerService } from 'vs/platform/opener/common/opener'; function createMockSession(model: DebugModel, name = 'mockSession', parentSession?: DebugSession | undefined): DebugSession { - return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); + return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); } suite('Debug - Model', () => { @@ -23,7 +24,7 @@ suite('Debug - Model', () => { let rawSession: MockRawSession; setup(() => { - model = new DebugModel([], true, [], [], [], { isDirty: (e: any) => false }); + model = new DebugModel([], true, [], [], [], [], { isDirty: (e: any) => false }); rawSession = new MockRawSession(); }); @@ -427,7 +428,7 @@ suite('Debug - Model', () => { // Repl output test('repl output', () => { - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); + const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); const repl = new ReplModel(session); repl.appendToRepl('first line\n', severity.Error); repl.appendToRepl('second line ', severity.Error); diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index bae3cd2b832..bb10110ab92 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -7,7 +7,7 @@ import { URI as uri } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Position } from 'vs/editor/common/core/position'; -import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource } from 'vs/workbench/contrib/debug/common/debug'; +import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { CompletionItem } from 'vs/editor/common/modes'; import Severity from 'vs/base/common/severity'; @@ -79,6 +79,13 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } + addDataBreakpoint(label: string, dataId: string, canPersist: boolean): Promise { + throw new Error('Method not implemented.'); + } + removeDataBreakpoints(id?: string | undefined): Promise { + throw new Error('Method not implemented.'); + } + public addReplExpression(name: string): Promise { throw new Error('not implemented'); } @@ -125,6 +132,13 @@ export class MockDebugService implements IDebugService { } export class MockSession implements IDebugSession { + dataBreakpointInfo(name: string, variablesReference?: number | undefined): Promise<{ dataId: string | null; description: string; canPersist?: boolean | undefined; }> { + throw new Error('Method not implemented.'); + } + + sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise { + throw new Error('Method not implemented.'); + } subId: string | undefined; @@ -155,7 +169,7 @@ export class MockSession implements IDebugSession { configuration: IConfig = { type: 'mock', name: 'mock', request: 'launch' }; unresolvedConfiguration: IConfig = { type: 'mock', name: 'mock', request: 'launch' }; state = State.Stopped; - root: IWorkspaceFolder; + root!: IWorkspaceFolder; capabilities: DebugProtocol.Capabilities = {}; getId(): string { @@ -301,9 +315,9 @@ export class MockSession implements IDebugSession { export class MockRawSession { - capabilities: DebugProtocol.Capabilities; - disconnected: boolean; - sessionLengthInSeconds: number; + capabilities: DebugProtocol.Capabilities = {}; + disconnected = false; + sessionLengthInSeconds: number = 0; public readyForBreakpoints = true; public emittedStopped = true; diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts b/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts similarity index 94% rename from src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts rename to src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts index 97f55700a40..b8ded4b910a 100644 --- a/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts +++ b/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts @@ -11,6 +11,8 @@ import { IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/exten import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Disposable } from 'vs/base/common/lifecycle'; import { language } from 'vs/base/common/platform'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; export class ExperimentalPrompts extends Disposable implements IWorkbenchContribution { @@ -18,7 +20,8 @@ export class ExperimentalPrompts extends Disposable implements IWorkbenchContrib @IExperimentService private readonly experimentService: IExperimentService, @IViewletService private readonly viewletService: IViewletService, @INotificationService private readonly notificationService: INotificationService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IOpenerService private readonly openerService: IOpenerService ) { super(); @@ -65,7 +68,7 @@ export class ExperimentalPrompts extends Disposable implements IWorkbenchContrib run: () => { logTelemetry(commandText); if (command.externalLink) { - window.open(command.externalLink); + this.openerService.open(URI.parse(command.externalLink)); } else if (command.curatedExtensionsKey && Array.isArray(command.curatedExtensionsList)) { this.viewletService.openViewlet('workbench.view.extensions', true) .then(viewlet => viewlet as IExtensionsViewlet) diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts similarity index 79% rename from src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts rename to src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts index ca28e9b6f67..67b6159734f 100644 --- a/src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts +++ b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; -import { ExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; +import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/electron-browser/experimentalPrompt'; +import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt'; registerSingleton(IExperimentService, ExperimentService, true); diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 437afe18bd6..5707377ce4b 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -4,7 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; +import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { language } from 'vs/base/common/platform'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { match } from 'vs/base/common/glob'; +import { IRequestService, asJson } from 'vs/platform/request/common/request'; +import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { distinct } from 'vs/base/common/arrays'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IProductService } from 'vs/platform/product/common/product'; +import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats'; export const enum ExperimentState { Evaluating, @@ -56,4 +72,388 @@ export interface IExperimentService { onExperimentEnabled: Event; } -export const IExperimentService = createDecorator('experimentService'); \ No newline at end of file +export const IExperimentService = createDecorator('experimentService'); + +interface IExperimentStorageState { + enabled: boolean; + state: ExperimentState; + editCount?: number; + lastEditedDate?: string; +} + +interface IRawExperiment { + id: string; + enabled?: boolean; + condition?: { + insidersOnly?: boolean; + newUser?: boolean; + displayLanguage?: string; + installedExtensions?: { + excludes?: string[]; + includes?: string[]; + }, + fileEdits?: { + filePathPattern?: string; + workspaceIncludes?: string[]; + workspaceExcludes?: string[]; + minEditCount: number; + }, + experimentsPreviouslyRun?: { + excludes?: string[]; + includes?: string[]; + } + userProbability?: number; + }; + action?: IExperimentAction; +} + +export class ExperimentService extends Disposable implements IExperimentService { + _serviceBrand: any; + private _experiments: IExperiment[] = []; + private _loadExperimentsPromise: Promise; + private _curatedMapping = Object.create(null); + + private readonly _onExperimentEnabled = this._register(new Emitter()); + onExperimentEnabled: Event = this._onExperimentEnabled.event; + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @ITextFileService private readonly textFileService: ITextFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IRequestService private readonly requestService: IRequestService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, + @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService + ) { + super(); + + this._loadExperimentsPromise = Promise.resolve(this.lifecycleService.when(LifecyclePhase.Eventually)).then(() => this.loadExperiments()); + } + + public getExperimentById(id: string): Promise { + return this._loadExperimentsPromise.then(() => { + return this._experiments.filter(x => x.id === id)[0]; + }); + } + + public getExperimentsByType(type: ExperimentActionType): Promise { + return this._loadExperimentsPromise.then(() => { + if (type === ExperimentActionType.Custom) { + return this._experiments.filter(x => x.enabled && (!x.action || x.action.type === type)); + } + return this._experiments.filter(x => x.enabled && x.action && x.action.type === type); + }); + } + + public getCuratedExtensionsList(curatedExtensionsKey: string): Promise { + return this._loadExperimentsPromise.then(() => { + for (const experiment of this._experiments) { + if (experiment.enabled + && experiment.state === ExperimentState.Run + && this._curatedMapping[experiment.id] + && this._curatedMapping[experiment.id].curatedExtensionsKey === curatedExtensionsKey) { + return this._curatedMapping[experiment.id].curatedExtensionsList; + } + } + return []; + }); + } + + public markAsCompleted(experimentId: string): void { + const storageKey = 'experiments.' + experimentId; + const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); + experimentState.state = ExperimentState.Complete; + this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); + } + + protected getExperiments(): Promise { + if (!this.productService.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) { + return Promise.resolve([]); + } + return this.requestService.request({ type: 'GET', url: this.productService.experimentsUrl }, CancellationToken.None).then(context => { + if (context.res.statusCode !== 200) { + return Promise.resolve(null); + } + return asJson(context).then((result: any) => { + return result && Array.isArray(result['experiments']) ? result['experiments'] : []; + }); + }, () => Promise.resolve(null)); + } + + private loadExperiments(): Promise { + return this.getExperiments().then(rawExperiments => { + // Offline mode + if (!rawExperiments) { + const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []); + if (Array.isArray(allExperimentIdsFromStorage)) { + allExperimentIdsFromStorage.forEach(experimentId => { + const storageKey = 'experiments.' + experimentId; + const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), null); + if (experimentState) { + this._experiments.push({ + id: experimentId, + enabled: experimentState.enabled, + state: experimentState.state + }); + } + }); + } + return Promise.resolve(null); + } + + // Clear disbaled/deleted experiments from storage + const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []); + const enabledExperiments = rawExperiments.filter(experiment => !!experiment.enabled).map(experiment => experiment.id.toLowerCase()); + if (Array.isArray(allExperimentIdsFromStorage)) { + allExperimentIdsFromStorage.forEach(experiment => { + if (enabledExperiments.indexOf(experiment) === -1) { + this.storageService.remove(`experiments.${experiment}`, StorageScope.GLOBAL); + } + }); + } + if (enabledExperiments.length) { + this.storageService.store('allExperiments', JSON.stringify(enabledExperiments), StorageScope.GLOBAL); + } else { + this.storageService.remove('allExperiments', StorageScope.GLOBAL); + } + + const promises = rawExperiments.map(experiment => { + const processedExperiment: IExperiment = { + id: experiment.id, + enabled: !!experiment.enabled, + state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun + }; + + if (experiment.action) { + processedExperiment.action = { + type: ExperimentActionType[experiment.action.type] || ExperimentActionType.Custom, + properties: experiment.action.properties + }; + if (processedExperiment.action.type === ExperimentActionType.Prompt) { + ((processedExperiment.action.properties).commands || []).forEach(x => { + if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) { + this._curatedMapping[experiment.id] = x; + } + }); + } + if (!processedExperiment.action.properties) { + processedExperiment.action.properties = {}; + } + } + this._experiments.push(processedExperiment); + + if (!processedExperiment.enabled) { + return Promise.resolve(null); + } + + const storageKey = 'experiments.' + experiment.id; + const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); + if (!experimentState.hasOwnProperty('enabled')) { + experimentState.enabled = processedExperiment.enabled; + } + if (!experimentState.hasOwnProperty('state')) { + experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun; + } else { + processedExperiment.state = experimentState.state; + } + + return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => { + experimentState.state = processedExperiment.state = state; + this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); + + if (state === ExperimentState.Run) { + this.fireRunExperiment(processedExperiment); + } + return Promise.resolve(null); + }); + + }); + return Promise.all(promises).then(() => { + type ExperimentsClassification = { + experiments: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + }; + this.telemetryService.publicLog2<{ experiments: IExperiment[] }, ExperimentsClassification>('experiments', { experiments: this._experiments }); + }); + }); + } + + private fireRunExperiment(experiment: IExperiment) { + this._onExperimentEnabled.fire(experiment); + const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []); + if (runExperimentIdsFromStorage.indexOf(experiment.id) === -1) { + runExperimentIdsFromStorage.push(experiment.id); + } + + // Ensure we dont store duplicates + const distinctExperiments = distinct(runExperimentIdsFromStorage); + if (runExperimentIdsFromStorage.length !== distinctExperiments.length) { + this.storageService.store('currentOrPreviouslyRunExperiments', JSON.stringify(distinctExperiments), StorageScope.GLOBAL); + } + } + + private checkExperimentDependencies(experiment: IRawExperiment): boolean { + const experimentsPreviouslyRun = experiment.condition ? experiment.condition.experimentsPreviouslyRun : undefined; + if (experimentsPreviouslyRun) { + const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []); + let includeCheck = true; + let excludeCheck = true; + const includes = experimentsPreviouslyRun.includes; + if (Array.isArray(includes)) { + includeCheck = runExperimentIdsFromStorage.some(x => includes.indexOf(x) > -1); + } + const excludes = experimentsPreviouslyRun.excludes; + if (includeCheck && Array.isArray(excludes)) { + excludeCheck = !runExperimentIdsFromStorage.some(x => excludes.indexOf(x) > -1); + } + if (!includeCheck || !excludeCheck) { + return false; + } + } + return true; + } + + private shouldRunExperiment(experiment: IRawExperiment, processedExperiment: IExperiment): Promise { + if (processedExperiment.state !== ExperimentState.Evaluating) { + return Promise.resolve(processedExperiment.state); + } + + if (!experiment.enabled) { + return Promise.resolve(ExperimentState.NoRun); + } + + const condition = experiment.condition; + if (!condition) { + return Promise.resolve(ExperimentState.Run); + } + + if (!this.checkExperimentDependencies(experiment)) { + return Promise.resolve(ExperimentState.NoRun); + } + + if (this.environmentService.appQuality === 'stable' && condition.insidersOnly === true) { + return Promise.resolve(ExperimentState.NoRun); + } + + const isNewUser = !this.storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL); + if ((condition.newUser === true && !isNewUser) + || (condition.newUser === false && isNewUser)) { + return Promise.resolve(ExperimentState.NoRun); + } + + if (typeof condition.displayLanguage === 'string') { + let localeToCheck = condition.displayLanguage.toLowerCase(); + let displayLanguage = language!.toLowerCase(); + + if (localeToCheck !== displayLanguage) { + const a = displayLanguage.indexOf('-'); + const b = localeToCheck.indexOf('-'); + if (a > -1) { + displayLanguage = displayLanguage.substr(0, a); + } + if (b > -1) { + localeToCheck = localeToCheck.substr(0, b); + } + if (displayLanguage !== localeToCheck) { + return Promise.resolve(ExperimentState.NoRun); + } + } + } + + if (!condition.userProbability) { + condition.userProbability = 1; + } + + let extensionsCheckPromise = Promise.resolve(true); + const installedExtensions = condition.installedExtensions; + if (installedExtensions) { + extensionsCheckPromise = this.extensionManagementService.getInstalled(ExtensionType.User).then(locals => { + let includesCheck = true; + let excludesCheck = true; + const localExtensions = locals.map(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}`); + if (Array.isArray(installedExtensions.includes) && installedExtensions.includes.length) { + const extensionIncludes = installedExtensions.includes.map(e => e.toLowerCase()); + includesCheck = localExtensions.some(e => extensionIncludes.indexOf(e) > -1); + } + if (Array.isArray(installedExtensions.excludes) && installedExtensions.excludes.length) { + const extensionExcludes = installedExtensions.excludes.map(e => e.toLowerCase()); + excludesCheck = !localExtensions.some(e => extensionExcludes.indexOf(e) > -1); + } + return includesCheck && excludesCheck; + }); + } + + const storageKey = 'experiments.' + experiment.id; + const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); + + return extensionsCheckPromise.then(success => { + const fileEdits = condition.fileEdits; + if (!success || !fileEdits || typeof fileEdits.minEditCount !== 'number') { + const runExperiment = success && typeof condition.userProbability === 'number' && Math.random() < condition.userProbability; + return runExperiment ? ExperimentState.Run : ExperimentState.NoRun; + } + + experimentState.editCount = experimentState.editCount || 0; + if (experimentState.editCount >= fileEdits.minEditCount) { + return ExperimentState.Run; + } + + const onSaveHandler = this.textFileService.models.onModelsSaved(e => { + const date = new Date().toDateString(); + const latestExperimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); + if (latestExperimentState.state !== ExperimentState.Evaluating) { + onSaveHandler.dispose(); + return; + } + e.forEach(async event => { + if (event.kind !== StateChange.SAVED + || latestExperimentState.state !== ExperimentState.Evaluating + || date === latestExperimentState.lastEditedDate + || (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) + ) { + return; + } + let filePathCheck = true; + let workspaceCheck = true; + + if (typeof fileEdits.filePathPattern === 'string') { + filePathCheck = match(fileEdits.filePathPattern, event.resource.fsPath); + } + if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) { + const tags = await this.workspaceStatsService.getTags(); + workspaceCheck = !!tags && fileEdits.workspaceIncludes.some(x => !!tags[x]); + } + if (workspaceCheck && Array.isArray(fileEdits.workspaceExcludes) && fileEdits.workspaceExcludes.length) { + const tags = await this.workspaceStatsService.getTags(); + workspaceCheck = !!tags && !fileEdits.workspaceExcludes.some(x => !!tags[x]); + } + if (filePathCheck && workspaceCheck) { + latestExperimentState.editCount = (latestExperimentState.editCount || 0) + 1; + latestExperimentState.lastEditedDate = date; + this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL); + } + }); + if (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) { + processedExperiment.state = latestExperimentState.state = (typeof condition.userProbability === 'number' && Math.random() < condition.userProbability && this.checkExperimentDependencies(experiment)) ? ExperimentState.Run : ExperimentState.NoRun; + this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL); + if (latestExperimentState.state === ExperimentState.Run && experiment.action && ExperimentActionType[experiment.action.type] === ExperimentActionType.Prompt) { + this.fireRunExperiment(processedExperiment); + } + } + }); + this._register(onSaveHandler); + return ExperimentState.Evaluating; + }); + } +} + + +function safeParse(text: string | undefined, defaultObject: any) { + try { + return text ? JSON.parse(text) || defaultObject : defaultObject; + } catch (e) { + return defaultObject; + } +} diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts b/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts deleted file mode 100644 index 79dd609cb60..00000000000 --- a/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts +++ /dev/null @@ -1,407 +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 { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; -import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { language } from 'vs/base/common/platform'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { match } from 'vs/base/common/glob'; -import { IRequestService, asJson } from 'vs/platform/request/common/request'; -import { Emitter, Event } from 'vs/base/common/event'; -import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { distinct } from 'vs/base/common/arrays'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { ExperimentState, IExperimentAction, IExperimentService, IExperiment, ExperimentActionType, IExperimentActionPromptProperties } from 'vs/workbench/contrib/experiments/common/experimentService'; -import { IProductService } from 'vs/platform/product/common/product'; -import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; - -interface IExperimentStorageState { - enabled: boolean; - state: ExperimentState; - editCount?: number; - lastEditedDate?: string; -} - -interface IRawExperiment { - id: string; - enabled?: boolean; - condition?: { - insidersOnly?: boolean; - newUser?: boolean; - displayLanguage?: string; - installedExtensions?: { - excludes?: string[]; - includes?: string[]; - }, - fileEdits?: { - filePathPattern?: string; - workspaceIncludes?: string[]; - workspaceExcludes?: string[]; - minEditCount: number; - }, - experimentsPreviouslyRun?: { - excludes?: string[]; - includes?: string[]; - } - userProbability?: number; - }; - action?: IExperimentAction; -} - -export class ExperimentService extends Disposable implements IExperimentService { - _serviceBrand: any; - private _experiments: IExperiment[] = []; - private _loadExperimentsPromise: Promise; - private _curatedMapping = Object.create(null); - - private readonly _onExperimentEnabled = this._register(new Emitter()); - onExperimentEnabled: Event = this._onExperimentEnabled.event; - - constructor( - @IStorageService private readonly storageService: IStorageService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @ITextFileService private readonly textFileService: ITextFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IRequestService private readonly requestService: IRequestService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IProductService private readonly productService: IProductService, - @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService - ) { - super(); - - this._loadExperimentsPromise = Promise.resolve(this.lifecycleService.when(LifecyclePhase.Eventually)).then(() => this.loadExperiments()); - } - - public getExperimentById(id: string): Promise { - return this._loadExperimentsPromise.then(() => { - return this._experiments.filter(x => x.id === id)[0]; - }); - } - - public getExperimentsByType(type: ExperimentActionType): Promise { - return this._loadExperimentsPromise.then(() => { - if (type === ExperimentActionType.Custom) { - return this._experiments.filter(x => x.enabled && (!x.action || x.action.type === type)); - } - return this._experiments.filter(x => x.enabled && x.action && x.action.type === type); - }); - } - - public getCuratedExtensionsList(curatedExtensionsKey: string): Promise { - return this._loadExperimentsPromise.then(() => { - for (const experiment of this._experiments) { - if (experiment.enabled - && experiment.state === ExperimentState.Run - && this._curatedMapping[experiment.id] - && this._curatedMapping[experiment.id].curatedExtensionsKey === curatedExtensionsKey) { - return this._curatedMapping[experiment.id].curatedExtensionsList; - } - } - return []; - }); - } - - public markAsCompleted(experimentId: string): void { - const storageKey = 'experiments.' + experimentId; - const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); - experimentState.state = ExperimentState.Complete; - this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); - } - - protected getExperiments(): Promise { - if (!this.productService.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) { - return Promise.resolve([]); - } - return this.requestService.request({ type: 'GET', url: this.productService.experimentsUrl }, CancellationToken.None).then(context => { - if (context.res.statusCode !== 200) { - return Promise.resolve(null); - } - return asJson(context).then((result: any) => { - return result && Array.isArray(result['experiments']) ? result['experiments'] : []; - }); - }, () => Promise.resolve(null)); - } - - private loadExperiments(): Promise { - return this.getExperiments().then(rawExperiments => { - // Offline mode - if (!rawExperiments) { - const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []); - if (Array.isArray(allExperimentIdsFromStorage)) { - allExperimentIdsFromStorage.forEach(experimentId => { - const storageKey = 'experiments.' + experimentId; - const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), null); - if (experimentState) { - this._experiments.push({ - id: experimentId, - enabled: experimentState.enabled, - state: experimentState.state - }); - } - }); - } - return Promise.resolve(null); - } - - // Clear disbaled/deleted experiments from storage - const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []); - const enabledExperiments = rawExperiments.filter(experiment => !!experiment.enabled).map(experiment => experiment.id.toLowerCase()); - if (Array.isArray(allExperimentIdsFromStorage)) { - allExperimentIdsFromStorage.forEach(experiment => { - if (enabledExperiments.indexOf(experiment) === -1) { - this.storageService.remove(`experiments.${experiment}`, StorageScope.GLOBAL); - } - }); - } - if (enabledExperiments.length) { - this.storageService.store('allExperiments', JSON.stringify(enabledExperiments), StorageScope.GLOBAL); - } else { - this.storageService.remove('allExperiments', StorageScope.GLOBAL); - } - - const promises = rawExperiments.map(experiment => { - const processedExperiment: IExperiment = { - id: experiment.id, - enabled: !!experiment.enabled, - state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun - }; - - if (experiment.action) { - processedExperiment.action = { - type: ExperimentActionType[experiment.action.type] || ExperimentActionType.Custom, - properties: experiment.action.properties - }; - if (processedExperiment.action.type === ExperimentActionType.Prompt) { - ((processedExperiment.action.properties).commands || []).forEach(x => { - if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) { - this._curatedMapping[experiment.id] = x; - } - }); - } - if (!processedExperiment.action.properties) { - processedExperiment.action.properties = {}; - } - } - this._experiments.push(processedExperiment); - - if (!processedExperiment.enabled) { - return Promise.resolve(null); - } - - const storageKey = 'experiments.' + experiment.id; - const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); - if (!experimentState.hasOwnProperty('enabled')) { - experimentState.enabled = processedExperiment.enabled; - } - if (!experimentState.hasOwnProperty('state')) { - experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun; - } else { - processedExperiment.state = experimentState.state; - } - - return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => { - experimentState.state = processedExperiment.state = state; - this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL); - - if (state === ExperimentState.Run) { - this.fireRunExperiment(processedExperiment); - } - return Promise.resolve(null); - }); - - }); - return Promise.all(promises).then(() => { - type ExperimentsClassification = { - experiments: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - }; - this.telemetryService.publicLog2<{ experiments: IExperiment[] }, ExperimentsClassification>('experiments', { experiments: this._experiments }); - }); - }); - } - - private fireRunExperiment(experiment: IExperiment) { - this._onExperimentEnabled.fire(experiment); - const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []); - if (runExperimentIdsFromStorage.indexOf(experiment.id) === -1) { - runExperimentIdsFromStorage.push(experiment.id); - } - - // Ensure we dont store duplicates - const distinctExperiments = distinct(runExperimentIdsFromStorage); - if (runExperimentIdsFromStorage.length !== distinctExperiments.length) { - this.storageService.store('currentOrPreviouslyRunExperiments', JSON.stringify(distinctExperiments), StorageScope.GLOBAL); - } - } - - private checkExperimentDependencies(experiment: IRawExperiment): boolean { - const experimentsPreviouslyRun = experiment.condition ? experiment.condition.experimentsPreviouslyRun : undefined; - if (experimentsPreviouslyRun) { - const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []); - let includeCheck = true; - let excludeCheck = true; - const includes = experimentsPreviouslyRun.includes; - if (Array.isArray(includes)) { - includeCheck = runExperimentIdsFromStorage.some(x => includes.indexOf(x) > -1); - } - const excludes = experimentsPreviouslyRun.excludes; - if (includeCheck && Array.isArray(excludes)) { - excludeCheck = !runExperimentIdsFromStorage.some(x => excludes.indexOf(x) > -1); - } - if (!includeCheck || !excludeCheck) { - return false; - } - } - return true; - } - - private shouldRunExperiment(experiment: IRawExperiment, processedExperiment: IExperiment): Promise { - if (processedExperiment.state !== ExperimentState.Evaluating) { - return Promise.resolve(processedExperiment.state); - } - - if (!experiment.enabled) { - return Promise.resolve(ExperimentState.NoRun); - } - - const condition = experiment.condition; - if (!condition) { - return Promise.resolve(ExperimentState.Run); - } - - if (!this.checkExperimentDependencies(experiment)) { - return Promise.resolve(ExperimentState.NoRun); - } - - if (this.environmentService.appQuality === 'stable' && condition.insidersOnly === true) { - return Promise.resolve(ExperimentState.NoRun); - } - - const isNewUser = !this.storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL); - if ((condition.newUser === true && !isNewUser) - || (condition.newUser === false && isNewUser)) { - return Promise.resolve(ExperimentState.NoRun); - } - - if (typeof condition.displayLanguage === 'string') { - let localeToCheck = condition.displayLanguage.toLowerCase(); - let displayLanguage = language!.toLowerCase(); - - if (localeToCheck !== displayLanguage) { - const a = displayLanguage.indexOf('-'); - const b = localeToCheck.indexOf('-'); - if (a > -1) { - displayLanguage = displayLanguage.substr(0, a); - } - if (b > -1) { - localeToCheck = localeToCheck.substr(0, b); - } - if (displayLanguage !== localeToCheck) { - return Promise.resolve(ExperimentState.NoRun); - } - } - } - - if (!condition.userProbability) { - condition.userProbability = 1; - } - - let extensionsCheckPromise = Promise.resolve(true); - const installedExtensions = condition.installedExtensions; - if (installedExtensions) { - extensionsCheckPromise = this.extensionManagementService.getInstalled(ExtensionType.User).then(locals => { - let includesCheck = true; - let excludesCheck = true; - const localExtensions = locals.map(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}`); - if (Array.isArray(installedExtensions.includes) && installedExtensions.includes.length) { - const extensionIncludes = installedExtensions.includes.map(e => e.toLowerCase()); - includesCheck = localExtensions.some(e => extensionIncludes.indexOf(e) > -1); - } - if (Array.isArray(installedExtensions.excludes) && installedExtensions.excludes.length) { - const extensionExcludes = installedExtensions.excludes.map(e => e.toLowerCase()); - excludesCheck = !localExtensions.some(e => extensionExcludes.indexOf(e) > -1); - } - return includesCheck && excludesCheck; - }); - } - - const storageKey = 'experiments.' + experiment.id; - const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); - - return extensionsCheckPromise.then(success => { - const fileEdits = condition.fileEdits; - if (!success || !fileEdits || typeof fileEdits.minEditCount !== 'number') { - const runExperiment = success && typeof condition.userProbability === 'number' && Math.random() < condition.userProbability; - return runExperiment ? ExperimentState.Run : ExperimentState.NoRun; - } - - experimentState.editCount = experimentState.editCount || 0; - if (experimentState.editCount >= fileEdits.minEditCount) { - return ExperimentState.Run; - } - - const onSaveHandler = this.textFileService.models.onModelsSaved(e => { - const date = new Date().toDateString(); - const latestExperimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {}); - if (latestExperimentState.state !== ExperimentState.Evaluating) { - onSaveHandler.dispose(); - return; - } - e.forEach(async event => { - if (event.kind !== StateChange.SAVED - || latestExperimentState.state !== ExperimentState.Evaluating - || date === latestExperimentState.lastEditedDate - || (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) - ) { - return; - } - let filePathCheck = true; - let workspaceCheck = true; - - if (typeof fileEdits.filePathPattern === 'string') { - filePathCheck = match(fileEdits.filePathPattern, event.resource.fsPath); - } - if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) { - const tags = await this.workspaceStatsService.getTags(); - workspaceCheck = !!tags && fileEdits.workspaceIncludes.some(x => !!tags[x]); - } - if (workspaceCheck && Array.isArray(fileEdits.workspaceExcludes) && fileEdits.workspaceExcludes.length) { - const tags = await this.workspaceStatsService.getTags(); - workspaceCheck = !!tags && !fileEdits.workspaceExcludes.some(x => !!tags[x]); - } - if (filePathCheck && workspaceCheck) { - latestExperimentState.editCount = (latestExperimentState.editCount || 0) + 1; - latestExperimentState.lastEditedDate = date; - this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL); - } - }); - if (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) { - processedExperiment.state = latestExperimentState.state = (typeof condition.userProbability === 'number' && Math.random() < condition.userProbability && this.checkExperimentDependencies(experiment)) ? ExperimentState.Run : ExperimentState.NoRun; - this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL); - if (latestExperimentState.state === ExperimentState.Run && experiment.action && ExperimentActionType[experiment.action.type] === ExperimentActionType.Prompt) { - this.fireRunExperiment(processedExperiment); - } - } - }); - this._register(onSaveHandler); - return ExperimentState.Evaluating; - }); - } -} - - -function safeParse(text: string | undefined, defaultObject: any) { - try { - return text ? JSON.parse(text) || defaultObject : defaultObject; - } catch (e) { - return defaultObject; - } -} diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index f44b4d3904e..70851b908c9 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ExperimentActionType, ExperimentState, IExperiment } from 'vs/workbench/contrib/experiments/common/experimentService'; -import { ExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; +import { ExperimentActionType, ExperimentState, IExperiment, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts index 781fc5ab7c0..ff693a1f4e3 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -12,7 +12,7 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/electron-browser/experimentalPrompt'; +import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt'; import { ExperimentActionType, ExperimentState, IExperiment, IExperimentActionPromptProperties, IExperimentService, LocalizedPromptText } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index d29d891120b..272fa9d0591 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -50,25 +50,11 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry'; import { isUndefined } from 'vs/base/common/types'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { URI } from 'vs/base/common/uri'; -import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/common/webview'; +import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; - -function renderBody(body: string): string { - const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-resource://'); - return ` - - - - - - - - - ${body} - - `; -} +import { generateUuid } from 'vs/base/common/uuid'; +import { platform } from 'vs/base/common/process'; +import { URI } from 'vs/base/common/uri'; function removeEmbeddedSVGs(documentContent: string): string { const newDocument = new DOMParser().parseFromString(documentContent, 'text/html'); @@ -149,29 +135,33 @@ interface IActiveElement { focus(): void; } +interface IExtensionEditorTemplate { + iconContainer: HTMLElement; + icon: HTMLImageElement; + name: HTMLElement; + identifier: HTMLElement; + preview: HTMLElement; + builtin: HTMLElement; + license: HTMLElement; + publisher: HTMLElement; + installCount: HTMLElement; + rating: HTMLElement; + repository: HTMLElement; + description: HTMLElement; + extensionActionBar: ActionBar; + navbar: NavBar; + content: HTMLElement; + subtextContainer: HTMLElement; + subtext: HTMLElement; + ignoreActionbar: ActionBar; + header: HTMLElement; +} + export class ExtensionEditor extends BaseEditor { static readonly ID: string = 'workbench.editor.extension'; - private iconContainer: HTMLElement; - private icon: HTMLImageElement; - private name: HTMLElement; - private identifier: HTMLElement; - private preview: HTMLElement; - private builtin: HTMLElement; - private license: HTMLElement; - private publisher: HTMLElement; - private installCount: HTMLElement; - private rating: HTMLElement; - private repository: HTMLElement; - private description: HTMLElement; - private extensionActionBar: ActionBar; - private navbar: NavBar; - private content: HTMLElement; - private subtextContainer: HTMLElement; - private subtext: HTMLElement; - private ignoreActionbar: ActionBar; - private header: HTMLElement; + private template: IExtensionEditorTemplate | undefined; private extensionReadme: Cache | null; private extensionChangelog: Cache | null; @@ -180,7 +170,7 @@ export class ExtensionEditor extends BaseEditor { private layoutParticipants: ILayoutParticipant[] = []; private readonly contentDisposables = this._register(new DisposableStore()); private readonly transientDisposables = this._register(new DisposableStore()); - private activeElement: IActiveElement | null; + private activeElement: IActiveElement | null = null; private editorLoadComplete: boolean = false; constructor( @@ -208,43 +198,43 @@ export class ExtensionEditor extends BaseEditor { const root = append(parent, $('.extension-editor')); root.tabIndex = 0; // this is required for the focus tracker on the editor root.style.outline = 'none'; - this.header = append(root, $('.header')); + const header = append(root, $('.header')); - this.iconContainer = append(this.header, $('.icon-container')); - this.icon = append(this.iconContainer, $('img.icon', { draggable: false })); + const iconContainer = append(header, $('.icon-container')); + const icon = append(iconContainer, $('img.icon', { draggable: false })); - const details = append(this.header, $('.details')); + const details = append(header, $('.details')); const title = append(details, $('.title')); - this.name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") })); - this.identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") })); + const name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") })); + const identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") })); - this.preview = append(title, $('span.preview', { title: localize('preview', "Preview") })); - this.preview.textContent = localize('preview', "Preview"); + const preview = append(title, $('span.preview', { title: localize('preview', "Preview") })); + preview.textContent = localize('preview', "Preview"); - this.builtin = append(title, $('span.builtin')); - this.builtin.textContent = localize('builtin', "Built-in"); + const builtin = append(title, $('span.builtin')); + builtin.textContent = localize('builtin', "Built-in"); const subtitle = append(details, $('.subtitle')); - this.publisher = append(subtitle, $('span.publisher.clickable', { title: localize('publisher', "Publisher name"), tabIndex: 0 })); + const publisher = append(subtitle, $('span.publisher.clickable', { title: localize('publisher', "Publisher name"), tabIndex: 0 })); - this.installCount = append(subtitle, $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 })); + const installCount = append(subtitle, $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 })); - this.rating = append(subtitle, $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 })); + const rating = append(subtitle, $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 })); - this.repository = append(subtitle, $('span.repository.clickable')); - this.repository.textContent = localize('repository', 'Repository'); - this.repository.style.display = 'none'; - this.repository.tabIndex = 0; + const repository = append(subtitle, $('span.repository.clickable')); + repository.textContent = localize('repository', 'Repository'); + repository.style.display = 'none'; + repository.tabIndex = 0; - this.license = append(subtitle, $('span.license.clickable')); - this.license.textContent = localize('license', 'License'); - this.license.style.display = 'none'; - this.license.tabIndex = 0; + const license = append(subtitle, $('span.license.clickable')); + license.textContent = localize('license', 'License'); + license.style.display = 'none'; + license.tabIndex = 0; - this.description = append(details, $('.description')); + const description = append(details, $('.description')); const extensionActions = append(details, $('.actions')); - this.extensionActionBar = new ActionBar(extensionActions, { + const extensionActionBar = this._register(new ActionBar(extensionActions, { animated: false, actionViewItemProvider: (action: Action) => { if (action instanceof ExtensionEditorDropDownAction) { @@ -252,29 +242,48 @@ export class ExtensionEditor extends BaseEditor { } return undefined; } - }); + })); - this.subtextContainer = append(details, $('.subtext-container')); - this.subtext = append(this.subtextContainer, $('.subtext')); - this.ignoreActionbar = new ActionBar(this.subtextContainer, { animated: false }); + const subtextContainer = append(details, $('.subtext-container')); + const subtext = append(subtextContainer, $('.subtext')); + const ignoreActionbar = this._register(new ActionBar(subtextContainer, { animated: false })); - this._register(this.extensionActionBar); - this._register(this.ignoreActionbar); - - this._register(Event.chain(this.extensionActionBar.onDidRun) + this._register(Event.chain(extensionActionBar.onDidRun) .map(({ error }) => error) .filter(error => !!error) .on(this.onError, this)); - this._register(Event.chain(this.ignoreActionbar.onDidRun) + this._register(Event.chain(ignoreActionbar.onDidRun) .map(({ error }) => error) .filter(error => !!error) .on(this.onError, this)); const body = append(root, $('.body')); - this.navbar = new NavBar(body); + const navbar = new NavBar(body); - this.content = append(body, $('.content')); + const content = append(body, $('.content')); + + this.template = { + builtin, + content, + description, + extensionActionBar, + header, + icon, + iconContainer, + identifier, + ignoreActionbar, + installCount, + license, + name, + navbar, + preview, + publisher, + rating, + repository, + subtext, + subtextContainer + }; } private onClick(element: HTMLElement, callback: () => void): IDisposable { @@ -292,6 +301,13 @@ export class ExtensionEditor extends BaseEditor { } async setInput(input: ExtensionsInput, options: EditorOptions, token: CancellationToken): Promise { + if (this.template) { + await this.updateTemplate(input, this.template); + } + return super.setInput(input, options, token); + } + + private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate): Promise { const runningExtensions = await this.extensionService.getExtensions(); const colorThemes = await this.workbenchThemeService.getColorThemes(); const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); @@ -306,18 +322,18 @@ export class ExtensionEditor extends BaseEditor { this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(token))); this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token))); - const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer, true); - const onError = Event.once(domEvent(this.icon, 'error')); - onError(() => this.icon.src = extension.iconUrlFallback, null, this.transientDisposables); - this.icon.src = extension.iconUrl; + const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, template.iconContainer, true); + const onError = Event.once(domEvent(template.icon, 'error')); + onError(() => template.icon.src = extension.iconUrlFallback, null, this.transientDisposables); + template.icon.src = extension.iconUrl; - 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'; + template.name.textContent = extension.displayName; + template.identifier.textContent = extension.identifier.id; + template.preview.style.display = extension.preview ? 'inherit' : 'none'; + template.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none'; - this.publisher.textContent = extension.publisherDisplayName; - this.description.textContent = extension.description; + template.publisher.textContent = extension.publisherDisplayName; + template.description.textContent = extension.description; const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); let recommendationsData = {}; @@ -335,40 +351,40 @@ export class ExtensionEditor extends BaseEditor { */ this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData)); - toggleClass(this.name, 'clickable', !!extension.url); - toggleClass(this.publisher, 'clickable', !!extension.url); - toggleClass(this.rating, 'clickable', !!extension.url); + toggleClass(template.name, 'clickable', !!extension.url); + toggleClass(template.publisher, 'clickable', !!extension.url); + toggleClass(template.rating, 'clickable', !!extension.url); if (extension.url) { - this.transientDisposables.add(this.onClick(this.name, () => window.open(extension.url))); - this.transientDisposables.add(this.onClick(this.rating, () => window.open(`${extension.url}#review-details`))); - this.transientDisposables.add(this.onClick(this.publisher, () => { + this.transientDisposables.add(this.onClick(template.name, () => this.openerService.open(URI.parse(extension.url!)))); + this.transientDisposables.add(this.onClick(template.rating, () => this.openerService.open(URI.parse(`${extension.url}#review-details`)))); + this.transientDisposables.add(this.onClick(template.publisher, () => { this.viewletService.openViewlet(VIEWLET_ID, true) .then(viewlet => viewlet as IExtensionsViewlet) .then(viewlet => viewlet.search(`publisher:"${extension.publisherDisplayName}"`)); })); if (extension.licenseUrl) { - this.transientDisposables.add(this.onClick(this.license, () => window.open(extension.licenseUrl))); - this.license.style.display = 'initial'; + this.transientDisposables.add(this.onClick(template.license, () => this.openerService.open(URI.parse(extension.licenseUrl!)))); + template.license.style.display = 'initial'; } else { - this.license.style.display = 'none'; + template.license.style.display = 'none'; } } else { - this.license.style.display = 'none'; + template.license.style.display = 'none'; } if (extension.repository) { - this.transientDisposables.add(this.onClick(this.repository, () => window.open(extension.repository))); - this.repository.style.display = 'initial'; + this.transientDisposables.add(this.onClick(template.repository, () => this.openerService.open(URI.parse(extension.repository!)))); + template.repository.style.display = 'initial'; } else { - this.repository.style.display = 'none'; + template.repository.style.display = 'none'; } const widgets = [ remoteBadge, - this.instantiationService.createInstance(InstallCountWidget, this.installCount, false), - this.instantiationService.createInstance(RatingsWidget, this.rating, false) + this.instantiationService.createInstance(InstallCountWidget, template.installCount, false), + this.instantiationService.createInstance(RatingsWidget, template.rating, false) ]; const reloadAction = this.instantiationService.createInstance(ReloadAction); const combinedInstallAction = this.instantiationService.createInstance(CombinedInstallAction); @@ -391,20 +407,20 @@ export class ExtensionEditor extends BaseEditor { const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]); extensionContainers.extension = extension; - this.extensionActionBar.clear(); - this.extensionActionBar.push(actions, { icon: true, label: true }); + template.extensionActionBar.clear(); + template.extensionActionBar.push(actions, { icon: true, label: true }); for (const disposable of [...actions, ...widgets, extensionContainers]) { this.transientDisposables.add(disposable); } - this.setSubText(extension, reloadAction); - this.content.innerHTML = ''; // Clear content before setting navbar actions. + this.setSubText(extension, reloadAction, template); + template.content.innerHTML = ''; // Clear content before setting navbar actions. - this.navbar.clear(); - this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables); + template.navbar.clear(); + template.navbar.onChange(e => this.onNavbarChange(extension, e, template), 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")); + template.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); } this.extensionManifest.get() .promise @@ -413,25 +429,23 @@ export class ExtensionEditor extends BaseEditor { combinedInstallAction.manifest = manifest; } if (extension.extensionPack.length) { - this.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together")); + template.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")); + template.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")); + template.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")); + template.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 { - hide(this.subtextContainer); + private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void { + hide(template.subtextContainer); const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction); const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction); @@ -440,23 +454,23 @@ export class ExtensionEditor extends BaseEditor { ignoreAction.enabled = false; undoIgnoreAction.enabled = false; - this.ignoreActionbar.clear(); - this.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true }); + template.ignoreActionbar.clear(); + template.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true }); this.transientDisposables.add(ignoreAction); this.transientDisposables.add(undoIgnoreAction); const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.identifier.id.toLowerCase()]) { ignoreAction.enabled = true; - this.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; - show(this.subtextContainer); + template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; + show(template.subtextContainer); } else if (this.extensionTipsService.getAllIgnoredRecommendations().global.indexOf(extension.identifier.id.toLowerCase()) !== -1) { undoIgnoreAction.enabled = true; - this.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); - show(this.subtextContainer); + template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); + show(template.subtextContainer); } else { - this.subtext.textContent = ''; + template.subtext.textContent = ''; } this.extensionTipsService.onRecommendationChange(change => { @@ -466,28 +480,28 @@ export class ExtensionEditor extends BaseEditor { const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); if (extRecommendations[extension.identifier.id.toLowerCase()]) { ignoreAction.enabled = true; - this.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; + template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; } } else { undoIgnoreAction.enabled = true; ignoreAction.enabled = false; - this.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); + template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."); } } }); this.transientDisposables.add(reloadAction.onDidChange(e => { if (e.tooltip) { - this.subtext.textContent = reloadAction.tooltip; - show(this.subtextContainer); + template.subtext.textContent = reloadAction.tooltip; + show(template.subtextContainer); ignoreAction.enabled = false; undoIgnoreAction.enabled = false; } if (e.enabled === true) { - show(this.subtextContainer); + show(template.subtextContainer); } if (e.enabled === false) { - hide(this.subtextContainer); + hide(template.subtextContainer); } })); } @@ -511,7 +525,7 @@ export class ExtensionEditor extends BaseEditor { } } - private onNavbarChange(extension: IExtension, { id, focus }: { id: string, focus: boolean }): void { + private onNavbarChange(extension: IExtension, { id, focus }: { id: string | null, focus: boolean }, template: IExtensionEditorTemplate): void { if (this.editorLoadComplete) { /* __GDPR__ "extensionEditor:navbarChange" : { @@ -525,45 +539,42 @@ export class ExtensionEditor extends BaseEditor { } this.contentDisposables.clear(); - this.content.innerHTML = ''; + template.content.innerHTML = ''; this.activeElement = null; - this.open(id, extension) - .then(activeElement => { - this.activeElement = activeElement; - if (focus) { - this.focus(); - } - }); + if (id) { + this.open(id, extension, template) + .then(activeElement => { + this.activeElement = activeElement; + if (focus) { + this.focus(); + } + }); + } } - private open(id: string, extension: IExtension): Promise { + private open(id: string, extension: IExtension, template: IExtensionEditorTemplate): Promise { switch (id) { - case NavbarSection.Readme: return this.openReadme(); - case NavbarSection.Contributions: return this.openContributions(); - case NavbarSection.Changelog: return this.openChangelog(); - case NavbarSection.Dependencies: return this.openDependencies(extension); - case NavbarSection.ExtensionPack: return this.openExtensionPack(extension); + case NavbarSection.Readme: return this.openReadme(template); + case NavbarSection.Contributions: return this.openContributions(template); + case NavbarSection.Changelog: return this.openChangelog(template); + case NavbarSection.Dependencies: return this.openDependencies(extension, template); + case NavbarSection.ExtensionPack: return this.openExtensionPack(extension, template); } return Promise.resolve(null); } - private openMarkdown(cacheResult: CacheResult, noContentCopy: string): Promise { - return this.loadContents(() => cacheResult) + private openMarkdown(cacheResult: CacheResult, noContentCopy: string, template: IExtensionEditorTemplate): Promise { + return this.loadContents(() => cacheResult, template) .then(marked.parse) - .then(renderBody) + .then(content => this.renderBody(content)) .then(removeEmbeddedSVGs) .then(body => { const webviewElement = this.webviewService.createWebview('extensionEditor', { enableFindWidget: true, }, - { - svgWhiteList: this.extensionsWorkbenchService.allowedBadgeProviders, - localResourceRoots: [ - URI.parse(require.toUrl('./media')) - ] - }); - webviewElement.mountTo(this.content); + {}); + webviewElement.mountTo(template.content); this.contentDisposables.add(webviewElement.onDidFocus(() => this.fireOnDidFocus())); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, webviewElement); this.contentDisposables.add(toDisposable(removeLayoutParticipant)); @@ -582,23 +593,211 @@ export class ExtensionEditor extends BaseEditor { return webviewElement; }) .then(undefined, () => { - const p = append(this.content, $('p.nocontent')); + const p = append(template.content, $('p.nocontent')); p.textContent = noContentCopy; return p; }); } - private openReadme(): Promise { - return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available.")); + private async renderBody(body: string): Promise { + const nonce = generateUuid(); + return ` + + + + + + + + + ${body} + + `; } - private openChangelog(): Promise { - return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available.")); + private openReadme(template: IExtensionEditorTemplate): Promise { + return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), template); } - private openContributions(): Promise { + private openChangelog(template: IExtensionEditorTemplate): Promise { + return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template); + } + + private openContributions(template: IExtensionEditorTemplate): Promise { const content = $('div', { class: 'subcontent', tabindex: '0' }); - return this.loadContents(() => this.extensionManifest!.get()) + return this.loadContents(() => this.extensionManifest!.get(), template) .then(manifest => { if (!manifest) { return content; @@ -629,28 +828,28 @@ export class ExtensionEditor extends BaseEditor { const isEmpty = !renders.some(x => x); if (isEmpty) { append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions"); - append(this.content, content); + append(template.content, content); } else { - append(this.content, scrollableContent.getDomNode()); + append(template.content, scrollableContent.getDomNode()); this.contentDisposables.add(scrollableContent); } return content; }, () => { append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions"); - append(this.content, content); + append(template.content, content); return content; }); } - private openDependencies(extension: IExtension): Promise { + private openDependencies(extension: IExtension, template: IExtensionEditorTemplate): Promise { if (arrays.isFalsyOrEmpty(extension.dependencies)) { - append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies"); - return Promise.resolve(this.content); + append(template.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies"); + return Promise.resolve(template.content); } const content = $('div', { class: 'subcontent' }); const scrollableContent = new DomScrollableElement(content, {}); - append(this.content, scrollableContent.getDomNode()); + append(template.content, scrollableContent.getDomNode()); this.contentDisposables.add(scrollableContent); const dependenciesTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.dependencies || [], this.extensionsWorkbenchService), content); @@ -667,10 +866,10 @@ export class ExtensionEditor extends BaseEditor { return Promise.resolve({ focus() { dependenciesTree.domFocus(); } }); } - private openExtensionPack(extension: IExtension): Promise { + private openExtensionPack(extension: IExtension, template: IExtensionEditorTemplate): Promise { const content = $('div', { class: 'subcontent' }); const scrollableContent = new DomScrollableElement(content, {}); - append(this.content, scrollableContent.getDomNode()); + append(template.content, scrollableContent.getDomNode()); this.contentDisposables.add(scrollableContent); const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content); @@ -1076,7 +1275,7 @@ export class ExtensionEditor extends BaseEditor { private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding | null { let key: string | undefined; - switch (process.platform) { + switch (platform) { case 'win32': key = rawKeyBinding.win; break; case 'linux': key = rawKeyBinding.linux; break; case 'darwin': key = rawKeyBinding.mac; break; @@ -1090,11 +1289,11 @@ export class ExtensionEditor extends BaseEditor { return null; } - private loadContents(loadingTask: () => CacheResult): Promise { - addClass(this.content, 'loading'); + private loadContents(loadingTask: () => CacheResult, template: IExtensionEditorTemplate): Promise { + addClass(template.content, 'loading'); const result = loadingTask(); - const onDone = () => removeClass(this.content, 'loading'); + const onDone = () => removeClass(template.content, 'loading'); result.promise.then(onDone, onDone); this.contentDisposables.add(toDisposable(() => result.dispose())); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts similarity index 93% rename from src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts rename to src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts index b424211b797..3e7884ae1f7 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts @@ -14,7 +14,6 @@ import { IExtensionTipsService, ExtensionRecommendationReason, IExtensionsConfig import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModel } from 'vs/editor/common/model'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import product from 'vs/platform/product/node/product'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction, InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import Severity from 'vs/base/common/severity'; @@ -23,13 +22,9 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet, IExtensionsWorkbenchService, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import * as pfs from 'vs/base/node/pfs'; -import * as os from 'os'; import { flatten, distinct, shuffle, coalesce } from 'vs/base/common/arrays'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { guessMimeTypes, MIME_UNKNOWN } from 'vs/base/common/mime'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { getHashedRemotesFromUri } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; import { IRequestService, asJson } from 'vs/platform/request/common/request'; import { isNumber } from 'vs/base/common/types'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -42,9 +37,12 @@ import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/wo import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { extname } from 'vs/base/common/resources'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IExeBasedExtensionTip } from 'vs/platform/product/common/product'; +import { IExeBasedExtensionTip, IProductService } from 'vs/platform/product/common/product'; import { timeout } from 'vs/base/common/async'; +import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { setImmediate, isWeb } from 'vs/base/common/platform'; +import { platform, env as processEnv } from 'vs/base/common/process'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); @@ -101,7 +99,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IExtensionService private readonly extensionService: IExtensionService, @IRequestService private readonly requestService: IRequestService, @IViewletService private readonly viewletService: IViewletService, @@ -109,7 +107,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, @IExperimentService private readonly experimentService: IExperimentService, - @ITextFileService private readonly textFileService: ITextFileService + @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService, + @IProductService private readonly productService: IProductService ) { super(); @@ -117,8 +116,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return; } - if (product.extensionsGallery && product.extensionsGallery.recommendationsUrl) { - this._extensionsRecommendationsUrl = product.extensionsGallery.recommendationsUrl; + if (this.productService.extensionsGallery && this.productService.extensionsGallery.recommendationsUrl) { + this._extensionsRecommendationsUrl = this.productService.extensionsGallery.recommendationsUrl; } this.sessionSeed = +new Date(); @@ -244,7 +243,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } getKeymapRecommendations(): IExtensionRecommendation[] { - return (product.keymapExtensionTips || []) + return (this.productService.keymapExtensionTips || []) .filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId)) .map(extensionId => ({ extensionId, sources: ['application'] })); } @@ -521,8 +520,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return false; } - const id = recommendationsToSuggest[0]; - const tip = this._importantExeBasedRecommendations[id]; + const extensionId = recommendationsToSuggest[0]; + const tip = this._importantExeBasedRecommendations[extensionId]; const message = localize('exeRecommended', "The '{0}' extension is recommended as you have {1} installed on your system.", tip.friendlyName!, tip.exeFriendlyName || basename(tip.windowsPath!)); this.notificationService.prompt(Severity.Info, message, @@ -535,8 +534,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } */ - this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'install', extensionId: name }); - this.instantiationService.createInstance(InstallRecommendedExtensionAction, id).run(); + this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'install', extensionId }); + this.instantiationService.createInstance(InstallRecommendedExtensionAction, extensionId).run(); } }, { label: localize('showRecommendations', "Show Recommendations"), @@ -547,7 +546,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } */ - this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'show', extensionId: name }); + this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'show', extensionId }); const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); recommendationsAction.run(); @@ -557,14 +556,14 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe label: choiceNever, isSecondary: true, run: () => { - this.addToImportantRecommendationsIgnore(id); + this.addToImportantRecommendationsIgnore(extensionId); /* __GDPR__ "exeExtensionRecommendations:popup" : { "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } */ - this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: name }); + this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId }); this.notificationService.prompt( Severity.Info, localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), @@ -587,7 +586,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } */ - this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'cancelled', extensionId: name }); + this.telemetryService.publicLog('exeExtensionRecommendations:popup', { userReaction: 'cancelled', extensionId }); } } ); @@ -601,10 +600,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return Object.keys(this._fileBasedRecommendations) .sort((a, b) => { if (this._fileBasedRecommendations[a].recommendedTime === this._fileBasedRecommendations[b].recommendedTime) { - if (!product.extensionImportantTips || caseInsensitiveGet(product.extensionImportantTips, a)) { + if (!this.productService.extensionImportantTips || caseInsensitiveGet(this.productService.extensionImportantTips, a)) { return -1; } - if (caseInsensitiveGet(product.extensionImportantTips, b)) { + if (caseInsensitiveGet(this.productService.extensionImportantTips, b)) { return 1; } } @@ -615,11 +614,11 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } /** - * Parse all file based recommendations from product.extensionTips - * Retire existing recommendations if they are older than a week or are not part of product.extensionTips anymore + * Parse all file based recommendations from this.productService.extensionTips + * Retire existing recommendations if they are older than a week or are not part of this.productService.extensionTips anymore */ private fetchFileBasedRecommendations() { - const extensionTips = product.extensionTips; + const extensionTips = this.productService.extensionTips; if (!extensionTips) { return; } @@ -636,7 +635,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } }); - forEach(product.extensionImportantTips, entry => { + forEach(this.productService.extensionImportantTips, entry => { let { key: id, value } = entry; const { pattern } = value; let ids = this._availableRecommendations[pattern]; @@ -698,7 +697,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe let { key: pattern, value: ids } = entry; if (match(pattern, model.uri.toString())) { for (let id of ids) { - if (caseInsensitiveGet(product.extensionImportantTips, id)) { + if (caseInsensitiveGet(this.productService.extensionImportantTips, id)) { recommendationsToSuggest.push(id); } const filedBasedRecommendation = this._fileBasedRecommendations[id.toLowerCase()] || { recommendedTime: now, sources: [] }; @@ -752,7 +751,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } const id = recommendationsToSuggest[0]; - const entry = caseInsensitiveGet(product.extensionImportantTips, id); + const entry = caseInsensitiveGet(this.productService.extensionImportantTips, id); if (!entry) { return false; } @@ -982,14 +981,16 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } /** - * If user has any of the tools listed in product.exeBasedExtensionTips, fetch corresponding recommendations + * If user has any of the tools listed in this.productService.exeBasedExtensionTips, fetch corresponding recommendations */ - private fetchExecutableRecommendations(important: boolean): Promise { - const homeDir = os.homedir(); - let foundExecutables: Set = new Set(); + private async fetchExecutableRecommendations(important: boolean): Promise { + if (isWeb) { + return; + } - let findExecutable = (exeName: string, tip: IExeBasedExtensionTip, path: string) => { - return pfs.fileExists(path).then(exists => { + const foundExecutables: Set = new Set(); + const findExecutable = (exeName: string, tip: IExeBasedExtensionTip, path: string) => { + return this.fileService.exists(URI.file(path)).then(exists => { if (exists && !foundExecutables.has(exeName)) { foundExecutables.add(exeName); (tip['recommendations'] || []).forEach(extensionId => { @@ -1004,9 +1005,9 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); }; - let promises: Promise[] = []; + const promises: Promise[] = []; // Loop through recommended extensions - forEach(product.exeBasedExtensionTips, entry => { + forEach(this.productService.exeBasedExtensionTips, entry => { if (typeof entry.value !== 'object' || !Array.isArray(entry.value['recommendations'])) { return; } @@ -1014,24 +1015,24 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return; } const exeName = entry.key; - if (process.platform === 'win32') { + if (platform === 'win32') { let windowsPath = entry.value['windowsPath']; if (!windowsPath || typeof windowsPath !== 'string') { return; } - windowsPath = windowsPath.replace('%USERPROFILE%', process.env['USERPROFILE']!) - .replace('%ProgramFiles(x86)%', process.env['ProgramFiles(x86)']!) - .replace('%ProgramFiles%', process.env['ProgramFiles']!) - .replace('%APPDATA%', process.env['APPDATA']!) - .replace('%WINDIR%', process.env['WINDIR']!); + windowsPath = windowsPath.replace('%USERPROFILE%', processEnv['USERPROFILE']!) + .replace('%ProgramFiles(x86)%', processEnv['ProgramFiles(x86)']!) + .replace('%ProgramFiles%', processEnv['ProgramFiles']!) + .replace('%APPDATA%', processEnv['APPDATA']!) + .replace('%WINDIR%', processEnv['WINDIR']!); promises.push(findExecutable(exeName, entry.value, windowsPath)); } else { promises.push(findExecutable(exeName, entry.value, join('/usr/local/bin', exeName))); - promises.push(findExecutable(exeName, entry.value, join(homeDir, exeName))); + promises.push(findExecutable(exeName, entry.value, join(this.environmentService.userHome, exeName))); } }); - return Promise.all(promises).then(() => undefined); + await Promise.all(promises); } /** @@ -1078,7 +1079,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const storageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations'; const workspaceUri = this.contextService.getWorkspace().folders[0].uri; - return Promise.all([getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, false), getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, true)]).then(([hashedRemotes1, hashedRemotes2]) => { + return Promise.all([this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, false), this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, true)]).then(([hashedRemotes1, hashedRemotes2]) => { const hashedRemotes = (hashedRemotes1 || []).concat(hashedRemotes2 || []); if (!hashedRemotes.length) { return undefined; @@ -1143,4 +1144,5 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private isExtensionAllowedToBeRecommended(id: string): boolean { return this._allIgnoredRecommendations.indexOf(id.toLowerCase()) === -1; } + } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 32bd703fba0..b7bed063f07 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionsLabel, ExtensionsChannelId, PreferencesLabel, IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/contrib/output/common/output'; @@ -45,9 +45,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); +registerSingleton(IExtensionTipsService, ExtensionTipsService); Registry.as(OutputExtensions.OutputChannels) .registerChannel({ id: ExtensionsChannelId, label: ExtensionsLabel, log: false }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts new file mode 100644 index 00000000000..439f218cfb3 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/browser/extensions.web.contribution.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; + +// Singletons +registerSingleton(IExtensionTipsService, ExtensionTipsService); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index fdb0056d228..ec19e774ab1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -346,7 +346,7 @@ export class RemoteInstallAction extends InstallInOtherServerAction { } protected getInstallLabel(): string { - return this.extensionManagementServerService.remoteExtensionManagementServer ? localize('Install on Server', "Install on {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label) : InstallInOtherServerAction.INSTALL_LABEL; + return this.extensionManagementServerService.remoteExtensionManagementServer ? localize('Install on Server', "Install in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label) : InstallInOtherServerAction.INSTALL_LABEL; } } @@ -692,7 +692,13 @@ export class ManageExtensionAction extends ExtensionDropDownAction { ]); groups.push([this.instantiationService.createInstance(UninstallAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); - groups.push([this.instantiationService.createInstance(ExtensionInfoAction), this.instantiationService.createInstance(ExtensionSettingsAction)]); + + const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction)]; + if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { + extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); + } + + groups.push(extensionActions); groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); @@ -3014,9 +3020,12 @@ interface IExtensionPickItem extends IQuickPickItem { extension?: IExtension; } -export class InstallLocalExtensionsOnRemoteAction extends Action { +export class InstallLocalExtensionsInRemoteAction extends Action { + + private extensions: IExtension[] | undefined = undefined; constructor( + private readonly selectAndInstall: boolean, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @@ -3026,34 +3035,58 @@ export class InstallLocalExtensionsOnRemoteAction extends Action { @IProgressService private readonly progressService: IProgressService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { - super('workbench.extensions.actions.installLocalExtensionsOnRemote'); + super('workbench.extensions.actions.installLocalExtensionsInRemote'); this.update(); - this._register(this.extensionsWorkbenchService.onChange(() => this.update())); + this.extensionsWorkbenchService.queryLocal().then(() => this.updateExtensions()); + this._register(this.extensionsWorkbenchService.onChange(() => { + if (this.extensions) { + this.updateExtensions(); + } + })); } get label(): string { - return this.extensionManagementServerService.remoteExtensionManagementServer ? - localize('install local extensions', "Install Local Extensions on {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label) : ''; + if (this.extensionManagementServerService.remoteExtensionManagementServer) { + return this.selectAndInstall ? + localize('select and install local extensions', "Install Local Extensions in {0}...", this.extensionManagementServerService.remoteExtensionManagementServer.label) + : localize('install local extensions', "Install Local Extensions in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label); + } + return ''; + } + + private updateExtensions(): void { + this.extensions = this.extensionsWorkbenchService.local; + this.update(); } private update(): void { - this.enabled = this.getLocalExtensionsToInstall().length > 0; + this.enabled = !!this.extensions && this.getExtensionsToInstall(this.extensions).length > 0; + this.tooltip = this.label; } - private getLocalExtensionsToInstall(): IExtension[] { - return this.extensionsWorkbenchService.local.filter(extension => { + async run(): Promise { + if (this.selectAndInstall) { + return this.selectAndInstallLocalExtensions(); + } else { + const extensionsToInstall = await this.queryExtensionsToInstall(); + return this.installLocalExtensions(extensionsToInstall); + } + } + + private async queryExtensionsToInstall(): Promise { + const local = await this.extensionsWorkbenchService.queryLocal(); + return this.getExtensionsToInstall(local); + } + + private getExtensionsToInstall(local: IExtension[]): IExtension[] { + return local.filter(extension => { const action = this.instantiationService.createInstance(RemoteInstallAction); action.extension = extension; return action.enabled; }); } - async run(): Promise { - this.selectAndInstallLocalExtensions(); - return Promise.resolve(); - } - - private selectAndInstallLocalExtensions(): void { + private async selectAndInstallLocalExtensions(): Promise { const quickPick = this.quickInputService.createQuickPick(); quickPick.busy = true; const disposable = quickPick.onDidAccept(() => { @@ -3063,9 +3096,10 @@ export class InstallLocalExtensionsOnRemoteAction extends Action { this.onDidAccept(quickPick.selectedItems); }); quickPick.show(); - const localExtensionsToInstall = this.getLocalExtensionsToInstall(); + const localExtensionsToInstall = await this.queryExtensionsToInstall(); quickPick.busy = false; if (localExtensionsToInstall.length) { + quickPick.title = localize('install local extensions title', "Install Local Extensions in {0}", this.extensionManagementServerService.remoteExtensionManagementServer!.label); quickPick.placeholder = localize('select extensions to install', "Select extensions to install"); quickPick.canSelectMany = true; localExtensionsToInstall.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)); @@ -3114,9 +3148,9 @@ export class InstallLocalExtensionsOnRemoteAction extends Action { this.notificationService.notify({ severity: Severity.Info, - message: localize('finished installing', "Completed installing the extensions. Please reload the window now."), + message: localize('finished installing', "Successfully installed extensions in {0}. Please reload the window to enable them.", this.extensionManagementServerService.remoteExtensionManagementServer!.label), actions: { - primary: [new Action('realod', localize('reload', "Realod Window"), '', true, + primary: [new Action('realod', localize('reload', "Reload Window"), '', true, () => this.windowService.reloadWindow())] } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts b/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts index 95d8c05f1c1..6474d2f3c58 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts @@ -68,7 +68,7 @@ export class ExtensionDependencyChecker extends Disposable implements IWorkbench severity: Severity.Info, message: localize('finished installing missing deps', "Finished installing missing dependencies. Please reload the window now."), actions: { - primary: [new Action('realod', localize('reload', "Realod Window"), '', true, + primary: [new Action('realod', localize('reload', "Reload Window"), '', true, () => this.windowService.reloadWindow())] } }); @@ -77,4 +77,4 @@ export class ExtensionDependencyChecker extends Disposable implements IWorkbench this.notificationService.info(localize('no missing deps', "There are no missing dependencies to install.")); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 8747ad8ca51..aeb50c75c64 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -156,7 +156,7 @@ export class UnknownExtensionRenderer implements IListRenderer { - return this.extensionsWorkdbenchService.open(this.extensionData.extension, sideByside); + if (this._extensionData) { + return this.extensionsWorkdbenchService.open(this._extensionData.extension, sideByside); + } + return Promise.resolve(); } } @@ -263,4 +262,4 @@ export class ExtensionData implements IExtensionData { } return null; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 9bed184ac05..d9aef35dae5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -22,7 +22,7 @@ import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, AutoUpdate import { ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction, ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, - EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, InstallLocalExtensionsOnRemoteAction + EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -319,7 +319,8 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensionsViewlet { - private onSearchChange: EventOf; + private readonly _onSearchChange: Emitter = this._register(new Emitter()); + private readonly onSearchChange: EventOf = this._onSearchChange.event; private nonEmptyWorkspaceContextKey: IContextKey; private defaultViewsContextKey: IContextKey; private searchMarketplaceExtensionsContextKey: IContextKey; @@ -333,12 +334,10 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private defaultRecommendedExtensionsContextKey: IContextKey; private searchDelayer: Delayer; - private root: HTMLElement; - - private searchBox: SuggestEnabledInput; - private extensionsBox: HTMLElement; - private primaryActions: IAction[]; - private secondaryActions: IAction[] | null; + private root: HTMLElement | undefined; + private searchBox: SuggestEnabledInput | undefined; + private primaryActions: IAction[] | undefined; + private secondaryActions: IAction[] | null = null; private readonly searchViewletState: MementoObject; constructor( @@ -348,7 +347,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio @IInstantiationService instantiationService: IInstantiationService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @INotificationService private readonly notificationService: INotificationService, @IViewletService private readonly viewletService: IViewletService, @IThemeService themeService: IThemeService, @@ -405,7 +403,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio triggerCharacters: ['@'], sortKey: (item: string) => { if (item.indexOf(':') === -1) { return 'a'; } - else if (/ext:/.test(item) || /tag:/.test(item)) { return 'b'; } + else if (/ext:/.test(item) || /id:/.test(item) || /tag:/.test(item)) { return 'b'; } else if (/sort:/.test(item)) { return 'c'; } else { return 'd'; } }, @@ -418,32 +416,35 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this._register(attachSuggestEnabledInputBoxStyler(this.searchBox, this.themeService)); - const _searchChange = new Emitter(); - this.onSearchChange = _searchChange.event; this._register(this.searchBox.onInputDidChange(() => { this.triggerSearch(); - _searchChange.fire(this.searchBox.getValue()); + this._onSearchChange.fire(this.searchBox!.getValue()); }, this)); this._register(this.searchBox.onShouldFocusResults(() => this.focusListView(), this)); this._register(this.onDidChangeVisibility(visible => { if (visible) { - this.searchBox.focus(); + this.searchBox!.focus(); } })); - this.extensionsBox = append(this.root, $('.extensions')); - super.create(this.extensionsBox); + super.create(append(this.root, $('.extensions'))); } focus(): void { - this.searchBox.focus(); + if (this.searchBox) { + this.searchBox.focus(); + } } layout(dimension: Dimension): void { - toggleClass(this.root, 'narrow', dimension.width <= 300); - this.searchBox.layout({ height: 20, width: dimension.width - 34 }); + if (this.root) { + toggleClass(this.root, 'narrow', dimension.width <= 300); + } + if (this.searchBox) { + this.searchBox.layout({ height: 20, width: dimension.width - 34 }); + } super.layout(new Dimension(dimension.width, dimension.height - 38)); } @@ -454,7 +455,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio getActions(): IAction[] { if (!this.primaryActions) { this.primaryActions = [ - this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox.getValue()) + this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : '') ]; } return this.primaryActions; @@ -478,7 +479,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL), ...(this.configurationService.getValue(AutoUpdateConfigurationKey) ? [this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)] : [this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)]), this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL), - ...(this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer ? [this.instantiationService.createInstance(InstallLocalExtensionsOnRemoteAction)] : []), new Separator(), this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL), this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL) @@ -489,22 +489,24 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio } search(value: string): void { - const event = new Event('input', { bubbles: true }) as SearchInputEvent; - event.immediate = true; + if (this.searchBox) { + const event = new Event('input', { bubbles: true }) as SearchInputEvent; + event.immediate = true; - this.searchBox.setValue(value); + this.searchBox.setValue(value); + } } - private triggerSearch(immediate = false): void { - this.searchDelayer.trigger(() => this.doSearch(), immediate || !this.searchBox.getValue() ? 0 : 500).then(undefined, err => this.onError(err)); + private triggerSearch(): void { + this.searchDelayer.trigger(() => this.doSearch(), this.searchBox && this.searchBox.getValue() ? 500 : 0).then(undefined, err => this.onError(err)); } private normalizedQuery(): string { - return this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:'); + return this.searchBox ? this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:') : ''; } protected saveState(): void { - const value = this.searchBox.getValue(); + const value = this.searchBox ? this.searchBox.getValue() : ''; if (ExtensionsListView.isLocalExtensionsQuery(value)) { this.searchViewletState['query.value'] = value; } else { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 5f905a190e2..733aa5fa5b1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -14,11 +14,11 @@ import { IExtensionManagementServer, IExtensionManagementServerService, IExtensi import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { append, $, toggleClass } from 'vs/base/browser/dom'; +import { append, $, toggleClass, addClass } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/contrib/extensions/browser/extensionsList'; -import { IExtension, IExtensionsWorkbenchService, ExtensionState } from '../common/extensions'; -import { Query } from '../common/extensionQuery'; +import { IExtension, IExtensionsWorkbenchService, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions'; +import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; @@ -27,8 +27,8 @@ import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/brows import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; -import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -73,15 +73,16 @@ class ExtensionListViewWarning extends Error { } export class ExtensionsListView extends ViewletPanel { - private readonly server: IExtensionManagementServer | undefined; - private messageContainer: HTMLElement; - private messageSeverityIcon: HTMLElement; - private messageBox: HTMLElement; - private extensionsList: HTMLElement; - private badge: CountBadge; - protected badgeContainer: HTMLElement; - private list: WorkbenchPagedList | null; - private queryRequest: { query: string, request: CancelablePromise> } | null; + protected readonly server: IExtensionManagementServer | undefined; + private bodyTemplate: { + messageContainer: HTMLElement; + messageSeverityIcon: HTMLElement; + messageBox: HTMLElement; + extensionsList: HTMLElement; + } | undefined; + private badge: CountBadge | undefined; + private list: WorkbenchPagedList | null = null; + private queryRequest: { query: string, request: CancelablePromise> } | null = null; constructor( options: ExtensionsListViewOptions, @@ -103,31 +104,27 @@ export class ExtensionsListView extends ViewletPanel { @IProductService protected readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService); + super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService); this.server = options.server; } protected renderHeader(container: HTMLElement): void { - this.renderHeaderTitle(container); - } + addClass(container, 'extension-view-header'); + super.renderHeader(container); - renderHeaderTitle(container: HTMLElement): void { - super.renderHeaderTitle(container, this.title); - - this.badgeContainer = append(container, $('.count-badge-wrapper')); - this.badge = new CountBadge(this.badgeContainer); + this.badge = new CountBadge(append(container, $('.count-badge-wrapper'))); this._register(attachBadgeStyler(this.badge, this.themeService)); } renderBody(container: HTMLElement): void { - this.extensionsList = append(container, $('.extensions-list')); - this.messageContainer = append(container, $('.message-container')); - this.messageSeverityIcon = append(this.messageContainer, $('')); - this.messageBox = append(this.messageContainer, $('.message')); + const extensionsList = append(container, $('.extensions-list')); + const messageContainer = append(container, $('.message-container')); + const messageSeverityIcon = append(messageContainer, $('')); + const messageBox = append(messageContainer, $('.message')); const delegate = new Delegate(); const extensionsViewState = new ExtensionsViewState(); const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState); - this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], { + this.list = this.instantiationService.createInstance(WorkbenchPagedList, extensionsList, delegate, [renderer], { ariaLabel: localize('extensions', "Extensions"), multipleSelectionSupport: false, setRowLineHeight: false, @@ -147,10 +144,19 @@ export class ExtensionsListView extends ViewletPanel { .map(e => e.elements[0]) .filter(e => !!e) .on(this.pin, this)); + + this.bodyTemplate = { + extensionsList, + messageBox, + messageContainer, + messageSeverityIcon + }; } protected layoutBody(height: number, width: number): void { - this.extensionsList.style.height = height + 'px'; + if (this.bodyTemplate) { + this.bodyTemplate.extensionsList.style.height = height + 'px'; + } if (this.list) { this.list.layout(height, width); } @@ -482,7 +488,7 @@ export class ExtensionsListView extends ViewletPanel { } - private _searchExperiments: Promise; + private _searchExperiments: Promise | undefined; private getSearchExperiments(): Promise { if (!this._searchExperiments) { this._searchExperiments = this.experimentService.getExperimentsByType(ExperimentActionType.ExtensionSearchResults); @@ -693,24 +699,27 @@ export class ExtensionsListView extends ViewletPanel { this.list.scrollTop = 0; const count = this.count(); - toggleClass(this.extensionsList, 'hidden', count === 0); - toggleClass(this.messageContainer, 'hidden', count > 0); - this.badge.setCount(count); + if (this.bodyTemplate && this.badge) { - if (count === 0 && this.isBodyVisible()) { - if (error) { - if (error instanceof ExtensionListViewWarning) { - this.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning); - this.messageBox.textContent = getErrorMessage(error); + toggleClass(this.bodyTemplate.extensionsList, 'hidden', count === 0); + toggleClass(this.bodyTemplate.messageContainer, 'hidden', count > 0); + this.badge.setCount(count); + + if (count === 0 && this.isBodyVisible()) { + if (error) { + if (error instanceof ExtensionListViewWarning) { + this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning); + this.bodyTemplate.messageBox.textContent = getErrorMessage(error); + } else { + this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Error); + this.bodyTemplate.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error)); + } } else { - this.messageSeverityIcon.className = SeverityIcon.className(Severity.Error); - this.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error)); + this.bodyTemplate.messageSeverityIcon.className = ''; + this.bodyTemplate.messageBox.textContent = localize('no extensions found', "No extensions found."); } - } else { - this.messageSeverityIcon.className = ''; - this.messageBox.textContent = localize('no extensions found', "No extensions found."); + alert(this.bodyTemplate.messageBox.textContent); } - alert(this.messageBox.textContent); } } } @@ -850,7 +859,7 @@ export class ServerExtensionsView extends ExtensionsListView { @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IProductService productService: IProductService, - @IContextKeyService contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService ) { options.server = server; super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService); @@ -864,6 +873,15 @@ export class ServerExtensionsView extends ExtensionsListView { } return super.show(query.trim()); } + + getActions(): IAction[] { + if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === this.server) { + const installLocalExtensionsInRemoteAction = this._register(this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction, false)); + installLocalExtensionsInRemoteAction.class = 'octicon octicon-cloud-download'; + return [installLocalExtensionsInRemoteAction]; + } + return []; + } } export class EnabledExtensionsView extends ExtensionsListView { @@ -943,7 +961,7 @@ export class RecommendedExtensionsView extends ExtensionsListView { export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { private readonly recommendedExtensionsQuery = '@recommended:workspace'; - private installAllAction: InstallWorkspaceRecommendedExtensionsAction; + private installAllAction: InstallWorkspaceRecommendedExtensionsAction | undefined; renderBody(container: HTMLElement): void { super.renderBody(container); @@ -953,25 +971,15 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { this._register(this.contextService.onDidChangeWorkbenchState(() => this.update())); } - renderHeader(container: HTMLElement): void { - super.renderHeader(container); + getActions(): IAction[] { + if (!this.installAllAction) { + this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, [])); + this.installAllAction.class = 'octicon octicon-cloud-download'; + } - const listActionBar = $('.list-actionbar-container'); - container.insertBefore(listActionBar, this.badgeContainer); - - const actionbar = this._register(new ActionBar(listActionBar, { - animated: false - })); - actionbar.onDidRun(({ error }) => error && this.notificationService.error(error)); - - this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, [])); const configureWorkspaceFolderAction = this._register(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL)); - - this.installAllAction.class = 'octicon octicon-cloud-download'; configureWorkspaceFolderAction.class = 'octicon octicon-pencil'; - - actionbar.push([this.installAllAction], { icon: true, label: false }); - actionbar.push([configureWorkspaceFolderAction], { icon: true, label: false }); + return [this.installAllAction, configureWorkspaceFolderAction]; } async show(query: string): Promise> { @@ -986,9 +994,11 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { this.setRecommendationsToInstall(); } - private setRecommendationsToInstall(): Promise { - return this.getRecommendationsToInstall() - .then(recommendations => { this.installAllAction.recommendations = recommendations; }); + private async setRecommendationsToInstall(): Promise { + const recommendations = await this.getRecommendationsToInstall(); + if (this.installAllAction) { + this.installAllAction.recommendations = recommendations; + } } private getRecommendationsToInstall(): Promise { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index e27cc72e7d1..05741f72a62 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -18,9 +18,9 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } + private _extension: IExtension | null = null; + get extension(): IExtension | null { return this._extension; } + set extension(extension: IExtension | null) { this._extension = extension; this.update(); } update(): void { this.render(); } abstract render(): void; } @@ -183,7 +183,7 @@ export class RecommendationWidget extends ExtensionWidget { private element?: HTMLElement; private readonly disposables = this._register(new DisposableStore()); - private _tooltip: string; + private _tooltip: string = ''; get tooltip(): string { return this._tooltip; } set tooltip(tooltip: string) { if (this._tooltip !== tooltip) { @@ -314,4 +314,4 @@ class RemoteBadge extends Disposable { updateTitle(); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 7ddf7c7fa70..31f9d115e24 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -131,7 +131,7 @@ class Extension implements IExtension { private get localIconUrl(): string | null { if (this.local && this.local.manifest.icon) { - return asDomUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(); + return asDomUri(resources.joinPath(this.local.location, this.local.manifest.icon)).toString(true); } return null; } @@ -484,7 +484,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private readonly _onChange: Emitter = new Emitter(); get onChange(): Event { return this._onChange.event; } - private _extensionAllowedBadgeProviders: string[]; private installing: IExtension[] = []; constructor( @@ -1020,13 +1019,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return changed; } - get allowedBadgeProviders(): string[] { - if (!this._extensionAllowedBadgeProviders) { - this._extensionAllowedBadgeProviders = (this.productService.extensionAllowedBadgeProviders || []).map(s => s.toLowerCase()); - } - return this._extensionAllowedBadgeProviders; - } - private _activityCallBack: (() => void) | null = null; private updateActivity(): void { if ((this.localExtensions && this.localExtensions.local.some(e => e.state === ExtensionState.Installing || e.state === ExtensionState.Uninstalling)) @@ -1096,12 +1088,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } - private _ignoredAutoUpdateExtensions: string[]; + private _ignoredAutoUpdateExtensions: string[] | undefined; private get ignoredAutoUpdateExtensions(): string[] { if (!this._ignoredAutoUpdateExtensions) { this._ignoredAutoUpdateExtensions = JSON.parse(this.storageService.get('extensions.ignoredAutoUpdateExtension', StorageScope.GLOBAL, '[]') || '[]'); } - return this._ignoredAutoUpdateExtensions; + return this._ignoredAutoUpdateExtensions!; } private set ignoredAutoUpdateExtensions(extensionIds: string[]) { diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensions.css b/src/vs/workbench/contrib/extensions/browser/media/extensions.css index 4a74b3ce38d..ad1858f6f05 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensions.css @@ -6,8 +6,3 @@ .monaco-workbench .activitybar > .content .monaco-action-bar .action-label.extensions { -webkit-mask: url('extensions-activity-bar.svg') no-repeat 50% 50%; } - -.extensions .split-view-view .panel-header .count-badge-wrapper { - position: absolute; - right: 12px; -} \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index cfc659bbbdf..57be7e4e1d4 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -27,13 +27,16 @@ height: calc(100% - 38px); } -.extensions-viewlet > .extensions .list-actionbar-container .monaco-action-bar .action-item > .octicon { - font-size: 12px; - line-height: 1; - margin-right: 10px; +.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar { + margin-right: 4px; } -.extensions-viewlet > .extensions .list-actionbar-container .monaco-action-bar .action-item.disabled { +.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar .action-item > .action-label.icon.octicon { + vertical-align: middle; + line-height: 22px; +} + +.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar .action-item.disabled { display: none; } @@ -44,7 +47,7 @@ } .extensions-viewlet > .extensions .panel-header { - padding-right: 28px; + padding-right: 6px; } .extensions-viewlet > .extensions .panel-header > .title { @@ -61,6 +64,10 @@ padding-left: 5px; } +.extensions-viewlet > .extensions .message-container .severity-icon { + flex-shrink: 0; +} + .extensions-viewlet > .extensions .monaco-list-row > .bookmark { display: inline-block; height: 20px; diff --git a/src/vs/workbench/contrib/extensions/browser/media/markdown.css b/src/vs/workbench/contrib/extensions/browser/media/markdown.css deleted file mode 100644 index 521ae95b082..00000000000 --- a/src/vs/workbench/contrib/extensions/browser/media/markdown.css +++ /dev/null @@ -1,175 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -body { - padding: 10px 20px; - line-height: 22px; -} - -img { - max-width: 100%; - max-height: 100%; -} - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -a:focus, -input:focus, -select:focus, -textarea:focus { - outline: 1px solid -webkit-focus-ring-color; - outline-offset: -1px; -} - -hr { - border: 0; - height: 2px; - border-bottom: 2px solid; -} - -h1 { - padding-bottom: 0.3em; - line-height: 1.2; - border-bottom-width: 1px; - border-bottom-style: solid; -} - -h1, h2, h3 { - font-weight: normal; -} - -table { - border-collapse: collapse; -} - -table > thead > tr > th { - text-align: left; - border-bottom: 1px solid; -} - -table > thead > tr > th, -table > thead > tr > td, -table > tbody > tr > th, -table > tbody > tr > td { - padding: 5px 10px; -} - -table > tbody > tr + tr > td { - border-top: 1px solid; -} - -blockquote { - margin: 0 7px 0 5px; - padding: 0 16px 0 10px; - border-left-width: 5px; - border-left-style: solid; -} - -code { - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; - font-size: 14px; - line-height: 19px; -} - -.mac code { - font-size: 12px; - line-height: 18px; -} - -code > div { - padding: 16px; - border-radius: 3px; - overflow: auto; -} - -#scroll-to-top { - position: fixed; - width: 40px; - height: 40px; - right: 25px; - bottom: 25px; - background-color:#444444; - border-radius: 50%; - cursor: pointer; - box-shadow: 1px 1px 1px rgba(0,0,0,.25); - outline: none; - display: flex; - justify-content: center; - align-items: center; -} - -#scroll-to-top:hover { - background-color:#007acc; - box-shadow: 2px 2px 2px rgba(0,0,0,.25); -} - -body.vscode-light #scroll-to-top { - background-color: #949494; -} - -body.vscode-high-contrast #scroll-to-top:hover { - background-color: #007acc; -} - -body.vscode-high-contrast #scroll-to-top { - background-color: black; - border: 2px solid #6fc3df; - box-shadow: none; -} -body.vscode-high-contrast #scroll-to-top:hover { - background-color: #007acc; -} - -#scroll-to-top span.icon::before { - content: ""; - display: block; - /* Chevron up icon */ - background:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNiAxNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTYgMTY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRkZGRkZGO30KCS5zdDF7ZmlsbDpub25lO30KPC9zdHlsZT4KPHRpdGxlPnVwY2hldnJvbjwvdGl0bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04LDUuMWwtNy4zLDcuM0wwLDExLjZsOC04bDgsOGwtMC43LDAuN0w4LDUuMXoiLz4KPHJlY3QgY2xhc3M9InN0MSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ii8+Cjwvc3ZnPgo='); - width: 16px; - height: 16px; -} - -/** Theming */ -.vscode-light code > div { - background-color: rgba(220, 220, 220, 0.4); -} - -.vscode-dark code > div { - background-color: rgba(10, 10, 10, 0.4); -} - -.vscode-high-contrast code > div { - background-color: rgb(0, 0, 0); -} - -.vscode-high-contrast h1 { - border-color: rgb(0, 0, 0); -} - -.vscode-light table > thead > tr > th { - border-color: rgba(0, 0, 0, 0.69); -} - -.vscode-dark table > thead > tr > th { - border-color: rgba(255, 255, 255, 0.69); -} - -.vscode-light h1, -.vscode-light hr, -.vscode-light table > tbody > tr + tr > td { - border-color: rgba(0, 0, 0, 0.18); -} - -.vscode-dark h1, -.vscode-dark hr, -.vscode-dark table > tbody > tr + tr > td { - border-color: rgba(255, 255, 255, 0.18); -} diff --git a/src/vs/workbench/contrib/extensions/browser/media/profile-start-dark.svg b/src/vs/workbench/contrib/extensions/browser/media/profile-start-dark.svg index c67e550c15f..a60d77cd37a 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/profile-start-dark.svg +++ b/src/vs/workbench/contrib/extensions/browser/media/profile-start-dark.svg @@ -1,10 +1,4 @@ - - - - - - - - + + diff --git a/src/vs/workbench/contrib/extensions/browser/media/profile-start-light.svg b/src/vs/workbench/contrib/extensions/browser/media/profile-start-light.svg index c67e550c15f..f541ed4d519 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/profile-start-light.svg +++ b/src/vs/workbench/contrib/extensions/browser/media/profile-start-light.svg @@ -1,10 +1,4 @@ - - - - - - - - + + diff --git a/src/vs/workbench/contrib/extensions/browser/media/start-dark.svg b/src/vs/workbench/contrib/extensions/browser/media/start-dark.svg index 9debdf8c625..8b0a58eca9b 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/start-dark.svg +++ b/src/vs/workbench/contrib/extensions/browser/media/start-dark.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/extensions/browser/media/start-light.svg b/src/vs/workbench/contrib/extensions/browser/media/start-light.svg index dac8bdae31f..2563bfa114b 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/start-light.svg +++ b/src/vs/workbench/contrib/extensions/browser/media/start-light.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts index 19e4a47c99c..b140853a820 100644 --- a/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts +++ b/src/vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller.ts @@ -11,7 +11,7 @@ import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILabelService } from 'vs/platform/label/common/label'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { InstallLocalExtensionsOnRemoteAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { InstallLocalExtensionsInRemoteAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchContribution { @@ -22,16 +22,16 @@ export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchC ) { super(); if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - const installLocalExtensionsOnRemoteAction = instantiationService.createInstance(InstallLocalExtensionsOnRemoteAction); - CommandsRegistry.registerCommand('workbench.extensions.installLocalExtensions', () => installLocalExtensionsOnRemoteAction.run()); + const installLocalExtensionsInRemoteAction = instantiationService.createInstance(InstallLocalExtensionsInRemoteAction, true); + CommandsRegistry.registerCommand('workbench.extensions.installLocalExtensions', () => installLocalExtensionsInRemoteAction.run()); let disposable = Disposable.None; const appendMenuItem = () => { disposable.dispose(); disposable = MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'workbench.extensions.installLocalExtensions', - category: localize('extensions', "Extensions"), - title: installLocalExtensionsOnRemoteAction.label + category: localize('remote', "Remote"), + title: installLocalExtensionsInRemoteAction.label } }); }; @@ -41,4 +41,4 @@ export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchC } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 33e46eecf82..47b8e916d88 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -91,7 +91,6 @@ export interface IExtensionsWorkbenchService { setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise; open(extension: IExtension, sideByside?: boolean): Promise; checkForUpdates(): Promise; - allowedBadgeProviders: string[]; } export const ConfigurationKey = 'extensions'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index 283c3406d8f..af073fb2c6f 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionHostProfile, ProfileSession, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -23,7 +23,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService { - _serviceBrand: ServiceIdentifier; + _serviceBrand: any; private readonly _onDidChangeState: Emitter = this._register(new Emitter()); public readonly onDidChangeState: Event = this._onDidChangeState.event; @@ -34,7 +34,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio private readonly _unresponsiveProfiles = new Map(); private _profile: IExtensionHostProfile | null; private _profileSession: ProfileSession | null; - private _state: ProfileSessionState; + private _state: ProfileSessionState = ProfileSessionState.None; private profilingStatusBarIndicator: IStatusbarEntryAccessor | undefined; private readonly profilingStatusBarIndicatorLabelUpdater = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index 643c6e1b456..4c21e1e9181 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -7,9 +7,7 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -25,7 +23,6 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; // Singletons -registerSingleton(IExtensionTipsService, ExtensionTipsService); registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); @@ -134,4 +131,4 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { }, group: 'navigation', when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID)) -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts index 3b81b03291c..e7e9842b027 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts @@ -17,11 +17,12 @@ import { join } from 'vs/base/common/path'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; abstract class RepoInfo { - readonly base: string; - readonly owner: string; - readonly repo: string; + abstract get base(): string; + abstract get owner(): string; + abstract get repo(): string; static fromExtension(desc: IExtensionDescription): RepoInfo | undefined { @@ -117,6 +118,7 @@ class ReportExtensionSlowAction extends Action { readonly repoInfo: RepoInfo, readonly profile: IExtensionHostProfile, @IDialogService private readonly _dialogService: IDialogService, + @IOpenerService private readonly _openerService: IOpenerService ) { super('report.slow', localize('cmd.report', "Report Issue")); } @@ -140,7 +142,7 @@ class ReportExtensionSlowAction extends Action { - VSCode version: \`${pkg.version}\`\n\n${message}`); const url = `${this.repoInfo.base}/${this.repoInfo.owner}/${this.repoInfo.repo}/issues/new/?body=${body}&title=${title}`; - window.open(url); + this._openerService.open(URI.parse(url)); this._dialogService.show( Severity.Info, @@ -158,6 +160,7 @@ class ShowExtensionSlowAction extends Action { readonly repoInfo: RepoInfo, readonly profile: IExtensionHostProfile, @IDialogService private readonly _dialogService: IDialogService, + @IOpenerService private readonly _openerService: IOpenerService ) { super('show.slow', localize('cmd.show', "Show Issues")); } @@ -172,7 +175,7 @@ class ShowExtensionSlowAction extends Action { // show issues const url = `${this.repoInfo.base}/${this.repoInfo.owner}/${this.repoInfo.repo}/issues?utf8=✓&q=is%3Aissue+state%3Aopen+%22Extension+causes+high+cpu+load%22`; - window.open(url); + this._openerService.open(URI.parse(url)); this._dialogService.show( Severity.Info, diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 1cc01b85802..b6ac536b089 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -44,6 +44,8 @@ import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/pl import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { SlowExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -120,7 +122,8 @@ export class RuntimeExtensionsEditor extends BaseEditor { @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, @IStorageService storageService: IStorageService, @ILabelService private readonly _labelService: ILabelService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @IOpenerService private readonly _openerService: IOpenerService ) { super(RuntimeExtensionsEditor.ID, telemetryService, themeService, storageService); @@ -312,7 +315,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { data.actionbar.push(this._instantiationService.createInstance(SlowExtensionAction, element.description, element.unresponsiveProfile), { icon: true, label: true }); } if (isNonEmptyArray(element.status.runtimeErrors)) { - data.actionbar.push(new ReportExtensionIssueAction(element), { icon: true, label: true }); + data.actionbar.push(new ReportExtensionIssueAction(element, this._openerService), { icon: true, label: true }); } let title: string; @@ -416,7 +419,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { const actions: IAction[] = []; - actions.push(new ReportExtensionIssueAction(e.element)); + actions.push(new ReportExtensionIssueAction(e.element, this._openerService)); actions.push(new Separator()); if (e.element.marketplaceInfo) { @@ -480,7 +483,7 @@ export class ReportExtensionIssueAction extends Action { marketplaceInfo: IExtension; status?: IExtensionsStatus; unresponsiveProfile?: IExtensionHostProfile - }) { + }, @IOpenerService private readonly openerService: IOpenerService) { super(ReportExtensionIssueAction._id, ReportExtensionIssueAction._label, 'extension-action report-issue'); this.enabled = extension.marketplaceInfo && extension.marketplaceInfo.type === ExtensionType.User @@ -490,7 +493,7 @@ export class ReportExtensionIssueAction extends Action { } async run(): Promise { - window.open(this._url); + this.openerService.open(URI.parse(this._url)); } private static _generateNewIssueUrl(extension: { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 2334ecebeec..32c9e48f8aa 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -16,7 +16,7 @@ import { import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; @@ -1518,7 +1518,7 @@ suite('ExtensionsActions Test', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install on remote', testObject.label); + assert.equal('Install in remote', testObject.label); assert.equal('extension-action prominent install', testObject.class); }); @@ -1544,7 +1544,7 @@ suite('ExtensionsActions Test', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install on remote', testObject.label); + assert.equal('Install in remote', testObject.label); assert.equal('extension-action prominent install', testObject.class); onInstallExtension.fire({ identifier: localWorkspaceExtension.identifier, gallery }); @@ -1577,7 +1577,7 @@ suite('ExtensionsActions Test', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install on remote', testObject.label); + assert.equal('Install in remote', testObject.label); assert.equal('extension-action prominent install', testObject.class); onInstallExtension.fire({ identifier: localWorkspaceExtension.identifier, gallery }); @@ -1608,7 +1608,7 @@ suite('ExtensionsActions Test', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install on remote', testObject.label); + assert.equal('Install in remote', testObject.label); assert.equal('extension-action prominent install', testObject.class); }); @@ -1704,7 +1704,7 @@ suite('ExtensionsActions Test', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install on remote', testObject.label); + assert.equal('Install in remote', testObject.label); uninstallEvent.fire(localWorkspaceExtension.identifier); assert.ok(!testObject.enabled); @@ -1825,7 +1825,7 @@ suite('ExtensionsActions Test', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install on remote', testObject.label); + assert.equal('Install in remote', testObject.label); assert.equal('extension-action prominent install', testObject.class); }); @@ -1848,7 +1848,7 @@ suite('ExtensionsActions Test', () => { await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; assert.ok(testObject.enabled); - assert.equal('Install on remote', testObject.label); + assert.equal('Install in remote', testObject.label); uninstallEvent.fire(languagePackExtension.identifier); assert.ok(!testObject.enabled); 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 cd6d14c858d..83231f515ea 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 @@ -15,14 +15,14 @@ import { DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestLifecycleService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestLifecycleService, TestSharedProcessService, productService } from 'vs/workbench/test/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; @@ -36,7 +36,6 @@ import { ConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensi import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { IURLService } from 'vs/platform/url/common/url'; -import product from 'vs/platform/product/node/product'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -52,6 +51,7 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { IFileService } from 'vs/platform/files/common/files'; +import { IProductService } from 'vs/platform/product/common/product'; const mockExtensionGallery: IGalleryExtension[] = [ aGalleryExtension('MockExtension1', { @@ -201,27 +201,31 @@ suite('ExtensionsTipsService Test', () => { instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IURLService, URLService); + instantiationService.set(IProductService, { + ...productService, + ...{ + extensionTips: { + 'ms-vscode.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}', + 'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx**/*.js,**/*.jsx,**/*.es6,**/.babelrc}', + 'lukehoban.Go': '**/*.go' + }, + extensionImportantTips: { + 'ms-python.python': { + 'name': 'Python', + 'pattern': '{**/*.py}' + }, + 'ms-vscode.PowerShell': { + 'name': 'PowerShell', + 'pattern': '{**/*.ps,**/*.ps1}' + } + } + } + }); experimentService = instantiationService.createInstance(TestExperimentService); instantiationService.stub(IExperimentService, experimentService); onModelAddedEvent = new Emitter(); - - product.extensionTips = { - 'ms-vscode.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}', - 'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx**/*.js,**/*.jsx,**/*.es6,**/.babelrc}', - 'lukehoban.Go': '**/*.go' - }; - product.extensionImportantTips = { - 'ms-python.python': { - 'name': 'Python', - 'pattern': '{**/*.py}' - }, - 'ms-vscode.PowerShell': { - 'name': 'PowerShell', - 'pattern': '{**/*.ps,**/*.ps1}' - } - }; }); suiteTeardown(() => { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index cf2b6c79522..318a7e17e45 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -17,7 +17,7 @@ import { import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IExtensionTipsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; @@ -35,8 +35,7 @@ import { URLService } from 'vs/platform/url/common/urlService'; import { URI } from 'vs/base/common/uri'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { SinonStub } from 'sinon'; -import { IExperimentService, ExperimentState, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService'; -import { ExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService'; +import { IExperimentService, ExperimentState, ExperimentActionType, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 04ed61d0d8a..c87fed721f4 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -17,7 +17,7 @@ import { import { IExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index 9cebd7fe920..ef308647b8d 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -20,6 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IProductService } from 'vs/platform/product/common/product'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; export interface IFeedback { feedback: string; @@ -27,7 +28,7 @@ export interface IFeedback { } export interface IFeedbackDelegate { - submitFeedback(feedback: IFeedback): void; + submitFeedback(feedback: IFeedback, openerService: IOpenerService): void; getCharacterLimit(sentiment: number): number; } @@ -46,13 +47,13 @@ export class FeedbackDropdown extends Dropdown { private readonly feedbackDelegate: IFeedbackDelegate; - private feedbackForm: HTMLFormElement | null; - private feedbackDescriptionInput: HTMLTextAreaElement | null; - private smileyInput: HTMLElement | null; - private frownyInput: HTMLElement | null; - private sendButton: Button; - private hideButton: HTMLInputElement; - private remainingCharacterCount: HTMLElement; + private feedbackForm: HTMLFormElement | null = null; + private feedbackDescriptionInput: HTMLTextAreaElement | null = null; + private smileyInput: HTMLElement | null = null; + private frownyInput: HTMLElement | null = null; + private sendButton: Button | null = null; + private hideButton: HTMLInputElement | null = null; + private remainingCharacterCount: HTMLElement | null = null; private requestFeatureLink: string | undefined; @@ -66,7 +67,8 @@ export class FeedbackDropdown extends Dropdown { @IIntegrityService private readonly integrityService: IIntegrityService, @IThemeService private readonly themeService: IThemeService, @IStatusbarService private readonly statusbarService: IStatusbarService, - @IProductService productService: IProductService + @IProductService productService: IProductService, + @IOpenerService private readonly openerService: IOpenerService ) { super(container, options); @@ -253,7 +255,7 @@ export class FeedbackDropdown extends Dropdown { // Checkbox: Hide Feedback Smiley const hideButtonContainer = dom.append(buttonsContainer, dom.$('div.hide-button-container')); - this.hideButton = dom.append(hideButtonContainer, dom.$('input.hide-button')); + this.hideButton = dom.append(hideButtonContainer, dom.$('input.hide-button')) as HTMLInputElement; this.hideButton.type = 'checkbox'; this.hideButton.checked = true; this.hideButton.id = 'hide-button'; @@ -316,7 +318,7 @@ export class FeedbackDropdown extends Dropdown { } private updateCharCountText(): void { - if (this.feedbackDescriptionInput) { + if (this.feedbackDescriptionInput && this.remainingCharacterCount && this.sendButton) { this.remainingCharacterCount.innerText = this.getCharCountText(this.feedbackDescriptionInput.value.length); this.sendButton.enabled = this.feedbackDescriptionInput.value.length > 0; } @@ -415,7 +417,7 @@ export class FeedbackDropdown extends Dropdown { this.feedbackDelegate.submitFeedback({ feedback: this.feedbackDescriptionInput.value, sentiment: this.sentiment - }); + }, this.openerService); this.hide(); } @@ -434,4 +436,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (linkColor) { collector.addRule(`.monaco-workbench .feedback-form .content .channels a { color: ${linkColor}; }`); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index 43d18373cc3..9eda590df6f 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -12,6 +12,8 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarService, StatusbarAlignment, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; class TwitterFeedbackService implements IFeedbackDelegate { @@ -23,11 +25,11 @@ class TwitterFeedbackService implements IFeedbackDelegate { return TwitterFeedbackService.HASHTAGS.join(','); } - submitFeedback(feedback: IFeedback): void { + submitFeedback(feedback: IFeedback, openerService: IOpenerService): void { const queryString = `?${feedback.sentiment === 1 ? `hashtags=${this.combineHashTagsAsString()}&` : null}ref_src=twsrc%5Etfw&related=twitterapi%2Ctwitter&text=${encodeURIComponent(feedback.feedback)}&tw_p=tweetbutton&via=${TwitterFeedbackService.VIA_NAME}`; const url = TwitterFeedbackService.TWITTER_URL + queryString; - window.open(url); + openerService.open(URI.parse(url)); } getCharacterLimit(sentiment: number): number { @@ -47,8 +49,8 @@ class TwitterFeedbackService implements IFeedbackDelegate { } export class FeedbackStatusbarConribution extends Disposable implements IWorkbenchContribution { - private dropdown: FeedbackDropdown; - private entry: IStatusbarEntryAccessor; + private dropdown: FeedbackDropdown | undefined; + private entry: IStatusbarEntryAccessor | undefined; constructor( @IStatusbarService statusbarService: IStatusbarService, @@ -72,15 +74,17 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben this.dropdown = this._register(this.instantiationService.createInstance(FeedbackDropdown, statusContainr.getElementsByClassName('octicon').item(0), { contextViewProvider: this.contextViewService, feedbackService: this.instantiationService.createInstance(TwitterFeedbackService), - onFeedbackVisibilityChange: visible => this.entry.update(this.getStatusEntry(visible)) + onFeedbackVisibilityChange: visible => this.entry!.update(this.getStatusEntry(visible)) })); } } - if (!this.dropdown.isVisible()) { - this.dropdown.show(); - } else { - this.dropdown.hide(); + if (this.dropdown) { + if (!this.dropdown.isVisible()) { + this.dropdown.show(); + } else { + this.dropdown.hide(); + } } } @@ -92,4 +96,4 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben }; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 580a2990ac4..9c0808d9ad1 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -7,16 +7,15 @@ import * as nls from 'vs/nls'; import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { URI } from 'vs/base/common/uri'; import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; /** * An implementation of editor for binary files like images. @@ -28,7 +27,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { constructor( @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, - @IWindowsService private readonly windowsService: IWindowsService, + @IOpenerService private readonly openerService: IOpenerService, @IEditorService private readonly editorService: IEditorService, @IStorageService storageService: IStorageService, @IFileService fileService: IFileService, @@ -39,7 +38,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { BinaryFileEditor.ID, { openInternal: (input, options) => this.openInternal(input, options), - openExternal: resource => this.openExternal(resource) + openExternal: resource => this.openerService.open(resource, { openExternal: true }) }, telemetryService, themeService, @@ -58,13 +57,6 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { } } - private async openExternal(resource: URI): Promise { - const didOpen = await this.windowsService.openExternal(resource.toString()); - if (!didOpen) { - return this.windowsService.showItemInFolder(resource); - } - } - getTitle(): string | null { return this.input ? this.input.getName() : nls.localize('binaryFileEditor', "Binary File Viewer"); } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index c04d6638e87..76c18cda35c 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -30,7 +30,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { - private closeOnFileDelete: boolean; + private closeOnFileDelete: boolean | undefined; private modelLoadQueue = new ResourceQueue(); private activeOutOfWorkspaceWatchers = new ResourceMap(); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 1021cb40834..c52724a2e64 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -40,7 +40,7 @@ export class TextFileEditor extends BaseTextEditor { static readonly ID = TEXT_FILE_EDITOR_ID; - private restoreViewState: boolean; + private restoreViewState: boolean | undefined; private readonly groupListener = this._register(new MutableDisposable()); constructor( diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 31cd7cb1006..c9713c80989 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -34,10 +34,11 @@ import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { withUndefinedAsNull } from 'vs/base/common/types'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { - private openEditorsVisibleContextKey: IContextKey; + private openEditorsVisibleContextKey!: IContextKey; constructor( @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @@ -202,7 +203,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { openEditorsView.setStructuralRefreshDelay(delay); } - let openedEditor: IEditor | null = null; + let openedEditor: IEditor | undefined; try { openedEditor = await this.editorService.openEditor(editor, options, group); } catch (error) { @@ -214,7 +215,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { } } - return openedEditor; + return withUndefinedAsNull(openedEditor); }); const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IEditorService, delegatingEditorService])); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 8b29304e715..40a0ec28bf2 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, ShowOpenedFileInNewWindow, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileActions'; +import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, ShowOpenedFileInNewWindow, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler, DOWNLOAD_COMMAND_ID, openFilePreserveFocusHandler } from 'vs/workbench/contrib/files/browser/fileActions'; import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/saveErrorHandler'; import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; @@ -186,6 +186,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'filesExplorer.openFilePreserveFocus', + weight: KeybindingWeight.WorkbenchContrib + explorerCommandsWeightBonus, + when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerFolderContext.toNegated()), + primary: KeyCode.Space, + handler: openFilePreserveFocusHandler +}); + const copyPathCommand = { id: COPY_PATH_COMMAND_ID, title: nls.localize('copyPath', "Copy Path") diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 4bde241c886..e204c90c0bc 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -15,7 +15,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as strings from 'vs/base/common/strings'; import { Action } from 'vs/base/common/actions'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID, IExplorerService, IFilesConfiguration } 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, SideBySideEditor } from 'vs/workbench/common/editor'; @@ -44,7 +44,7 @@ import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/e import { coalesce } from 'vs/base/common/arrays'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { onUnexpectedError, getErrorMessage } from 'vs/base/common/errors'; export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -328,7 +328,7 @@ function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean } -export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwrite: boolean }): URI { +export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwrite: boolean }, incrementalNaming: 'simple' | 'smart'): URI { let name = resources.basenameOrAuthority(fileToPaste.resource); let candidate = resources.joinPath(targetFolder.resource, name); @@ -337,37 +337,104 @@ export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste break; } - name = incrementFileName(name, !!fileToPaste.isDirectory); + name = incrementFileName(name, !!fileToPaste.isDirectory, incrementalNaming); candidate = resources.joinPath(targetFolder.resource, name); } return candidate; } -export function incrementFileName(name: string, isFolder: boolean): string { - let namePrefix = name; - let extSuffix = ''; - if (!isFolder) { - extSuffix = extname(name); - namePrefix = basename(name, extSuffix); +export function incrementFileName(name: string, isFolder: boolean, incrementalNaming: 'simple' | 'smart'): string { + if (incrementalNaming === 'simple') { + let namePrefix = name; + let extSuffix = ''; + if (!isFolder) { + extSuffix = extname(name); + namePrefix = basename(name, extSuffix); + } + + // name copy 5(.txt) => name copy 6(.txt) + // name copy(.txt) => name copy 2(.txt) + const suffixRegex = /^(.+ copy)( \d+)?$/; + if (suffixRegex.test(namePrefix)) { + return namePrefix.replace(suffixRegex, (match, g1?, g2?) => { + let number = (g2 ? parseInt(g2) : 1); + return number === 0 + ? `${g1}` + : (number < Constants.MAX_SAFE_SMALL_INTEGER + ? `${g1} ${number + 1}` + : `${g1}${g2} copy`); + }) + extSuffix; + } + + // name(.txt) => name copy(.txt) + return `${namePrefix} copy${extSuffix}`; } - // name copy 5(.txt) => name copy 6(.txt) - // name copy(.txt) => name copy 2(.txt) - const suffixRegex = /^(.+ copy)( \d+)?$/; - if (suffixRegex.test(namePrefix)) { - return namePrefix.replace(suffixRegex, (match, g1?, g2?) => { - let number = (g2 ? parseInt(g2) : 1); - return number === 0 - ? `${g1}` - : (number < Constants.MAX_SAFE_SMALL_INTEGER - ? `${g1} ${number + 1}` - : `${g1}${g2} copy`); - }) + extSuffix; + const separators = '[\\.\\-_]'; + const maxNumber = Constants.MAX_SAFE_SMALL_INTEGER; + + // file.1.txt=>file.2.txt + let suffixFileRegex = RegExp('(.*' + separators + ')(\\d+)(\\..*)$'); + if (!isFolder && name.match(suffixFileRegex)) { + return name.replace(suffixFileRegex, (match, g1?, g2?, g3?) => { + let number = parseInt(g2); + return number < maxNumber + ? g1 + strings.pad(number + 1, g2.length) + g3 + : strings.format('{0}{1}.1{2}', g1, g2, g3); + }); } - // name(.txt) => name copy(.txt) - return `${namePrefix} copy${extSuffix}`; + // 1.file.txt=>2.file.txt + let prefixFileRegex = RegExp('(\\d+)(' + separators + '.*)(\\..*)$'); + if (!isFolder && name.match(prefixFileRegex)) { + return name.replace(prefixFileRegex, (match, g1?, g2?, g3?) => { + let number = parseInt(g1); + return number < maxNumber + ? strings.pad(number + 1, g1.length) + g2 + g3 + : strings.format('{0}{1}.1{2}', g1, g2, g3); + }); + } + + // 1.txt=>2.txt + let prefixFileNoNameRegex = RegExp('(\\d+)(\\..*)$'); + if (!isFolder && name.match(prefixFileNoNameRegex)) { + return name.replace(prefixFileNoNameRegex, (match, g1?, g2?) => { + let number = parseInt(g1); + return number < maxNumber + ? strings.pad(number + 1, g1.length) + g2 + : strings.format('{0}.1{1}', g1, g2); + }); + } + + // file.txt=>file.1.txt + const lastIndexOfDot = name.lastIndexOf('.'); + if (!isFolder && lastIndexOfDot >= 0) { + return strings.format('{0}.1{1}', name.substr(0, lastIndexOfDot), name.substr(lastIndexOfDot)); + } + + // folder.1=>folder.2 + if (isFolder && name.match(/(\d+)$/)) { + return name.replace(/(\d+)$/, (match: string, ...groups: any[]) => { + let number = parseInt(groups[0]); + return number < maxNumber + ? strings.pad(number + 1, groups[0].length) + : strings.format('{0}.1', groups[0]); + }); + } + + // 1.folder=>2.folder + if (isFolder && name.match(/^(\d+)/)) { + return name.replace(/^(\d+)(.*)$/, (match: string, ...groups: any[]) => { + let number = parseInt(groups[0]); + return number < maxNumber + ? strings.pad(number + 1, groups[0].length) + groups[1] + : strings.format('{0}{1}.1', groups[0], groups[1]); + }); + } + + // file/folder=>file.1/folder.1 + return strings.format('{0}.1', name); } // Global Compare with @@ -404,7 +471,7 @@ export class GlobalCompareResourcesAction extends Action { override: this.editorService.openEditor({ leftResource: activeResource, rightResource: resource - }).then(() => null) + }).then(() => undefined) }; } @@ -1006,6 +1073,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { const textFileService = accessor.get(ITextFileService); const notificationService = accessor.get(INotificationService); const editorService = accessor.get(IEditorService); + const configurationService = accessor.get(IConfigurationService); if (listService.lastFocusedList) { const explorerContext = getContext(listService.lastFocusedList); @@ -1030,7 +1098,8 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { target = element.isDirectory ? element : element.parent!; } - const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }); + const incrementalNaming = configurationService.getValue().explorer.incrementalNaming; + const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming); // Move/Copy File if (pasteShouldMove) { @@ -1039,7 +1108,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { return await fileService.copy(fileToPaste, targetFile); } } catch (e) { - onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile"))); + onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile. {0}", getErrorMessage(e)))); return undefined; } })); @@ -1059,3 +1128,19 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { } } }; + +export const openFilePreserveFocusHandler = async (accessor: ServicesAccessor) => { + const listService = accessor.get(IListService); + const editorService = accessor.get(IEditorService); + + if (listService.lastFocusedList) { + const explorerContext = getContext(listService.lastFocusedList); + if (explorerContext.stat) { + const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; + await editorService.openEditors(stats.filter(s => !s.isDirectory).map(s => ({ + resource: s.resource, + options: { preserveFocus: true } + }))); + } + } +}; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index e94990d2390..384d6735830 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -37,7 +37,6 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IEditorService, SIDE_GROUP } 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 { onUnexpectedError } from 'vs/base/common/errors'; import { basename, toLocalResource, joinPath } from 'vs/base/common/resources'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -106,7 +105,7 @@ export const newWindowCommand = (accessor: ServicesAccessor, options?: INewWindo windowsService.openNewWindow(options); }; -function save( +async function save( resource: URI | null, isSaveAs: boolean, options: ISaveOptions | undefined, @@ -117,99 +116,110 @@ function save( editorGroupService: IEditorGroupsService, environmentService: IWorkbenchEnvironmentService ): Promise { - - function ensureForcedSave(options?: ISaveOptions): ISaveOptions { - if (!options) { - options = { force: true }; - } else { - options.force = true; - } - - return options; + if (!resource || (!fileService.canHandleResource(resource) && resource.scheme !== Schemas.untitled)) { + return; // save is not supported } - if (resource && (fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { - - // Save As (or Save untitled with associated path) - if (isSaveAs || resource.scheme === Schemas.untitled) { - let encodingOfSource: string | undefined; - if (resource.scheme === Schemas.untitled) { - encodingOfSource = untitledEditorService.getEncoding(resource); - } else if (fileService.canHandleResource(resource)) { - const textModel = textFileService.models.get(resource); - encodingOfSource = textModel && textModel.getEncoding(); // text model can be null e.g. if this is a binary file! - } - - let viewStateOfSource: IEditorViewState | null; - const activeTextEditorWidget = getCodeEditor(editorService.activeTextEditorWidget); - if (activeTextEditorWidget) { - const activeResource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); - if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && activeResource.toString() === resource.toString()) { - viewStateOfSource = activeTextEditorWidget.saveViewState(); - } - } - - // Special case: an untitled file with associated path gets saved directly unless "saveAs" is true - let savePromise: Promise; - if (!isSaveAs && resource.scheme === Schemas.untitled && untitledEditorService.hasAssociatedFilePath(resource)) { - savePromise = textFileService.save(resource, options).then(result => { - if (result) { - return toLocalResource(resource, environmentService.configuration.remoteAuthority); - } - - return undefined; - }); - } - - // Otherwise, really "Save As..." - else { - - // Force a change to the file to trigger external watchers if any - // fixes https://github.com/Microsoft/vscode/issues/59655 - options = ensureForcedSave(options); - - savePromise = textFileService.saveAs(resource, undefined, options); - } - - return savePromise.then(target => { - if (!target || target.toString() === resource.toString()) { - return false; // save canceled or same resource used - } - - const replacement: IResourceInput = { - resource: target, - encoding: encodingOfSource, - options: { - pinned: true, - viewState: viewStateOfSource || undefined - } - }; - - return Promise.all(editorGroupService.groups.map(g => - editorService.replaceEditors([{ - editor: { resource }, - replacement - }], g))).then(() => true); - }); - } - - // Pin the active editor if we are saving it - const activeControl = editorService.activeControl; - const activeEditorResource = activeControl && activeControl.input && activeControl.input.getResource(); - if (activeControl && activeEditorResource && activeEditorResource.toString() === resource.toString()) { - activeControl.group.pinEditor(activeControl.input); - } - - // Just save (force a change to the file to trigger external watchers if any) - options = ensureForcedSave(options); - - return textFileService.save(resource, options); + // Save As (or Save untitled with associated path) + if (isSaveAs || resource.scheme === Schemas.untitled) { + return doSaveAs(resource, isSaveAs, options, editorService, fileService, untitledEditorService, textFileService, editorGroupService, environmentService); } - return Promise.resolve(false); + // Save + return doSave(resource, options, editorService, textFileService); } -function saveAll(saveAllArguments: any, editorService: IEditorService, untitledEditorService: IUntitledEditorService, +async function doSaveAs( + resource: URI, + isSaveAs: boolean, + options: ISaveOptions | undefined, + editorService: IEditorService, + fileService: IFileService, + untitledEditorService: IUntitledEditorService, + textFileService: ITextFileService, + editorGroupService: IEditorGroupsService, + environmentService: IWorkbenchEnvironmentService +): Promise { + let viewStateOfSource: IEditorViewState | null = null; + const activeTextEditorWidget = getCodeEditor(editorService.activeTextEditorWidget); + if (activeTextEditorWidget) { + const activeResource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); + if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && activeResource.toString() === resource.toString()) { + viewStateOfSource = activeTextEditorWidget.saveViewState(); + } + } + + // Special case: an untitled file with associated path gets saved directly unless "saveAs" is true + let target: URI | undefined; + if (!isSaveAs && resource.scheme === Schemas.untitled && untitledEditorService.hasAssociatedFilePath(resource)) { + const result = await textFileService.save(resource, options); + if (result) { + target = toLocalResource(resource, environmentService.configuration.remoteAuthority); + } + } + + // Otherwise, really "Save As..." + else { + + // Force a change to the file to trigger external watchers if any + // fixes https://github.com/Microsoft/vscode/issues/59655 + options = ensureForcedSave(options); + + target = await textFileService.saveAs(resource, undefined, options); + } + + if (!target || target.toString() === resource.toString()) { + return false; // save canceled or same resource used + } + + const replacement: IResourceInput = { + resource: target, + options: { + pinned: true, + viewState: viewStateOfSource || undefined + } + }; + + await Promise.all(editorGroupService.groups.map(group => + editorService.replaceEditors([{ + editor: { resource }, + replacement + }], group))); + + return true; +} + +async function doSave( + resource: URI, + options: ISaveOptions | undefined, + editorService: IEditorService, + textFileService: ITextFileService +): Promise { + + // Pin the active editor if we are saving it + const activeControl = editorService.activeControl; + const activeEditorResource = activeControl && activeControl.input && activeControl.input.getResource(); + if (activeControl && activeEditorResource && activeEditorResource.toString() === resource.toString()) { + activeControl.group.pinEditor(activeControl.input); + } + + // Just save (force a change to the file to trigger external watchers if any) + options = ensureForcedSave(options); + + return textFileService.save(resource, options); +} + +function ensureForcedSave(options?: ISaveOptions): ISaveOptions { + if (!options) { + options = { force: true }; + } else { + options.force = true; + } + + return options; +} + +async function saveAll(saveAllArguments: any, editorService: IEditorService, untitledEditorService: IUntitledEditorService, textFileService: ITextFileService, editorGroupService: IEditorGroupsService): Promise { // Store some properties per untitled file to restore later after save is completed @@ -239,17 +249,18 @@ function saveAll(saveAllArguments: any, editorService: IEditorService, untitledE }); // Save all - return textFileService.saveAll(saveAllArguments).then(result => { - groupIdToUntitledResourceInput.forEach((inputs, groupId) => { - // Update untitled resources to the saved ones, so we open the proper files - inputs.forEach(i => { - const targetResult = result.results.filter(r => r.success && r.source.toString() === i.resource.toString()).pop(); - if (targetResult && targetResult.target) { - i.resource = targetResult.target; - } - }); - editorService.openEditors(inputs, groupId); + const result = await textFileService.saveAll(saveAllArguments); + + // Update untitled resources to the saved ones, so we open the proper files + groupIdToUntitledResourceInput.forEach((inputs, groupId) => { + inputs.forEach(i => { + const targetResult = result.results.filter(r => r.success && r.source.toString() === i.resource.toString()).pop(); + if (targetResult && targetResult.target) { + i.resource = targetResult.target; + } }); + + editorService.openEditors(inputs, groupId); }); } @@ -257,7 +268,7 @@ function saveAll(saveAllArguments: any, editorService: IEditorService, untitledE CommandsRegistry.registerCommand({ id: REVERT_FILE_COMMAND_ID, - handler: (accessor, resource: URI | object) => { + handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); const textFileService = accessor.get(ITextFileService); const notificationService = accessor.get(INotificationService); @@ -265,12 +276,12 @@ CommandsRegistry.registerCommand({ .filter(resource => resource.scheme !== Schemas.untitled); if (resources.length) { - return textFileService.revertAll(resources, { force: true }).then(undefined, error => { + try { + await textFileService.revertAll(resources, { force: true }); + } catch (error) { notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", resources.map(r => basename(r)).join(', '), toErrorMessage(error, false))); - }); + } } - - return Promise.resolve(true); } }); @@ -281,7 +292,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, - id: OPEN_TO_SIDE_COMMAND_ID, handler: (accessor, resource: URI | object) => { + id: OPEN_TO_SIDE_COMMAND_ID, handler: async (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); const listService = accessor.get(IListService); const fileService = accessor.get(IFileService); @@ -289,16 +300,13 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Set side input if (resources.length) { - return fileService.resolveAll(resources.map(resource => ({ resource }))).then(resolved => { - const editors = resolved.filter(r => r.stat && r.success && !r.stat.isDirectory).map(r => ({ - resource: r.stat!.resource - })); + const resolved = await fileService.resolveAll(resources.map(resource => ({ resource }))); + const editors = resolved.filter(r => r.stat && r.success && !r.stat.isDirectory).map(r => ({ + resource: r.stat!.resource + })); - return editorService.openEditors(editors, SIDE_GROUP); - }); + await editorService.openEditors(editors, SIDE_GROUP); } - - return Promise.resolve(true); } }); @@ -393,7 +401,7 @@ CommandsRegistry.registerCommand({ editorService.openEditor({ leftResource: globalResourceToCompare, rightResource - }).then(undefined, onUnexpectedError); + }); } } }); @@ -492,27 +500,28 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ CommandsRegistry.registerCommand({ id: REVEAL_IN_EXPLORER_COMMAND_ID, - handler: (accessor, resource: URI | object) => { + handler: async (accessor, resource: URI | object) => { const viewletService = accessor.get(IViewletService); const contextService = accessor.get(IWorkspaceContextService); const explorerService = accessor.get(IExplorerService); const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); - viewletService.openViewlet(VIEWLET_ID, false).then((viewlet: ExplorerViewlet) => { - if (uri && contextService.isInsideWorkspace(uri)) { - const explorerView = viewlet.getExplorerView(); - if (explorerView) { - explorerView.setExpanded(true); - explorerService.select(uri, true).then(() => explorerView.focus(), onUnexpectedError); - } - } else { - const openEditorsView = viewlet.getOpenEditorsView(); - if (openEditorsView) { - openEditorsView.setExpanded(true); - openEditorsView.focus(); - } + const viewlet = await viewletService.openViewlet(VIEWLET_ID, false) as ExplorerViewlet; + + if (uri && contextService.isInsideWorkspace(uri)) { + const explorerView = viewlet.getExplorerView(); + if (explorerView) { + explorerView.setExpanded(true); + await explorerService.select(uri, true); + explorerView.focus(); } - }); + } else { + const openEditorsView = viewlet.getOpenEditorsView(); + if (openEditorsView) { + openEditorsView.setExpanded(true); + openEditorsView.focus(); + } + } } }); @@ -578,7 +587,7 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: SAVE_ALL_IN_GROUP_COMMAND_ID, - handler: (accessor, resource: URI | object, editorContext: IEditorCommandsContext) => { + handler: (accessor, _: URI | object, editorContext: IEditorCommandsContext) => { const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService), accessor.get(IEditorGroupsService)); const editorGroupService = accessor.get(IEditorGroupsService); let saveAllArg: any; diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index b219a608bd2..19fcd5b0acb 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -426,6 +426,15 @@ configurationRegistry.registerConfiguration({ description: nls.localize('explorer.decorations.badges', "Controls whether file decorations should use badges."), default: true }, + 'explorer.incrementalNaming': { + enum: ['simple', 'smart'], + enumDescriptions: [ + nls.localize('simple', "Appends the word \"copy\" at the end of the duplicated name potentially followed by a number"), + nls.localize('smart', "Adds a number at the end of the duplicated name. If some number is already part of the name, tries to increase that number") + ], + description: nls.localize('explorer.incrementalNaming', "Controls what naming strategy to use when a giving a new name to a duplicated explorer item on paste."), + default: 'simple' + } } }); diff --git a/src/vs/workbench/contrib/files/browser/media/check-dark.svg b/src/vs/workbench/contrib/files/browser/media/check-dark.svg index 865cc83c347..51674695e1f 100644 --- a/src/vs/workbench/contrib/files/browser/media/check-dark.svg +++ b/src/vs/workbench/contrib/files/browser/media/check-dark.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/files/browser/media/check-light.svg b/src/vs/workbench/contrib/files/browser/media/check-light.svg index e1a546660ed..7b1da6d7208 100644 --- a/src/vs/workbench/contrib/files/browser/media/check-light.svg +++ b/src/vs/workbench/contrib/files/browser/media/check-light.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts index ac09186d046..608638b5178 100644 --- a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts @@ -236,7 +236,6 @@ class ResolveSaveConflictAction extends Action { @IEditorService private readonly editorService: IEditorService, @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IStorageService private readonly storageService: IStorageService, @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super('workbench.files.action.resolveConflict', nls.localize('compareChanges', "Compare")); @@ -250,21 +249,15 @@ class ResolveSaveConflictAction extends Action { await TextFileContentProvider.open(resource, CONFLICT_RESOLUTION_SCHEME, editorLabel, this.editorService, { pinned: true }); - if (this.storageService.getBoolean(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, StorageScope.GLOBAL)) { - return; // return if this message is ignored - } - // Show additional help how to resolve the save conflict - const primaryActions: IAction[] = [ - this.instantiationService.createInstance(ResolveConflictLearnMoreAction) - ]; - const secondaryActions: IAction[] = [ - this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction) - ]; - - const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; - const handle = this.notificationService.notify({ severity: Severity.Info, message: conflictEditorHelp, actions }); - Event.once(handle.onDidClose)(() => { dispose(primaryActions); dispose(secondaryActions); }); + const actions: INotificationActions = { primary: [this.instantiationService.createInstance(ResolveConflictLearnMoreAction)] }; + const handle = this.notificationService.notify({ + severity: Severity.Info, + message: conflictEditorHelp, + actions, + neverShowAgain: { id: LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, isSecondary: true } + }); + Event.once(handle.onDidClose)(() => dispose(actions.primary!)); pendingResolveSaveConflictMessages.push(handle); } diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index fddb247aa63..e7e9ecf5f61 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -32,9 +32,9 @@ export class EmptyView extends ViewletPanel { static readonly ID: string = 'workbench.explorer.emptyView'; static readonly NAME = nls.localize('noWorkspace', "No Folder Opened"); - private button: Button; - private messageElement: HTMLElement; - private titleElement: HTMLElement; + private button!: Button; + private messageElement!: HTMLElement; + private titleElement!: HTMLElement; constructor( options: IViewletViewOptions, @@ -59,7 +59,6 @@ export class EmptyView extends ViewletPanel { container.appendChild(titleContainer); this.titleElement = document.createElement('span'); - this.titleElement.textContent = name; titleContainer.appendChild(this.titleElement); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 9b464d453c9..c5bd34e7ac6 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -55,8 +55,8 @@ export class ExplorerView extends ViewletPanel { static readonly ID: string = 'workbench.explorer.fileView'; static readonly TREE_VIEW_STATE_STORAGE_KEY: string = 'workbench.explorer.treeViewState'; - private tree: WorkbenchAsyncDataTree; - private filter: FilesFilter; + private tree!: WorkbenchAsyncDataTree; + private filter!: FilesFilter; private resourceContext: ResourceContextKey; private folderContext: IContextKey; @@ -66,7 +66,7 @@ export class ExplorerView extends ViewletPanel { // Refresh is needed on the initial explorer open private shouldRefresh = true; - private dragHandler: DelayedDragHandler; + private dragHandler!: DelayedDragHandler; private autoReveal = false; constructor( @@ -301,7 +301,8 @@ export class ExplorerView extends ViewletPanel { filter: this.filter, sorter: this.instantiationService.createInstance(FileSorter), dnd: this.instantiationService.createInstance(FileDragAndDrop), - autoExpandSingleChildren: true + autoExpandSingleChildren: true, + additionalScrollHeight: ExplorerDelegate.ITEM_HEIGHT }); this._register(this.tree); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 6543070e81c..d8d9f52f320 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -49,7 +49,7 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; export class ExplorerDelegate implements IListVirtualDelegate { - private static readonly ITEM_HEIGHT = 22; + static readonly ITEM_HEIGHT = 22; getHeight(element: ExplorerItem): number { return ExplorerDelegate.ITEM_HEIGHT; @@ -218,56 +218,46 @@ export class FilesRenderer implements ITreeRenderer 0 && !stat.isDirectory ? lastDot : value.length }); - let isFinishableDisposeEvent = false; - setTimeout(() => { - // Check if disposed - if (!inputBox.inputElement) { - return; - } - inputBox.focus(); - inputBox.select({ start: 0, end: lastDot > 0 && !stat.isDirectory ? lastDot : value.length }); - isFinishableDisposeEvent = true; - }, 0); - - const done = once(async (success: boolean) => { + const done = once(async (success: boolean, blur: boolean) => { label.element.style.display = 'none'; const value = inputBox.value; dispose(toDispose); - label.element.remove(); - // Timeout: once done rendering only then re-render #70902 - setTimeout(() => editableData.onFinish(value, success), 0); + container.removeChild(label.element); + editableData.onFinish(value, success); }); - const blurDisposable = DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { - done(inputBox.isInputValid()); - }); + // It can happen that the tree re-renders this node. When that happens, + // we're gonna get a blur event first and only after an element disposable. + // Because of that, we should setTimeout the blur handler to differentiate + // between the blur happening because of a unrender or because of a user action. + let ignoreBlur = false; const toDispose = [ inputBox, DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => { if (e.equals(KeyCode.Enter)) { if (inputBox.validate()) { - done(true); + done(true, false); } } else if (e.equals(KeyCode.Escape)) { - done(false); + done(false, false); } }), - blurDisposable, + DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { + setTimeout(() => { + if (!ignoreBlur) { + done(inputBox.isInputValid(), true); + } + }, 0); + }), label, styler ]; - return toDisposable(() => { - if (isFinishableDisposeEvent) { - done(false); - } - else { - dispose(toDispose); - label.element.remove(); - } - }); + return toDisposable(() => ignoreBlur = true); } disposeElement?(element: ITreeNode, index: number, templateData: IFileTemplateData): void { @@ -440,7 +430,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { private static readonly CONFIRM_DND_SETTING_KEY = 'explorer.confirmDragAndDrop'; private toDispose: IDisposable[]; - private dropEnabled: boolean; + private dropEnabled = false; constructor( @INotificationService private notificationService: INotificationService, @@ -808,8 +798,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop { private doHandleExplorerDrop(source: ExplorerItem, target: ExplorerItem, isCopy: boolean): Promise { // Reuse duplicate action if user copies if (isCopy) { - - return this.fileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false })).then(stat => { + const incrementalNaming = this.configurationService.getValue().explorer.incrementalNaming; + return this.fileService.copy(source.resource, findValidPasteFileTarget(target, { resource: source.resource, isDirectory: source.isDirectory, allowOverwrite: false }, incrementalNaming)).then(stat => { if (!stat.isDirectory) { return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }).then(() => undefined); } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 893ea55f5be..96be5e2659b 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -51,16 +51,16 @@ export class OpenEditorsView extends ViewletPanel { static readonly ID = 'workbench.explorer.openEditorsView'; static NAME = nls.localize({ key: 'openEditors', comment: ['Open is an adjective'] }, "Open Editors"); - private dirtyCountElement: HTMLElement; + private dirtyCountElement!: HTMLElement; private listRefreshScheduler: RunOnceScheduler; private structuralRefreshDelay: number; - private list: WorkbenchList; - private listLabels: ResourceLabels; - private contributedContextMenu: IMenu; - private needsRefresh: boolean; - private resourceContext: ResourceContextKey; - private groupFocusedContext: IContextKey; - private dirtyEditorFocusedContext: IContextKey; + private list!: WorkbenchList; + private listLabels: ResourceLabels | undefined; + private contributedContextMenu!: IMenu; + private needsRefresh = false; + private resourceContext!: ResourceContextKey; + private groupFocusedContext!: IContextKey; + private dirtyEditorFocusedContext!: IContextKey; constructor( options: IViewletViewOptions, @@ -129,7 +129,7 @@ export class OpenEditorsView extends ViewletPanel { const index = this.getIndex(group, e.editor); switch (e.kind) { - case GroupChangeKind.GROUP_LABEL: { + case GroupChangeKind.GROUP_INDEX: { if (this.showGroups) { this.list.splice(index, 1, [group]); } @@ -466,9 +466,13 @@ interface IEditorGroupTemplateData { } class OpenEditorActionRunner extends ActionRunner { - public editor: OpenEditor; + public editor: OpenEditor | undefined; run(action: IAction, context?: any): Promise { + if (!this.editor) { + return Promise.resolve(); + } + return super.run(action, { groupId: this.editor.groupId, editorIndex: this.editor.editorIndex }); } } diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts index 49ef8b18505..20c46e13777 100644 --- a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts +++ b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts @@ -19,7 +19,7 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo export class DirtyFilesTracker extends Disposable implements IWorkbenchContribution { private isDocumentedEdited: boolean; - private lastDirtyCount: number; + private lastKnownDirtyCount: number | undefined; private readonly badgeHandle = this._register(new MutableDisposable()); constructor( @@ -50,6 +50,10 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut this.lifecycleService.onShutdown(this.dispose, this); } + private get hasDirtyCount(): boolean { + return typeof this.lastKnownDirtyCount === 'number' && this.lastKnownDirtyCount > 0; + } + private onUntitledDidChangeDirty(resource: URI): void { const gotDirty = this.untitledEditorService.isDirty(resource); @@ -57,7 +61,7 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut this.updateDocumentEdited(); } - if (gotDirty || this.lastDirtyCount > 0) { + if (gotDirty || this.hasDirtyCount) { this.updateActivityBadge(); } } @@ -100,7 +104,7 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut this.updateDocumentEdited(); } - if (this.lastDirtyCount > 0) { + if (this.hasDirtyCount) { this.updateActivityBadge(); } } @@ -118,15 +122,17 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut this.updateDocumentEdited(); } - if (this.lastDirtyCount > 0) { + if (this.hasDirtyCount) { this.updateActivityBadge(); } } private updateActivityBadge(): void { const dirtyCount = this.textFileService.getDirty().length; - this.lastDirtyCount = dirtyCount; + this.lastKnownDirtyCount = dirtyCount; + this.badgeHandle.clear(); + if (dirtyCount > 0) { this.badgeHandle.value = this.activityService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); } diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 2d25923a2c2..3aae29a21be 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -203,17 +203,20 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { switch (verbosity) { case Verbosity.SHORT: title = this.shortTitle; + // already decorated by getName() break; default: case Verbosity.MEDIUM: title = this.mediumTitle; + title = this.decorateLabel(title); break; case Verbosity.LONG: title = this.longTitle; + title = this.decorateLabel(title); break; } - return this.decorateLabel(title); + return title; } private decorateLabel(label: string): string { diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 85c08590a5d..cf0172fd317 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -19,7 +19,7 @@ import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; export class ExplorerModel implements IDisposable { - private _roots: ExplorerItem[]; + private _roots!: ExplorerItem[]; private _listener: IDisposable; private _onDidChangeRoots = new Emitter(); @@ -75,7 +75,7 @@ export class ExplorerModel implements IDisposable { export class ExplorerItem { private _isDirectoryResolved: boolean; - public isError: boolean; + public isError = false; constructor( public resource: URI, @@ -148,7 +148,7 @@ export class ExplorerItem { return this === this.root; } - static create(raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: URI[]): ExplorerItem { + static create(raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { const stat = new ExplorerItem(raw.resource, parent, raw.isDirectory, raw.isSymbolicLink, raw.isReadonly, raw.name, raw.mtime); // Recursively add children if present @@ -367,4 +367,4 @@ export class NewExplorerItem extends ExplorerItem { constructor(parent: ExplorerItem, isDirectory: boolean) { super(URI.file(''), parent, isDirectory); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 9d0f177a77a..aff4390570c 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -158,7 +158,11 @@ export class ExplorerService implements IExplorerService { // Stat needs to be resolved first and then revealed const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === 'modified' }; const workspaceFolder = this.contextService.getWorkspaceFolder(resource); - const rootUri = workspaceFolder ? workspaceFolder.uri : this.roots[0].resource; + if (workspaceFolder === null) { + return Promise.resolve(undefined); + } + const rootUri = workspaceFolder.uri; + const root = this.roots.filter(r => r.resource.toString() === rootUri.toString()).pop()!; try { diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index e60fda6cf31..342088011f5 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -114,6 +114,7 @@ export interface IFilesConfiguration extends IFilesConfiguration, IWorkbenchEdit colors: boolean; badges: boolean; }; + incrementalNaming: 'simple' | 'smart'; }; editor: IEditorOptions; } diff --git a/src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts b/src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts index d52b99bb2d9..bf0642cde5c 100644 --- a/src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts +++ b/src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts @@ -6,132 +6,292 @@ import * as assert from 'assert'; import { incrementFileName } from 'vs/workbench/contrib/files/browser/fileActions'; -suite('Files - Increment file name', () => { +suite('Files - Increment file name simple', () => { test('Increment file name without any version', function () { const name = 'test.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy.js'); }); test('Increment file name with suffix version', function () { const name = 'test copy.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy 2.js'); }); test('Increment file name with suffix version with leading zeros', function () { const name = 'test copy 005.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy 6.js'); }); test('Increment file name with suffix version, too big number', function () { const name = 'test copy 9007199254740992.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy 9007199254740992 copy.js'); }); test('Increment file name with just version in name', function () { const name = 'copy.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'copy copy.js'); }); test('Increment file name with just version in name, v2', function () { const name = 'copy 2.js'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'copy 2 copy.js'); }); test('Increment file name without any extension or version', function () { const name = 'test'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy'); }); test('Increment file name without any extension or version, trailing dot', function () { const name = 'test.'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy.'); }); test('Increment file name without any extension or version, leading dot', function () { const name = '.test'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, '.test copy'); }); test('Increment file name without any extension or version, leading dot v2', function () { const name = '..test'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, '. copy.test'); }); test('Increment file name without any extension but with suffix version', function () { const name = 'test copy 5'; - const result = incrementFileName(name, false); + const result = incrementFileName(name, false, 'simple'); assert.strictEqual(result, 'test copy 6'); }); test('Increment folder name without any version', function () { const name = 'test'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy'); }); test('Increment folder name with suffix version', function () { const name = 'test copy'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy 2'); }); test('Increment folder name with suffix version, leading zeros', function () { const name = 'test copy 005'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy 6'); }); test('Increment folder name with suffix version, too big number', function () { const name = 'test copy 9007199254740992'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy 9007199254740992 copy'); }); test('Increment folder name with just version in name', function () { const name = 'copy'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'copy copy'); }); test('Increment folder name with just version in name, v2', function () { const name = 'copy 2'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'copy 2 copy'); }); test('Increment folder name "with extension" but without any version', function () { const name = 'test.js'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test.js copy'); }); test('Increment folder name "with extension" and with suffix version', function () { const name = 'test.js copy 5'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test.js copy 6'); }); test('Increment file/folder name with suffix version, special case 1', function () { const name = 'test copy 0'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy'); }); test('Increment file/folder name with suffix version, special case 2', function () { const name = 'test copy 1'; - const result = incrementFileName(name, true); + const result = incrementFileName(name, true, 'simple'); assert.strictEqual(result, 'test copy 2'); }); }); + +suite('Files - Increment file name smart', () => { + + test('Increment file name without any version', function () { + const name = 'test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test.1.js'); + }); + + test('Increment folder name without any version', function () { + const name = 'test'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test.1'); + }); + + test('Increment file name with suffix version', function () { + const name = 'test.1.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test.2.js'); + }); + + test('Increment file name with suffix version with trailing zeros', function () { + const name = 'test.001.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test.002.js'); + }); + + test('Increment file name with suffix version with trailing zeros, changing length', function () { + const name = 'test.009.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test.010.js'); + }); + + test('Increment file name with suffix version with `-` as separator', function () { + const name = 'test-1.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test-2.js'); + }); + + test('Increment file name with suffix version with `-` as separator, trailing zeros', function () { + const name = 'test-001.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test-002.js'); + }); + + test('Increment file name with suffix version with `-` as separator, trailing zeros, changnig length', function () { + const name = 'test-099.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test-100.js'); + }); + + test('Increment file name with suffix version with `_` as separator', function () { + const name = 'test_1.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test_2.js'); + }); + + test('Increment folder name with suffix version', function () { + const name = 'test.1'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test.2'); + }); + + test('Increment folder name with suffix version, trailing zeros', function () { + const name = 'test.001'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test.002'); + }); + + test('Increment folder name with suffix version with `-` as separator', function () { + const name = 'test-1'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test-2'); + }); + + test('Increment folder name with suffix version with `_` as separator', function () { + const name = 'test_1'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test_2'); + }); + + test('Increment file name with suffix version, too big number', function () { + const name = 'test.9007199254740992.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, 'test.9007199254740992.1.js'); + }); + + test('Increment folder name with suffix version, too big number', function () { + const name = 'test.9007199254740992'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, 'test.9007199254740992.1'); + }); + + test('Increment file name with prefix version', function () { + const name = '1.test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '2.test.js'); + }); + + test('Increment file name with just version in name', function () { + const name = '1.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '2.js'); + }); + + test('Increment file name with just version in name, too big number', function () { + const name = '9007199254740992.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '9007199254740992.1.js'); + }); + + test('Increment file name with prefix version, trailing zeros', function () { + const name = '001.test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '002.test.js'); + }); + + test('Increment file name with prefix version with `-` as separator', function () { + const name = '1-test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '2-test.js'); + }); + + test('Increment file name with prefix version with `-` as separator', function () { + const name = '1_test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '2_test.js'); + }); + + test('Increment file name with prefix version, too big number', function () { + const name = '9007199254740992.test.js'; + const result = incrementFileName(name, false, 'smart'); + assert.strictEqual(result, '9007199254740992.test.1.js'); + }); + + test('Increment folder name with prefix version', function () { + const name = '1.test'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, '2.test'); + }); + + test('Increment folder name with prefix version, too big number', function () { + const name = '9007199254740992.test'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, '9007199254740992.test.1'); + }); + + test('Increment folder name with prefix version, trailing zeros', function () { + const name = '001.test'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, '002.test'); + }); + + test('Increment folder name with prefix version with `-` as separator', function () { + const name = '1-test'; + const result = incrementFileName(name, true, 'smart'); + assert.strictEqual(result, '2-test'); + }); + +}); diff --git a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts index 3aa862c7599..1cb252b5e68 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts @@ -13,7 +13,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; import { WorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issueService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IIssueService } from 'vs/platform/issue/common/issue'; +import { IIssueService } from 'vs/platform/issue/node/issue'; const helpCategory = { value: nls.localize('help', "Help"), original: 'Help' }; const workbenchActionsRegistry = Registry.as(Extensions.WorkbenchActions); diff --git a/src/vs/workbench/contrib/issue/electron-browser/issue.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.ts index 2cc057f6bcc..ca98eb60aca 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issue.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issue.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IssueReporterData } from 'vs/platform/issue/common/issue'; +import { IssueReporterData } from 'vs/platform/issue/node/issue'; export const IWorkbenchIssueService = createDecorator('workbenchIssueService'); diff --git a/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts b/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts index 4eaaf7c3029..4b6b25c31c0 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts @@ -5,7 +5,7 @@ import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; -import { IssueType } from 'vs/platform/issue/common/issue'; +import { IssueType } from 'vs/platform/issue/node/issue'; import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; export class OpenProcessExplorer extends Action { diff --git a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts index e93eac14556..5dfea6f09da 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issueService.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IssueReporterStyles, IIssueService, IssueReporterData, ProcessExplorerData, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; +import { IssueReporterStyles, IIssueService, IssueReporterData, ProcessExplorerData, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { textLinkForeground, inputBackground, inputBorder, inputForeground, buttonBackground, buttonHoverBackground, buttonForeground, inputValidationErrorBorder, foreground, inputActiveOptionBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, editorBackground, editorForeground, listHoverBackground, listHoverForeground, listHighlightForeground, textLinkActiveForeground } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index 0ab7785ff31..1982d8aebc0 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -68,8 +68,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo } private onDidInstallExtension(e: DidInstallExtensionEvent): void { - const donotAskUpdateKey = 'langugage.update.donotask'; - if (!this.storageService.getBoolean(donotAskUpdateKey, StorageScope.GLOBAL) && e.local && e.operation === InstallOperation.Install && e.local.manifest.contributes && e.local.manifest.contributes.localizations && e.local.manifest.contributes.localizations.length) { + if (e.local && e.operation === InstallOperation.Install && e.local.manifest.contributes && e.local.manifest.contributes.localizations && e.local.manifest.contributes.localizations.length) { const locale = e.local.manifest.contributes.localizations[0].languageId; if (platform.language !== locale) { const updateAndRestart = platform.locale !== locale; @@ -83,12 +82,11 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo const updatePromise = updateAndRestart ? this.jsonEditingService.write(this.environmentService.localeResource, { key: 'locale', value: locale }, true) : Promise.resolve(undefined); updatePromise.then(() => this.windowsService.relaunch({}), e => this.notificationService.error(e)); } - }, { - label: localize('neverAgain', "Don't Show Again"), - isSecondary: true, - run: () => this.storageService.store(donotAskUpdateKey, true, StorageScope.GLOBAL) }], - { sticky: true } + { + sticky: true, + neverShowAgain: { id: 'langugage.update.donotask', isSecondary: true } + } ); } } @@ -302,4 +300,4 @@ ExtensionsRegistry.registerExtensionPoint({ } } } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts b/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts index ce1755e1f09..7c13cb5575e 100644 --- a/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts +++ b/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts @@ -56,8 +56,8 @@ class MarkersDecorationsProvider implements IDecorationsProvider { class MarkersFileDecorations implements IWorkbenchContribution { private readonly _disposables: IDisposable[]; - private _provider: IDisposable; - private _enabled: boolean; + private _provider?: IDisposable; + private _enabled?: boolean; constructor( @IMarkerService private readonly _markerService: IMarkerService, diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 71b13da1413..36f1b9bd365 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -534,8 +534,10 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { const span1 = dom.append(container, dom.$('span')); span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS; const link = dom.append(container, dom.$('a.messageAction')); - link.textContent = localize('clearFilter', "Clear Filter."); + link.textContent = localize('clearFilter', "Clear Filter"); link.setAttribute('tabIndex', '0'); + const span2 = dom.append(container, dom.$('span')); + span2.textContent = '.'; dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.filterText = ''); dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index ed087ccf8c0..156c52dd125 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -258,7 +258,7 @@ class MarkerWidget extends Disposable { })); this.icon = dom.append(parent, dom.$('')); this.multilineActionbar = this._register(new ActionBar(dom.append(parent, dom.$('.multiline-actions')))); - this.messageAndDetailsContainer = dom.append(parent, dom.$('.marker-message-details')); + this.messageAndDetailsContainer = dom.append(parent, dom.$('.marker-message-details-container')); } render(element: Marker, filterData: MarkerFilterData | undefined): void { @@ -312,33 +312,34 @@ class MarkerWidget extends Disposable { const viewState = this.markersViewModel.getViewModel(element); const multiline = !viewState || viewState.multiline; const lineMatches = filterData && filterData.lineMatches || []; - const messageContainer = dom.append(this.messageAndDetailsContainer, dom.$('.marker-message')); - dom.toggleClass(messageContainer, 'multiline', multiline); - let lastLineElement = messageContainer; + let lastLineElement: HTMLElement | undefined = undefined; for (let index = 0; index < (multiline ? lines.length : 1); index++) { - lastLineElement = dom.append(messageContainer, dom.$('.marker-message-line')); - const highlightedLabel = new HighlightedLabel(lastLineElement, false); - highlightedLabel.set(lines[index], lineMatches[index]); + lastLineElement = dom.append(this.messageAndDetailsContainer, dom.$('.marker-message-line')); + const messageElement = dom.append(lastLineElement, dom.$('.marker-message')); + const highlightedLabel = new HighlightedLabel(messageElement, false); + highlightedLabel.set(lines[index].length > 1000 ? `${lines[index].substring(0, 1000)}...` : lines[index], lineMatches[index]); if (lines[index] === '') { lastLineElement.style.height = `${VirtualDelegate.LINE_HEIGHT}px`; } } - this.renderDetails(marker, filterData, multiline ? lastLineElement : this.messageAndDetailsContainer); + this.renderDetails(marker, filterData, lastLineElement || dom.append(this.messageAndDetailsContainer, dom.$('.marker-message-line'))); } private renderDetails(marker: IMarker, filterData: MarkerFilterData | undefined, parent: HTMLElement): void { dom.addClass(parent, 'details-container'); - const sourceMatches = filterData && filterData.sourceMatches || []; - const codeMatches = filterData && filterData.codeMatches || []; - const source = new HighlightedLabel(dom.append(parent, dom.$('')), false); - source.set(marker.source, sourceMatches); - dom.toggleClass(source.element, 'marker-source', !!marker.source); + if (marker.source || marker.code) { + const source = new HighlightedLabel(dom.append(parent, dom.$('.marker-source')), false); + const sourceMatches = filterData && filterData.sourceMatches || []; + source.set(marker.source, sourceMatches); - const code = new HighlightedLabel(dom.append(parent, dom.$('')), false); - code.set(marker.code, codeMatches); - dom.toggleClass(code.element, 'marker-code', !!marker.code); + if (marker.code) { + const code = new HighlightedLabel(dom.append(parent, dom.$('.marker-code')), false); + const codeMatches = filterData && filterData.codeMatches || []; + code.set(marker.code, codeMatches); + } + } const lnCol = dom.append(parent, dom.$('span.marker-line')); lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(marker.startLineNumber, marker.startColumn); diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index 7cbfe050394..42321b8dba4 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -97,6 +97,7 @@ .markers-panel .markers-panel-container .tree-container .monaco-tl-contents { display: flex; line-height: 22px; + padding-right: 10px; } .hc-black .markers-panel .markers-panel-container .tree-container .monaco-tl-contents { @@ -112,23 +113,22 @@ margin-left: 10px; } -.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-message-details, -.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-message:not(.multiline) { - display: flex; +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-message-details-container { + flex: 1; overflow: hidden; } -.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-message.multiline { - white-space: pre; - flex: 1; +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-message-details-container > .marker-message-line { + overflow: hidden; } -.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-message:not(.multiline) .marker-message-line { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-message-details-container > .marker-message-line > .marker-message { overflow: hidden; text-overflow: ellipsis; + white-space: nowrap; } -.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-message .details-container { +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-message-details-container > .marker-message-line.details-container { display: flex; } @@ -222,4 +222,4 @@ .markers-panel .monaco-tl-contents .multiline-actions .action-item.disabled, .markers-panel .monaco-tl-contents .actions .action-item.disabled { display: none; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index fd070d880eb..90dfc9732bf 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -122,7 +122,8 @@ class RequestOracle { let handle: any; let contentListener = codeEditor.onDidChangeModelContent(event => { clearTimeout(handle); - handle = setTimeout(() => this._callback(codeEditor!, event), 350); + const timeout = OutlineModel.getRequestDelay(codeEditor!.getModel()); + handle = setTimeout(() => this._callback(codeEditor!, event), timeout); }); let modeListener = codeEditor.onDidChangeModelLanguage(_ => { this._callback(codeEditor!, undefined); @@ -238,14 +239,14 @@ export class OutlinePanel extends ViewletPanel { private _editorDisposables = new DisposableStore(); private _outlineViewState = new OutlineViewState(); private _requestOracle?: RequestOracle; - private _domNode: HTMLElement; - private _message: HTMLDivElement; - private _inputContainer: HTMLDivElement; - private _progressBar: ProgressBar; - private _tree: WorkbenchDataTree; - private _treeDataSource: OutlineDataSource; - private _treeRenderer: OutlineElementRenderer; - private _treeComparator: OutlineItemComparator; + private _domNode!: HTMLElement; + private _message!: HTMLDivElement; + private _inputContainer!: HTMLDivElement; + private _progressBar!: ProgressBar; + private _tree!: WorkbenchDataTree; + private _treeDataSource!: OutlineDataSource; + private _treeRenderer!: OutlineElementRenderer; + private _treeComparator!: OutlineItemComparator; private _treeStates = new LRUCache(10); private _treeFakeUIEvent = new UIEvent('me'); diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index e99147c8079..561891fc62e 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -122,8 +122,8 @@ export class SwitchOutputActionViewItem extends SelectActionViewItem { private static readonly SEPARATOR = '─────────'; - private outputChannels: IOutputChannelDescriptor[]; - private logChannels: IOutputChannelDescriptor[]; + private outputChannels: IOutputChannelDescriptor[] = []; + private logChannels: IOutputChannelDescriptor[] = []; constructor( action: IAction, @@ -267,4 +267,4 @@ export class OpenOutputLogFileAction extends Action { return undefined; }); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index 09365d68550..b63f819dcf6 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -29,9 +29,9 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; export class OutputPanel extends AbstractTextResourceEditor { - private actions: IAction[]; + private actions: IAction[] | undefined; private scopedInstantiationService: IInstantiationService; - private _focus: boolean; + private _focus = false; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -155,7 +155,7 @@ export class OutputPanel extends AbstractTextResourceEditor { } const model = codeEditor.getModel(); - if (model) { + if (model && this.actions) { const newPositionLine = e.position.lineNumber; const lastLine = model.getLineCount(); const newLockState = lastLine !== newPositionLine; diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index 695b84e3c23..6e7141a041b 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -14,14 +14,11 @@ import { EditorOptions } from 'vs/workbench/common/editor'; import { IOutputChannelDescriptor, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, LOG_SCHEME, CONTEXT_ACTIVE_LOG_OUTPUT, LOG_MIME, OUTPUT_MIME } from 'vs/workbench/contrib/output/common/output'; import { OutputPanel } from 'vs/workbench/contrib/output/browser/outputPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { OutputLinkProvider } from 'vs/workbench/contrib/output/common/outputLinkProvider'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { ITextModel } from 'vs/editor/common/model'; import { IPanel } from 'vs/workbench/common/panel'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { ILogService } from 'vs/platform/log/common/log'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -71,16 +68,13 @@ export class OutputService extends Disposable implements IOutputService, ITextMo private readonly _onActiveOutputChannel = this._register(new Emitter()); readonly onActiveOutputChannel: Event = this._onActiveOutputChannel.event; - private _outputPanel: OutputPanel; + private _outputPanel: OutputPanel | undefined; constructor( @IStorageService private readonly storageService: IStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IPanelService private readonly panelService: IPanelService, - @IWorkspaceContextService contextService: IWorkspaceContextService, @ITextModelService textModelResolverService: ITextModelService, - @IEnvironmentService environmentService: IEnvironmentService, - @IWindowService windowService: IWindowService, @ILogService private readonly logService: ILogService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -224,7 +218,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(!!channel.outputChannelDescriptor.file && channel.outputChannelDescriptor.log); return this._outputPanel.setInput(this.createInput(channel), EditorOptions.create({ preserveFocus }), CancellationToken.None) .then(() => { - if (!preserveFocus) { + if (!preserveFocus && this._outputPanel) { this._outputPanel.focus(); } }); @@ -291,4 +285,4 @@ export class LogContentProvider { } return channelModel; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index 052e4bd2f5c..b0a1861b2c5 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -152,8 +152,8 @@ class PerfModelContentProvider implements ITextModelContentProvider { table.push(['nls:start => nls:end', metrics.timers.ellapsedNlsGeneration, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['require(main.bundle.js)', metrics.initialStartup ? perf.getDuration('willLoadMainBundle', 'didLoadMainBundle') : undefined, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['app.isReady => window.loadUrl()', metrics.timers.ellapsedWindowLoad, '[main]', `initial startup: ${metrics.initialStartup}`]); - table.push(['window.loadUrl() => begin to require(workbench.main.js)', metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind)]); - table.push(['require(workbench.main.js)', metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${stats ? `, node_modules took ${stats.nodeRequireTotal}ms` : ''}`]); + table.push(['window.loadUrl() => begin to require(workbench.desktop.main.js)', metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind)]); + table.push(['require(workbench.desktop.main.js)', metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${stats ? `, node_modules took ${stats.nodeRequireTotal}ms` : ''}`]); table.push(['require & init workspace storage', metrics.timers.ellapsedWorkspaceStorageInit, '[renderer]', undefined]); table.push(['init workspace service', metrics.timers.ellapsedWorkspaceServiceInit, '[renderer]', undefined]); table.push(['register extensions & spawn extension host', metrics.timers.ellapsedExtensions, '[renderer]', undefined]); @@ -262,11 +262,11 @@ class PerfModelContentProvider implements ITextModelContentProvider { } abstract class LoaderStats { - readonly amdLoad: (string | number)[][]; - readonly amdInvoke: (string | number)[][]; - readonly nodeRequire: (string | number)[][]; - readonly nodeEval: (string | number)[][]; - readonly nodeRequireTotal: number; + abstract get amdLoad(): (string | number)[][]; + abstract get amdInvoke(): (string | number)[][]; + abstract get nodeRequire(): (string | number)[][]; + abstract get nodeEval(): (string | number)[][]; + abstract get nodeRequireTotal(): number; static get(): LoaderStats { diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index ecfdd8e2ea0..9c8fe678aed 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -17,6 +17,7 @@ import { PerfviewInput } from 'vs/workbench/contrib/performance/electron-browser import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { URI } from 'vs/base/common/uri'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; export class StartupProfiler implements IWorkbenchContribution { @@ -28,6 +29,7 @@ export class StartupProfiler implements IWorkbenchContribution { @IClipboardService private readonly _clipboardService: IClipboardService, @ILifecycleService lifecycleService: ILifecycleService, @IExtensionService extensionService: IExtensionService, + @IOpenerService private readonly _openerService: IOpenerService ) { // wait for everything to be ready Promise.all([ @@ -116,6 +118,6 @@ export class StartupProfiler implements IWorkbenchContribution { const baseUrl = product.reportIssueUrl; const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&'; - window.open(`${baseUrl}${queryStringPrefix}body=${encodeURIComponent(body)}`); + this._openerService.open(URI.parse(`${baseUrl}${queryStringPrefix}body=${encodeURIComponent(body)}`)); } } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index d83000eaaa4..a45a2680f32 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -789,7 +789,7 @@ class KeybindingItemRenderer implements IListRenderer { return this.fileService.createFile(file, VSBuffer.fromString(KeyboardLayoutPickerAction.DEFAULT_CONTENT)); - }).then((stat): Promise | null => { + }).then((stat): Promise | undefined => { if (!stat) { - return null; + return undefined; } return this.editorService.openEditor({ resource: stat.resource, diff --git a/src/vs/workbench/contrib/preferences/browser/media/check-dark.svg b/src/vs/workbench/contrib/preferences/browser/media/check-dark.svg index 865cc83c347..51674695e1f 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/check-dark.svg +++ b/src/vs/workbench/contrib/preferences/browser/media/check-dark.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/preferences/browser/media/check-light.svg b/src/vs/workbench/contrib/preferences/browser/media/check-light.svg index e1a546660ed..7b1da6d7208 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/check-light.svg +++ b/src/vs/workbench/contrib/preferences/browser/media/check-light.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 43d1569f305..5e8238fde91 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -143,7 +143,7 @@ max-width: 952px; /* 1000 - 24*2 padding */ margin-left: -476px; - z-index: 1000; + z-index: 11; } .settings-editor > .settings-body .settings-tree-container .setting-toolbar-container { @@ -182,7 +182,7 @@ .settings-editor > .settings-body .settings-toc-container { width: 100%; pointer-events: none; - z-index: 100; + z-index: 10; position: absolute; } @@ -511,4 +511,4 @@ .settings-editor.search-mode > .settings-body .settings-toc-container .monaco-list-row .settings-toc-count { display: block; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css index fe32e55ab66..ff5f813a4fc 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css @@ -12,10 +12,29 @@ margin-left: 2px; } +/* Deal with overflow */ +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-value, +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-sibling { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-value { + max-width: 90%; +} +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-sibling { + max-width: 10%; +} + .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-value, .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-sibling { display: inline-block; - line-height: 22px; + line-height: 24px; +} + +/* Use monospace to display glob patterns in exclude widget */ +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-exclude-widget .setting-list-value, +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-exclude-widget .setting-list-sibling { font-family: var(--monaco-monospace-font); } @@ -30,11 +49,11 @@ display: none; position: absolute; right: 0px; - margin-top: 1px; } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-row { position: relative; + max-height: 24px; } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-row:focus { @@ -48,7 +67,7 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-row .monaco-action-bar .action-label { width: 16px; - height: 16px; + height: 20px; padding: 2px; margin-right: 2px; } @@ -83,6 +102,7 @@ } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .monaco-text-button.setting-list-addButton { + margin-top: 4px; margin-right: 10px; } @@ -92,7 +112,7 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-valueInput, .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-siblingInput { - height: 22px; + height: 24px; max-width: 320px; flex: 1; margin-right: 10px; @@ -102,6 +122,8 @@ margin-right: 10px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget { +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget, +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-exclude-widget { margin-bottom: 1px; + padding: 1px; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index 34d9362a283..b93c9b2fd62 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -225,7 +225,7 @@ export class OpenFolderSettingsAction extends Action { return this.preferencesService.openFolderSettings(workspaceFolder.uri); } - return null; + return undefined; }); } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts index 2887abfc0f3..37de5e7766e 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -25,6 +25,7 @@ import { nullRange } from 'vs/workbench/services/preferences/common/preferencesM import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStringDictionary } from 'vs/base/common/collections'; import { IProductService } from 'vs/platform/product/common/product'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export interface IEndpointDetails { urlBase?: string; @@ -563,4 +564,6 @@ export class SettingMatches { endColumn: setting.valueRange.startColumn + match.end + 1 }; } -} \ No newline at end of file +} + +registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 7eeb9c15120..66678cbfffb 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -36,7 +36,7 @@ import { ISettingsGroup } from 'vs/workbench/services/preferences/common/prefere export class SettingsHeaderWidget extends Widget implements IViewZone { - private id: number; + private id: string; private _domNode: HTMLElement; protected titleContainer: HTMLElement; @@ -121,7 +121,7 @@ export class DefaultSettingsHeaderWidget extends SettingsHeaderWidget { export class SettingsGroupTitleWidget extends Widget implements IViewZone { - private id: number; + private id: string; private _afterLineNumber: number; private _domNode: HTMLElement; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 0d080e7d577..1382fd0df38 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -281,6 +281,11 @@ export class SettingsEditor2 extends BaseEditor { layout(dimension: DOM.Dimension): void { this.dimension = dimension; + + if (!this.isVisible()) { + return; + } + this.layoutTrees(dimension); const innerWidth = Math.min(1000, dimension.width) - 24 * 2; // 24px padding on left and right; @@ -453,12 +458,12 @@ export class SettingsEditor2 extends BaseEditor { } } - switchToSettingsFile(): Promise { + switchToSettingsFile(): Promise { const query = parseQuery(this.searchWidget.getValue()); return this.openSettingsFile(query.query); } - private openSettingsFile(query?: string): Promise { + private openSettingsFile(query?: string): Promise { const currentSettingsTarget = this.settingsTargetsWidget.settingsTarget; const options: ISettingsEditorOptions = { query }; @@ -841,7 +846,7 @@ export class SettingsEditor2 extends BaseEditor { this._register(model.onDidChangeGroups(() => this.onConfigUpdate())); this.defaultSettingsEditorModel = model; - return this.onConfigUpdate(); + return this.onConfigUpdate(undefined, true); }); } return Promise.resolve(null); @@ -971,7 +976,9 @@ export class SettingsEditor2 extends BaseEditor { if (key) { const focusedKey = focusedSetting.getAttribute(AbstractSettingRenderer.SETTING_KEY_ATTR); if (focusedKey === key && - !DOM.hasClass(focusedSetting, 'setting-item-list')) { // update `list`s live, as they have a separate "submit edit" step built in before this + // update `list`s live, as they have a separate "submit edit" step built in before this + (focusedSetting.parentElement && !DOM.hasClass(focusedSetting.parentElement, 'setting-item-list')) + ) { this.updateModifiedLabelForKey(key); this.scheduleRefresh(focusedSetting, key); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index b6437fbb2e1..4b0a52f9aa3 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -100,6 +100,11 @@ export const tocData: ITOCEntry = { id: 'workbench/zenmode', label: localize('zenMode', "Zen Mode"), settings: ['zenmode.*'] + }, + { + id: 'workbench/screencastmode', + label: localize('screencastMode', "Screencast Mode"), + settings: ['screencastMode.*'] } ] }, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 4c48aa744d3..82465a22624 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -698,32 +698,46 @@ export class SettingArrayRenderer extends AbstractSettingRenderer implements ITr private onDidChangeList(template: ISettingListItemTemplate, e: IListChangeEvent): void { if (template.context) { - const newValue: any[] | undefined = isArray(template.context.scopeValue) - ? [...template.context.scopeValue] - : [...template.context.value]; + let newValue: any[] = []; + if (isArray(template.context.scopeValue)) { + newValue = [...template.context.scopeValue]; + } else if (isArray(template.context.value)) { + newValue = [...template.context.value]; + } - // Delete value - if (e.removeIndex) { - if (!e.value && e.originalValue && e.removeIndex > -1) { - newValue.splice(e.removeIndex, 1); + if (e.targetIndex !== undefined) { + // Delete value + if (!e.value && e.originalValue && e.targetIndex > -1) { + newValue.splice(e.targetIndex, 1); } - } - // Add value - else if (e.value && !e.originalValue) { - newValue.push(e.value); - } - // Update value - else if (e.value && e.originalValue) { - const valueIndex = newValue.indexOf(e.originalValue); - if (valueIndex > -1) { - newValue[valueIndex] = e.value; + // Update value + else if (e.value && e.originalValue) { + if (e.targetIndex > -1) { + newValue[e.targetIndex] = e.value; + } + // For some reason, we are updating and cannot find original value + // Just append the value in this case + else { + newValue.push(e.value); + } } - // For some reason, we are updating and cannot find original value - // Just append the value in this case - else { + // Add value + else if (e.value && !e.originalValue && e.targetIndex >= newValue.length) { newValue.push(e.value); } } + if ( + template.context.defaultValue && + isArray(template.context.defaultValue) && + template.context.defaultValue.length === newValue.length && + template.context.defaultValue.join() === newValue.join() + ) { + return this._onDidChangeSetting.fire({ + key: template.context.setting.key, + value: undefined, // reset setting + type: template.context.valueType + }); + } this._onDidChangeSetting.fire({ key: template.context.setting.key, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index c85026c360b..efe952907cd 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -9,12 +9,12 @@ import { isArray, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { ITOCEntry, knownAcronyms, knownTermMappings } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices'; @@ -227,20 +227,24 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { matchesScope(scope: SettingsTarget, isRemote: boolean): boolean { const configTarget = URI.isUri(scope) ? ConfigurationTarget.WORKSPACE_FOLDER : scope; + if (!this.setting.scope) { + return true; + } + if (configTarget === ConfigurationTarget.WORKSPACE_FOLDER) { - return this.setting.scope === ConfigurationScope.RESOURCE; + return FOLDER_SCOPES.indexOf(this.setting.scope) !== -1; } if (configTarget === ConfigurationTarget.WORKSPACE) { - return this.setting.scope === ConfigurationScope.WINDOW || this.setting.scope === ConfigurationScope.RESOURCE; + return WORKSPACE_SCOPES.indexOf(this.setting.scope) !== -1; } if (configTarget === ConfigurationTarget.USER_REMOTE) { - return this.setting.scope === ConfigurationScope.MACHINE || this.setting.scope === ConfigurationScope.WINDOW || this.setting.scope === ConfigurationScope.RESOURCE; + return REMOTE_MACHINE_SCOPES.indexOf(this.setting.scope) !== -1; } if (configTarget === ConfigurationTarget.USER_LOCAL && isRemote) { - return this.setting.scope !== ConfigurationScope.MACHINE; + return LOCAL_MACHINE_SCOPES.indexOf(this.setting.scope) !== -1; } return true; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index 6f38e066dd4..6a2fbad2b88 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -16,10 +16,11 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/settingsWidgets'; import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { foreground, inputBackground, inputBorder, inputForeground, listActiveSelectionBackground, listActiveSelectionForeground, listHoverBackground, listHoverForeground, listInactiveSelectionBackground, listInactiveSelectionForeground, registerColor, selectBackground, selectBorder, selectForeground, textLinkForeground, textPreformatForeground, editorWidgetBorder, textLinkActiveForeground } from 'vs/platform/theme/common/colorRegistry'; +import { foreground, inputBackground, inputBorder, inputForeground, listActiveSelectionBackground, listActiveSelectionForeground, listHoverBackground, listHoverForeground, listInactiveSelectionBackground, listInactiveSelectionForeground, registerColor, selectBackground, selectBorder, selectForeground, textLinkForeground, textPreformatForeground, editorWidgetBorder, textLinkActiveForeground, simpleCheckboxBackground, simpleCheckboxForeground, simpleCheckboxBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { disposableTimeout } from 'vs/base/common/async'; +import { isUndefinedOrNull } from 'vs/base/common/types'; const $ = DOM.$; export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hc: '#ffffff' }, localize('headerForeground', "(For settings editor preview) The foreground color for a section header or active title.")); @@ -36,9 +37,9 @@ export const settingsSelectBorder = registerColor('settings.dropdownBorder', { d export const settingsSelectListBorder = registerColor('settings.dropdownListBorder', { dark: editorWidgetBorder, light: editorWidgetBorder, hc: editorWidgetBorder }, localize('settingsDropdownListBorder', "(For settings editor preview) Settings editor dropdown list border. This surrounds the options and separates the options from the description.")); // Bool control colors -export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', { dark: selectBackground, light: selectBackground, hc: selectBackground }, localize('settingsCheckboxBackground', "(For settings editor preview) Settings editor checkbox background.")); -export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, localize('settingsCheckboxForeground', "(For settings editor preview) Settings editor checkbox foreground.")); -export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', { dark: selectBorder, light: selectBorder, hc: selectBorder }, localize('settingsCheckboxBorder', "(For settings editor preview) Settings editor checkbox border.")); +export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', { dark: simpleCheckboxBackground, light: simpleCheckboxBackground, hc: simpleCheckboxBackground }, localize('settingsCheckboxBackground', "(For settings editor preview) Settings editor checkbox background.")); +export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', { dark: simpleCheckboxForeground, light: simpleCheckboxForeground, hc: simpleCheckboxForeground }, localize('settingsCheckboxForeground', "(For settings editor preview) Settings editor checkbox foreground.")); +export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', { dark: simpleCheckboxBorder, light: simpleCheckboxBorder, hc: simpleCheckboxBorder }, localize('settingsCheckboxBorder', "(For settings editor preview) Settings editor checkbox border.")); // Text control colors export const settingsTextInputBackground = registerColor('settings.textInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('textInputBoxBackground', "(For settings editor preview) Settings editor text input box background.")); @@ -131,14 +132,16 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } }); +type EditKey = 'none' | 'create' | number; + export class ListSettingListModel { private _dataItems: IListDataItem[] = []; - private _editKey: string | null; + private _editKey: EditKey; private _selectedIdx: number | null; get items(): IListViewItem[] { const items = this._dataItems.map((item, i) => { - const editing = item.value === this._editKey; + const editing = typeof this._editKey === 'number' && this._editKey === i; return { ...item, editing, @@ -146,7 +149,7 @@ export class ListSettingListModel { }; }); - if (this._editKey === '') { + if (this._editKey === 'create') { items.push({ editing: true, selected: true, @@ -158,7 +161,7 @@ export class ListSettingListModel { return items; } - setEditKey(key: string | null): void { + setEditKey(key: EditKey): void { this._editKey = key; } @@ -195,7 +198,7 @@ export interface IListChangeEvent { originalValue: string; value?: string; sibling?: string; - removeIndex?: number; + targetIndex?: number; } export class ListSettingWidget extends Disposable { @@ -218,7 +221,8 @@ export class ListSettingWidget extends Disposable { ) { super(); - this.listElement = DOM.append(container, $('.setting-list-widget')); + this.listElement = DOM.append(container, $('div')); + this.getContainerClasses().forEach(c => this.listElement.classList.add(c)); this.listElement.setAttribute('tabindex', '0'); DOM.append(container, this.renderAddButton()); this.renderList(); @@ -228,13 +232,19 @@ export class ListSettingWidget extends Disposable { this._register(DOM.addStandardDisposableListener(this.listElement, 'keydown', (e: KeyboardEvent) => { if (e.keyCode === KeyCode.UpArrow) { + const selectedIndex = this.model.getSelected(); this.model.selectPrevious(); - this.renderList(); + if (this.model.getSelected() !== selectedIndex) { + this.renderList(); + } e.preventDefault(); e.stopPropagation(); } else if (e.keyCode === KeyCode.DownArrow) { + const selectedIndex = this.model.getSelected(); this.model.selectNext(); - this.renderList(); + if (this.model.getSelected() !== selectedIndex) { + this.renderList(); + } e.preventDefault(); e.stopPropagation(); } @@ -259,6 +269,10 @@ export class ListSettingWidget extends Disposable { }; } + protected getContainerClasses() { + return ['setting-list-widget']; + } + setValue(listData: IListDataItem[]): void { this.model.setValue(listData); this.renderList(); @@ -288,7 +302,7 @@ export class ListSettingWidget extends Disposable { const item = this.model.items[targetIdx]; if (item) { - this.editSetting(item.value); + this.editSetting(targetIdx); e.preventDefault(); e.stopPropagation(); } @@ -332,7 +346,7 @@ export class ListSettingWidget extends Disposable { .map((item, i) => this.renderItem(item, i, focused)) .forEach(itemElement => this.listElement.appendChild(itemElement)); - const listHeight = 22 * this.model.items.length; + const listHeight = 24 * this.model.items.length; this.listElement.style.height = listHeight + 'px'; } @@ -342,30 +356,30 @@ export class ListSettingWidget extends Disposable { enabled: true, id: 'workbench.action.removeListItem', tooltip: this.getLocalizedStrings().deleteActionTooltip, - run: () => this._onDidChangeList.fire({ originalValue: key, value: undefined, removeIndex: idx }) + run: () => this._onDidChangeList.fire({ originalValue: key, value: undefined, targetIndex: idx }) }; } - private createEditAction(key: string): IAction { + private createEditAction(idx: number): IAction { return { class: 'setting-listAction-edit', enabled: true, id: 'workbench.action.editListItem', tooltip: this.getLocalizedStrings().editActionTooltip, run: () => { - this.editSetting(key); + this.editSetting(idx); } }; } - private editSetting(key: string): void { - this.model.setEditKey(key); + private editSetting(idx: number): void { + this.model.setEditKey(idx); this.renderList(); } private renderItem(item: IListViewItem, idx: number, listFocused: boolean): HTMLElement { return item.editing ? - this.renderEditItem(item) : + this.renderEditItem(item, idx) : this.renderDataItem(item, idx, listFocused); } @@ -384,7 +398,7 @@ export class ListSettingWidget extends Disposable { siblingElement.textContent = item.sibling ? ('when: ' + item.sibling) : null; actionBar.push([ - this.createEditAction(item.value), + this.createEditAction(idx), this.createDeleteAction(item.value, idx) ], { icon: true, label: false }); @@ -412,24 +426,25 @@ export class ListSettingWidget extends Disposable { this._register(attachButtonStyler(startAddButton, this.themeService)); this._register(startAddButton.onDidClick(() => { - this.model.setEditKey(''); + this.model.setEditKey('create'); this.renderList(); })); return rowElement; } - private renderEditItem(item: IListViewItem): HTMLElement { + private renderEditItem(item: IListViewItem, idx: number): HTMLElement { const rowElement = $('.setting-list-edit-row'); const onSubmit = (edited: boolean) => { - this.model.setEditKey(null); + this.model.setEditKey('none'); const value = valueInput.value.trim(); - if (edited && value) { + if (edited && !isUndefinedOrNull(value)) { this._onDidChangeList.fire({ originalValue: item.value, value: value, - sibling: siblingInput && siblingInput.value.trim() + sibling: siblingInput && siblingInput.value.trim(), + targetIndex: idx }); } this.renderList(); @@ -514,6 +529,10 @@ export class ExcludeSettingWidget extends ListSettingWidget { settingListRowSiblingHintLabel: localize('excludeSiblingHintLabel', "Exclude files matching `{0}`, only when a file matching `{1}` is present", pattern, sibling) }; } + + protected getContainerClasses() { + return ['setting-list-exclude-widget']; + } } export interface IListDataItem { diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts index eb8c57ee939..98fd156537b 100644 --- a/src/vs/workbench/contrib/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -55,7 +55,6 @@ export interface IKeybindingsEditor extends IEditor { focusKeybindings(): void; recordSearchKeys(): void; toggleSortByPrecedence(): void; - layoutColumns(columns: HTMLElement[]): void; selectKeybinding(keybindingEntry: IKeybindingItemEntry): void; defineKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; defineWhenExpression(keybindingEntry: IKeybindingItemEntry): void; diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts index 724509b2d05..19e76cbf3b7 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts @@ -99,18 +99,17 @@ class GotoLineEntry extends EditorQuickOpenEntry { if (this.editorService.activeTextEditorWidget && this.invalidRange(maxLineNumber)) { const position = this.editorService.activeTextEditorWidget.getPosition(); if (position) { - const currentLine = position.lineNumber; if (maxLineNumber > 0) { - return nls.localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}. Type a line number between 1 and {1} to navigate to.", currentLine, maxLineNumber); + return nls.localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}, Column: {1}. Type a line number between 1 and {2} to navigate to.", position.lineNumber, position.column, maxLineNumber); } - return nls.localize('gotoLineLabelEmpty', "Current Line: {0}. Type a line number to navigate to.", currentLine); + return nls.localize('gotoLineLabelEmpty', "Current Line: {0}, Column: {1}. Type a line number to navigate to.", position.lineNumber, position.column); } } // Input valid, indicate action - return this.column ? nls.localize('gotoLineColumnLabel', "Go to line {0} and character {1}.", this.line, this.column) : nls.localize('gotoLineLabel', "Go to line {0}.", this.line); + return this.column ? nls.localize('gotoLineColumnLabel', "Go to line {0} and column {1}.", this.line, this.column) : nls.localize('gotoLineLabel', "Go to line {0}.", this.line); } private invalidRange(maxLineNumber: number = this.getMaxLineNumber()): boolean { @@ -229,8 +228,7 @@ export class GotoLineHandler extends QuickOpenHandler { if (this.editorService.activeTextEditorWidget) { const position = this.editorService.activeTextEditorWidget.getPosition(); if (position) { - const currentLine = position.lineNumber; - return nls.localize('gotoLineLabelEmpty', "Current Line: {0}. Type a line number to navigate to.", currentLine); + return nls.localize('gotoLineLabelEmpty', "Current Line: {0}, Column: {1}. Type a line number to navigate to.", position.lineNumber, position.column); } } diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index ed5f3203846..eef3c303d6c 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -366,7 +366,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { static readonly ID = 'workbench.picker.filesymbols'; private rangeHighlightDecorationId?: IEditorLineDecoration; - private lastKnownEditorViewState: IEditorViewState | null; + private lastKnownEditorViewState: IEditorViewState | null = null; private cachedOutlineRequest?: Promise; private pendingOutlineRequest?: CancellationTokenSource; diff --git a/src/vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts similarity index 76% rename from src/vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution.ts rename to src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts index b53f3002e94..2ec3089894f 100644 --- a/src/vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts @@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { RunOnceScheduler } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { isEqual } from 'vs/base/common/resources'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isNative } from 'vs/base/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -23,7 +23,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ interface IConfiguration extends IWindowsConfiguration { update: { mode: string; }; telemetry: { enableCrashReporter: boolean }; - keyboard: { touchbar: { enabled: boolean } }; workbench: { list: { horizontalScrolling: boolean }, useExperimentalGridLayout: boolean }; debug: { console: { wordWrap: boolean } }; } @@ -36,7 +35,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private clickThroughInactive: boolean; private updateMode: string; private enableCrashReporter: boolean; - private touchbarEnabled: boolean; private treeHorizontalScrolling: boolean; private useGridLayout: boolean; private debugConsoleWordWrap: boolean; @@ -57,48 +55,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private onConfigurationChange(config: IConfiguration, notify: boolean): void { let changed = false; - // Titlebar style - if (config.window && config.window.titleBarStyle !== this.titleBarStyle && (config.window.titleBarStyle === 'native' || config.window.titleBarStyle === 'custom')) { - this.titleBarStyle = config.window.titleBarStyle; - changed = true; - } - - // macOS: Native tabs - if (isMacintosh && config.window && typeof config.window.nativeTabs === 'boolean' && config.window.nativeTabs !== this.nativeTabs) { - this.nativeTabs = config.window.nativeTabs; - changed = true; - } - - // macOS: Native fullscreen - if (isMacintosh && config.window && typeof config.window.nativeFullScreen === 'boolean' && config.window.nativeFullScreen !== this.nativeFullScreen) { - this.nativeFullScreen = config.window.nativeFullScreen; - changed = true; - } - - // macOS: Click through (accept first mouse) - if (isMacintosh && config.window && typeof config.window.clickThroughInactive === 'boolean' && config.window.clickThroughInactive !== this.clickThroughInactive) { - this.clickThroughInactive = config.window.clickThroughInactive; - changed = true; - } - - // Update channel - if (config.update && typeof config.update.mode === 'string' && config.update.mode !== this.updateMode) { - this.updateMode = config.update.mode; - changed = true; - } - - // Crash reporter - if (config.telemetry && typeof config.telemetry.enableCrashReporter === 'boolean' && config.telemetry.enableCrashReporter !== this.enableCrashReporter) { - this.enableCrashReporter = config.telemetry.enableCrashReporter; - changed = true; - } - - // macOS: Touchbar config - if (isMacintosh && config.keyboard && config.keyboard.touchbar && typeof config.keyboard.touchbar.enabled === 'boolean' && config.keyboard.touchbar.enabled !== this.touchbarEnabled) { - this.touchbarEnabled = config.keyboard.touchbar.enabled; - changed = true; - } - // Tree horizontal scrolling support if (config.workbench && config.workbench.list && typeof config.workbench.list.horizontalScrolling === 'boolean' && config.workbench.list.horizontalScrolling !== this.treeHorizontalScrolling) { this.treeHorizontalScrolling = config.workbench.list.horizontalScrolling; @@ -117,12 +73,57 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo changed = true; } + if (isNative) { + + // Titlebar style + if (config.window && config.window.titleBarStyle !== this.titleBarStyle && (config.window.titleBarStyle === 'native' || config.window.titleBarStyle === 'custom')) { + this.titleBarStyle = config.window.titleBarStyle; + changed = true; + } + + // macOS: Native tabs + if (isMacintosh && config.window && typeof config.window.nativeTabs === 'boolean' && config.window.nativeTabs !== this.nativeTabs) { + this.nativeTabs = config.window.nativeTabs; + changed = true; + } + + // macOS: Native fullscreen + if (isMacintosh && config.window && typeof config.window.nativeFullScreen === 'boolean' && config.window.nativeFullScreen !== this.nativeFullScreen) { + this.nativeFullScreen = config.window.nativeFullScreen; + changed = true; + } + + // macOS: Click through (accept first mouse) + if (isMacintosh && config.window && typeof config.window.clickThroughInactive === 'boolean' && config.window.clickThroughInactive !== this.clickThroughInactive) { + this.clickThroughInactive = config.window.clickThroughInactive; + changed = true; + } + + // Update channel + if (config.update && typeof config.update.mode === 'string' && config.update.mode !== this.updateMode) { + this.updateMode = config.update.mode; + changed = true; + } + + // Crash reporter + if (config.telemetry && typeof config.telemetry.enableCrashReporter === 'boolean' && config.telemetry.enableCrashReporter !== this.enableCrashReporter) { + this.enableCrashReporter = config.telemetry.enableCrashReporter; + changed = true; + } + } + // Notify only when changed and we are the focused window (avoids notification spam across windows) if (notify && changed) { this.doConfirm( - localize('relaunchSettingMessage', "A setting has changed that requires a restart to take effect."), - localize('relaunchSettingDetail', "Press the restart button to restart {0} and enable the setting.", this.envService.appNameLong), - localize('restart', "&&Restart"), + isNative ? + localize('relaunchSettingMessage', "A setting has changed that requires a restart to take effect.") : + localize('relaunchSettingMessageWeb', "A setting has changed that requires a reload to take effect."), + isNative ? + localize('relaunchSettingDetail', "Press the restart button to restart {0} and enable the setting.", this.envService.appNameLong) : + localize('relaunchSettingDetailWeb', "Press the reload button to reload {0} and enable the setting.", this.envService.appNameLong), + isNative ? + localize('restart', "&&Restart") : + localize('restartWeb', "&&Reload"), () => this.windowsService.relaunch(Object.create(null)) ); } @@ -158,7 +159,7 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IExtensionService extensionService: IExtensionService, - @IWindowService windowSevice: IWindowService, + @IWindowService windowService: IWindowService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { super(); @@ -167,8 +168,9 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor if (!!environmentService.extensionTestsLocationURI) { return; // no restart when in tests: see https://github.com/Microsoft/vscode/issues/66936 } + if (environmentService.configuration.remoteAuthority) { - windowSevice.reloadWindow(); // TODO aeschli, workaround + windowService.reloadWindow(); // TODO@aeschli, workaround } else { extensionService.restartExtensionHost(); } diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg new file mode 100644 index 00000000000..2673902c684 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg new file mode 100644 index 00000000000..e8dc8205bab --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg new file mode 100644 index 00000000000..4a3009baeee --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg new file mode 100644 index 00000000000..5d99408934e --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg new file mode 100644 index 00000000000..941430e9dd6 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg new file mode 100644 index 00000000000..72437202b72 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg new file mode 100644 index 00000000000..0ea65d83198 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg new file mode 100644 index 00000000000..5bb05d3d8c5 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg new file mode 100644 index 00000000000..46cde7f7cc0 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg new file mode 100644 index 00000000000..0117ceb7ded --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg new file mode 100644 index 00000000000..b0c521b7dc6 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg new file mode 100644 index 00000000000..5da9322b6a9 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg new file mode 100644 index 00000000000..21eec9cbcb8 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg new file mode 100644 index 00000000000..94013ea52ae --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg new file mode 100644 index 00000000000..826d0eefbf4 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg b/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg new file mode 100644 index 00000000000..029e6b051c2 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts new file mode 100644 index 00000000000..eb7bc581f9d --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -0,0 +1,423 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./remoteViewlet'; +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { URI } from 'vs/base/common/uri'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/contrib/remote/common/remote.contribution'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IViewDescriptor, IViewsRegistry, Extensions } from 'vs/workbench/common/views'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; +import { Event } from 'vs/base/common/event'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; + +interface HelpInformation { + extensionDescription: IExtensionDescription; + getStarted?: string; + documentation?: string; + feedback?: string; + issues?: string; +} + +const remoteHelpExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'remoteHelp', + jsonSchema: { + description: nls.localize('RemoteHelpInformationExtPoint', 'Contributes help information for Remote'), + type: 'object', + properties: { + 'getStarted': { + description: nls.localize('RemoteHelpInformationExtPoint.getStarted', "The url to your project's Getting Started page"), + type: 'string' + }, + 'documentation': { + description: nls.localize('RemoteHelpInformationExtPoint.documentation', "The url to your project's documentation page"), + type: 'string' + }, + 'feedback': { + description: nls.localize('RemoteHelpInformationExtPoint.feedback', "The url to your project's feedback reporter"), + type: 'string' + }, + 'issues': { + description: nls.localize('RemoteHelpInformationExtPoint.issues', "The url to your project's issues list"), + type: 'string' + } + } + } +}); + +interface IViewModel { + helpInformations: HelpInformation[]; +} + +class HelpTreeVirtualDelegate implements IListVirtualDelegate { + getHeight(element: IHelpItem): number { + return 22; + } + + getTemplateId(element: IHelpItem): string { + return 'HelpItemTemplate'; + } +} + +interface IHelpItemTemplateData { + parent: HTMLElement; + icon: HTMLElement; +} + +class HelpTreeRenderer implements ITreeRenderer { + templateId: string = 'HelpItemTemplate'; + + renderTemplate(container: HTMLElement): IHelpItemTemplateData { + dom.addClass(container, 'remote-help-tree-node-item'); + + const icon = dom.append(container, dom.$('.remote-help-tree-node-item-icon')); + + const data = Object.create(null); + data.parent = container; + data.icon = icon; + + return data; + } + + renderElement(element: ITreeNode, index: number, templateData: IHelpItemTemplateData, height: number | undefined): void { + const container = templateData.parent; + dom.append(container, templateData.icon); + dom.addClass(templateData.icon, element.element.key); + const labelContainer = dom.append(container, dom.$('.help-item-label')); + labelContainer.innerText = element.element.label; + } + + disposeTemplate(templateData: IHelpItemTemplateData): void { + + } +} + +class HelpDataSource implements IAsyncDataSource { + hasChildren(element: any) { + return element instanceof HelpModel; + } + + getChildren(element: any) { + if (element instanceof HelpModel) { + return element.items; + } + + return []; + } +} + +interface IHelpItem { + key: string; + label: string; + handleClick(): Promise; +} + +class HelpItem implements IHelpItem { + constructor( + public key: string, + public label: string, + public values: { extensionDescription: IExtensionDescription; url: string }[], + private openerService: IOpenerService, + private quickInputService: IQuickInputService + ) { + } + + async handleClick() { + if (this.values.length > 1) { + let actions = this.values.map(value => { + return { + label: value.extensionDescription.displayName || value.extensionDescription.identifier.value, + description: value.url + }; + }); + + const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtension', "Select url to open") }); + + if (action) { + await this.openerService.open(URI.parse(action.label)); + } + } else { + await this.openerService.open(URI.parse(this.values[0].url)); + } + } +} + +class IssueReporterItem implements IHelpItem { + constructor( + public key: string, + public label: string, + public extensionDescriptions: IExtensionDescription[], + private quickInputService: IQuickInputService, + private commandService: ICommandService + ) { + } + + async handleClick() { + if (this.extensionDescriptions.length > 1) { + let actions = this.extensionDescriptions.map(extension => { + return { + label: extension.displayName || extension.identifier.value, + identifier: extension.identifier + }; + }); + + const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtensionToReportIssue', "Select an extension to report issue") }); + + if (action) { + await this.commandService.executeCommand('workbench.action.openIssueReporter', [action.identifier.value]); + } + } else { + await this.commandService.executeCommand('workbench.action.openIssueReporter', [this.extensionDescriptions[0].identifier.value]); + } + } +} + +class HelpModel { + items: IHelpItem[]; + + constructor( + viewModel: IViewModel, + openerService: IOpenerService, + quickInputService: IQuickInputService, + commandService: ICommandService + ) { + let helpItems: IHelpItem[] = []; + const getStarted = viewModel.helpInformations.filter(info => info.getStarted); + + if (getStarted.length) { + helpItems.push(new HelpItem( + 'getStarted', + nls.localize('remote.help.getStarted', "Get Started"), + getStarted.map((info: HelpInformation) => ({ + extensionDescription: info.extensionDescription, + url: info.getStarted! + })), + openerService, + quickInputService + )); + } + + const documentation = viewModel.helpInformations.filter(info => info.documentation); + + if (documentation.length) { + helpItems.push(new HelpItem( + 'documentation', + nls.localize('remote.help.documentation', "Read Documentation"), + documentation.map((info: HelpInformation) => ({ + extensionDescription: info.extensionDescription, + url: info.documentation! + })), + openerService, + quickInputService + )); + } + + const feedback = viewModel.helpInformations.filter(info => info.feedback); + + if (feedback.length) { + helpItems.push(new HelpItem( + 'feedback', + nls.localize('remote.help.feedback', "Provide Feedback"), + feedback.map((info: HelpInformation) => ({ + extensionDescription: info.extensionDescription, + url: info.feedback! + })), + openerService, + quickInputService + )); + } + + const issues = viewModel.helpInformations.filter(info => info.issues); + + if (issues.length) { + helpItems.push(new HelpItem( + 'issues', + nls.localize('remote.help.issues', "Review Issues"), + issues.map((info: HelpInformation) => ({ + extensionDescription: info.extensionDescription, + url: info.issues! + })), + openerService, + quickInputService + )); + } + + if (helpItems.length) { + helpItems.push(new IssueReporterItem( + 'issueReporter', + nls.localize('remote.help.report', "Report Issue"), + viewModel.helpInformations.map(info => info.extensionDescription), + quickInputService, + commandService + )); + } + + if (helpItems.length) { + this.items = helpItems; + } + } +} + +class HelpPanel extends ViewletPanel { + static readonly ID = '~remote.helpPanel'; + static readonly TITLE = nls.localize('remote.help', "Help and feedback"); + private tree!: WorkbenchAsyncDataTree; + + constructor( + protected viewModel: IViewModel, + options: IViewletPanelOptions, + @IKeybindingService protected keybindingService: IKeybindingService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IContextKeyService protected contextKeyService: IContextKeyService, + @IConfigurationService protected configurationService: IConfigurationService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IOpenerService protected openerService: IOpenerService, + @IQuickInputService protected quickInputService: IQuickInputService, + @ICommandService protected commandService: ICommandService + + + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + } + + protected renderBody(container: HTMLElement): void { + dom.addClass(container, 'remote-help'); + const treeContainer = document.createElement('div'); + dom.addClass(treeContainer, 'remote-help-content'); + container.appendChild(treeContainer); + + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, + treeContainer, + new HelpTreeVirtualDelegate(), + [new HelpTreeRenderer()], + new HelpDataSource(), + { + keyboardSupport: true, + } + ); + + const model = new HelpModel(this.viewModel, this.openerService, this.quickInputService, this.commandService); + + this.tree.setInput(model); + + const helpItemNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: false, openOnSelection: false })); + + this._register(Event.debounce(helpItemNavigator.onDidOpenResource, (last, event) => event, 75, true)(e => { + e.element.handleClick(); + })); + } + + protected layoutBody(height: number, width: number): void { + this.tree.layout(height, width); + } +} + +class HelpPanelDescriptor implements IViewDescriptor { + readonly id = HelpPanel.ID; + readonly name = HelpPanel.TITLE; + readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly canToggleVisibility = true; + readonly hideByDefault = false; + readonly workspace = true; + + constructor(viewModel: IViewModel) { + this.ctorDescriptor = { ctor: HelpPanel, arguments: [viewModel] }; + } +} + + +export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { + private helpPanelDescriptor = new HelpPanelDescriptor(this); + + helpInformations: HelpInformation[] = []; + + constructor( + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IStorageService storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService + ) { + super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + + remoteHelpExtPoint.setHandler((extensions) => { + let helpInformation: HelpInformation[] = []; + for (let extension of extensions) { + this._handleRemoteInfoExtensionPoint(extension, helpInformation); + } + + this.helpInformations = helpInformation; + + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + if (this.helpInformations.length) { + viewsRegistry.registerViews([this.helpPanelDescriptor], VIEW_CONTAINER); + } else { + viewsRegistry.deregisterViews([this.helpPanelDescriptor], VIEW_CONTAINER); + } + }); + } + + private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { + if (!extension.description.enableProposedApi) { + return; + } + + if (!extension.value.documentation && !extension.value.feedback && !extension.value.getStarted && !extension.value.issues) { + return; + } + + helpInformation.push({ + extensionDescription: extension.description, + getStarted: extension.value.getStarted, + documentation: extension.value.documentation, + feedback: extension.value.feedback, + issues: extension.value.issues + }); + } + + onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { + // too late, already added to the view model + return super.onDidAddViews(added); + } + + getTitle(): string { + const title = nls.localize('remote.explorer', "Remote Explorer"); + return title; + } +} + +Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( + RemoteViewlet, + VIEWLET_ID, + nls.localize('remote.explorer', "Remote Explorer"), + 'remote', + 4 +)); diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css new file mode 100644 index 00000000000..86bd4b76dc9 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .activitybar>.content .monaco-action-bar .action-label.remote { + -webkit-mask: url('remote-activity-bar.svg') no-repeat 50% 50%; +} + +.remote-help-content .monaco-list .monaco-list-row .remote-help-tree-node-item { + display: flex; + height: 22px; + line-height: 22px; + flex: 1; + text-overflow: ellipsis; + overflow: hidden; + flex-wrap: nowrap; +} + +.remote-help-content .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon { + background-size: 16px; + background-position: left center; + background-repeat: no-repeat; + padding-right: 6px; + width: 16px; + height: 22px; + -webkit-font-smoothing: antialiased; +} + +.remote-help-content .monaco-list .monaco-list-row .monaco-tl-twistie { + width: 0px !important; +} + +.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { + background-image: url('help-getting-started-light.svg') +} + +.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { + background-image: url('help-documentation-light.svg') +} + +.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { + background-image: url('help-feedback-light.svg') +} + +.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { + background-image: url('help-review-issues-light.svg') +} + +.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { + background-image: url('help-report-issue-light.svg') +} + +.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { + background-image: url('help-getting-started-dark.svg') +} + +.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { + background-image: url('help-documentation-dark.svg') +} + +.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { + background-image: url('help-feedback-dark.svg') +} + +.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { + background-image: url('help-review-issues-dark.svg') +} + +.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { + background-image: url('help-report-issue-dark.svg') +} + +.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { + background-image: url('help-getting-started-hc.svg') +} + +.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { + background-image: url('help-documentation-hc.svg') +} + +.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { + background-image: url('help-feedback-hc.svg') +} + +.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { + background-image: url('help-review-issues-hc.svg') +} + +.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { + background-image: url('help-report-issue-hc.svg') +} diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 9235c739fb0..124fbfdbc9f 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -17,6 +17,34 @@ import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/c import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; + +export const VIEWLET_ID = 'workbench.view.remote'; +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( + VIEWLET_ID, + true, + undefined, + { + getOrder: (group?: string) => { + if (!group) { + return; + } + + let matches = /^targets@(\d+)$/.exec(group); + if (matches) { + return -1000; + } + + matches = /^details@(\d+)$/.exec(group); + + if (matches) { + return -500; + } + + return; + } + } +); export class LabelContribution implements IWorkbenchContribution { constructor( @@ -36,7 +64,8 @@ export class LabelContribution implements IWorkbenchContribution { formatting: { label: '${path}', separator: remoteEnvironment.os === OperatingSystem.Windows ? '\\' : '/', - tildify: remoteEnvironment.os !== OperatingSystem.Windows + tildify: remoteEnvironment.os !== OperatingSystem.Windows, + normalizeDriveLetter: remoteEnvironment.os === OperatingSystem.Windows } }); } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index c0640c2ffc3..00e601638ec 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -27,7 +27,7 @@ import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; import { ipcRenderer as ipc } from 'electron'; -import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProgressService, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; @@ -85,7 +85,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc group: '6_close', command: { id: CLOSE_REMOTE_COMMAND_ID, - title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "C&&lose Remote Connection") + title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection") }, order: 3.5 }); @@ -284,6 +284,26 @@ class ProgressReporter { } } +class RemoteExtensionHostEnvironmentUpdater implements IWorkbenchContribution { + constructor( + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IRemoteAuthorityResolverService remoteResolverService: IRemoteAuthorityResolverService, + @IExtensionService extensionService: IExtensionService + ) { + const connection = remoteAgentService.getConnection(); + if (connection) { + connection.onDidStateChange(async e => { + if (e.type === PersistentConnectionEventType.ConnectionGain) { + const resolveResult = await remoteResolverService.resolveAuthority(connection.remoteAuthority); + if (resolveResult.options && resolveResult.options.extensionHostEnv) { + await extensionService.setRemoteEnvironment(resolveResult.options.extensionHostEnv); + } + } + }); + } + } +} + class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, @@ -440,6 +460,7 @@ const workbenchContributionsRegistry = Registry.as { //#region --- fetching/caching -const _cacheName = 'vscode-resources'; -const _resourcePrefix = '/vscode-resources/fetch'; +const _cacheName = 'vscode-extension-resources'; +const _resourcePrefix = '/vscode-remote'; const _pendingFetch = new Map(); self.addEventListener('message', event => { @@ -71,38 +69,18 @@ async function respondWithDefault(event: FetchEvent): Promise { } async function respondWithResource(event: FetchEvent, uri: URI): Promise { - const cacheKey = event.request.url.replace('&r=1', ''); - const cachedValue = await caches.open(_cacheName).then(cache => cache.match(cacheKey)); + + const cachedValue = await caches.open(_cacheName).then(cache => cache.match(event.request)); if (cachedValue) { return cachedValue; } - return new Promise(resolve => { + const response: Response = await event.preloadResponse || await fetch(event.request); + if (response.headers.get('X-VSCode-Extension') === 'true') { + await caches.open(_cacheName).then(cache => cache.put(event.request, response.clone())); + } - const token = generateUuid(); - const [first] = uri.query.split('&'); - const components = JSON.parse(first.substr(2)); - - _pendingFetch.set(token, async (data: ArrayBuffer, isExtensionResource: boolean) => { - - const res = new Response(data, { - status: 200, - headers: { 'Content-Type': getMediaMime(components.path) || 'text/plain' } - }); - - if (isExtensionResource) { - // only cache extension resources but not other - // resources, esp not workspace resources - await caches.open(_cacheName).then(cache => cache.put(cacheKey, res.clone())); - } - - return resolve(res); - }); - - self.clients.get(event.clientId).then(client => { - client.postMessage({ uri: components, token }); - }); - }); + return response; } //#endregion diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts index dfda6a1cfbb..326dfb49eea 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerClient.ts @@ -3,145 +3,41 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IFileService } from 'vs/platform/files/common/files'; -import { URI } from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { isEqualOrParent } from 'vs/base/common/resources'; import { ILogService } from 'vs/platform/log/common/log'; - -// load and start service worker as soon as this file -// is being loaded and later, when services are ready, -// claim this service worker so that messages can be -// replied to -const _serviceWorker = new class ServiceWorkerStarter { - - private static _url = require.toUrl('./resourceServiceWorkerMain.js'); - - private _beforeReadyEvents: ExtendableMessageEvent[] = []; - private _messageHandler?: (event: ExtendableMessageEvent) => void; - - constructor() { - navigator.serviceWorker.register(ServiceWorkerStarter._url, { scope: '/' }).then(reg => { - // console.debug('SW#reg', reg); - return reg.update(); - // }).then(() => { - // // console.debug('SW#updated', reg); - // return navigator.serviceWorker.ready; - }).then(() => { - console.info('SW#ready'); - }).catch(err => { - console.error('SW#init', err); - }); - - const handleMessage = (event: ExtendableMessageEvent) => { - if (!this._messageHandler) { - this._beforeReadyEvents.push(event); - console.debug('SW#buffered', event.data); - } else { - this._messageHandler(event); - } - }; - - navigator.serviceWorker.addEventListener('message', e => handleMessage(e as ExtendableMessageEvent)); - } - - dispose(): void { - // when to dispose? - } - - claim(handler: (event: ExtendableMessageEvent) => void): void { - this._messageHandler = handler; - this._beforeReadyEvents.forEach(this._messageHandler); - } -}; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; class ResourceServiceWorker { + private static _url = require.toUrl('./resourceServiceWorkerMain.js'); + + private readonly _disposables = new DisposableStore(); + constructor( - @IFileService private readonly _fileService: IFileService, - @IExtensionService private readonly _extensionService: IExtensionService, @ILogService private readonly _logService: ILogService, ) { - this._updateEarlyResourceUris(); - _serviceWorker.claim(e => this._handleMessage(e)); + navigator.serviceWorker.register(ResourceServiceWorker._url, { scope: '/' }).then(reg => { + this._logService.trace('SW#reg', reg); + return reg.update(); + }).then(() => { + this._logService.info('SW#ready'); + }).catch(err => { + this._logService.error('SW#init', err); + }); + + const handler = (e: ExtendableMessageEvent) => this._handleMessage(e); + navigator.serviceWorker.addEventListener('message', handler); + this._disposables.add(toDisposable(() => navigator.serviceWorker.removeEventListener('message', handler))); + } + + dispose(): void { + this._disposables.dispose(); } private _handleMessage(event: ExtendableMessageEvent): void { - this._logService.trace('SW - fetch', event.data.uri); - - const uri = URI.revive(event.data.uri); - Promise.all([ - this._fileService.readFile(uri), - this._isExtensionResource(uri) - ]).then(([file, isExtensionResource]) => { - if (!event.source) { - return; - } - event.source.postMessage({ - token: event.data.token, - data: file.value.buffer.buffer, - isExtensionResource - }, [file.value.buffer.buffer]); - }); - } - - private async _isExtensionResource(uri: URI): Promise { - for (const ext of await this._extensionService.getExtensions()) { - if (isEqualOrParent(uri, ext.extensionLocation)) { - return true; - } - } - return false; - } - - private _updateEarlyResourceUris(): void { - - let updateCount = 0; - - // find style-tags - const styleElements = document.querySelectorAll('style'); - for (let i = 0; i < styleElements.length; i++) { - const el = styleElements.item(i); - if (!el.sheet) { - continue; - } - const rules = (el.sheet).rules; - for (let j = 0; j < rules.length; j++) { - const rule = rules[j]; - const newCssText = this._updateResourceUris(rule.cssText); - if (newCssText) { - (el.sheet).deleteRule(j); - (el.sheet).insertRule(newCssText, j); - updateCount += 1; - } - } - } - - // find any tag using remote uris - const htmlElements = document.querySelectorAll('[style*="/vscode-resources/fetch"]'); - for (let i = 0; i < htmlElements.length; i++) { - const el = htmlElements.item(i); - const newCssText = this._updateResourceUris(el.style.cssText); - if (newCssText) { - el.style.cssText = newCssText; - updateCount += 1; - } - } - - this._logService.trace('SW - count of changed, early dom element: ', updateCount); - } - - private _updateResourceUris(cssText: string): string | undefined { - let changed = false; - let newCssText = cssText.replace(/url\((["'])?(.+?\/vscode-resources\/fetch\?.+?)\1\)/g, (_match, g1, g2, _offset, _input) => { - changed = true; - return `url(${g1 || ''}${g2}&r=1${g1 || ''})`; - }); - - return changed ? newCssText : undefined; + this._logService.trace('SW', event.data); } } diff --git a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts index 49f317ecd55..0dc883addcc 100644 --- a/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts +++ b/src/vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.ts @@ -10,7 +10,7 @@ // statement. // trigger service worker updates -const _tag = 'a6f9835e-c10e-4299-ab39-b8e29547c20a'; +const _tag = '52278406-3ca9-48af-a8fb-8495add5bb4e'; // loader world const baseUrl = '../../../../../'; diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 97539eb481f..955579069d7 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -234,7 +234,7 @@ class DirtyDiffWidget extends PeekViewWidget { const changeTypeColor = getChangeTypeColor(this.themeService.getTheme(), changeType); this.style({ frameColor: changeTypeColor, arrowColor: changeTypeColor }); - this._actionbarWidget.context = [this.model.modified!.uri, this.model.changes, index]; + this._actionbarWidget!.context = [this.model.modified!.uri, this.model.changes, index]; this.show(position, height); this.editor.focus(); } @@ -255,11 +255,11 @@ class DirtyDiffWidget extends PeekViewWidget { this._disposables.add(previous); this._disposables.add(next); - this._actionbarWidget.push([previous, next], { label: false, icon: true }); + this._actionbarWidget!.push([previous, next], { label: false, icon: true }); const actions: IAction[] = []; this._disposables.add(createAndFillInActionBarActions(this.menu, { shouldForwardArgs: true }, actions)); - this._actionbarWidget.push(actions, { label: false, icon: true }); + this._actionbarWidget!.push(actions, { label: false, icon: true }); } protected _getActionBarOptions(): IActionBarOptions { diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index beef3cdc9fb..84504b8b1f5 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -13,10 +13,10 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { StatusUpdater, StatusBarController } from './scmActivity'; +import { SCMStatusController } from './scmActivity'; import { SCMViewlet } from 'vs/workbench/contrib/scm/browser/scmViewlet'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -47,10 +47,7 @@ Registry.as(ViewletExtensions.Viewlets).registerViewlet(new Vie )); Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored); - -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(StatusBarController, LifecyclePhase.Restored); + .registerWorkbenchContribution(SCMStatusController, LifecyclePhase.Restored); // Register Action to Open Viewlet Registry.as(WorkbenchActionExtensions.WorkbenchActions).registerWorkbenchAction( @@ -69,10 +66,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis order: 5, title: localize('scmConfigurationTitle', "SCM"), type: 'object', + scope: ConfigurationScope.RESOURCE, properties: { 'scm.alwaysShowProviders': { type: 'boolean', - description: localize('alwaysShowProviders', "Controls whether to always show the Source Control Provider section."), + description: localize('alwaysShowProviders', "Controls whether to show the Source Control Provider section even when there's only one Provider registered."), default: false }, 'scm.providers.visible': { @@ -96,6 +94,17 @@ Registry.as(ConfigurationExtensions.Configuration).regis type: 'boolean', description: localize('alwaysShowActions', "Controls whether inline actions are always visible in the Source Control view."), default: false + }, + 'scm.countBadge': { + type: 'string', + enum: ['all', 'focused', 'off'], + enumDescriptions: [ + localize('scm.countBadge.all', "Show the sum of all Source Control Providers count badges."), + localize('scm.countBadge.focused', "Show the count badge of the focused Source Control Provider."), + localize('scm.countBadge.off', "Disable the Source Control count badge.") + ], + description: localize('scm.countBadge', "Controls the Source Control count badge."), + default: 'all' } } }); diff --git a/src/vs/workbench/contrib/scm/browser/scmActivity.ts b/src/vs/workbench/contrib/scm/browser/scmActivity.ts index aaa9676f892..a76d60e0e0e 100644 --- a/src/vs/workbench/contrib/scm/browser/scmActivity.ts +++ b/src/vs/workbench/contrib/scm/browser/scmActivity.ts @@ -14,92 +14,45 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { commonPrefixLength } from 'vs/base/common/strings'; -import { ILogService } from 'vs/platform/log/common/log'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -export class StatusUpdater implements IWorkbenchContribution { - - private readonly badgeDisposable = new MutableDisposable(); - private disposables: IDisposable[] = []; - - constructor( - @ISCMService private readonly scmService: ISCMService, - @IActivityService private readonly activityService: IActivityService, - @ILogService private readonly logService: ILogService - ) { - for (const repository of this.scmService.repositories) { - this.onDidAddRepository(repository); - } - - this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); - this.render(); - } - - private onDidAddRepository(repository: ISCMRepository): void { - const provider = repository.provider; - const onDidChange = Event.any(provider.onDidChange, provider.onDidChangeResources); - const changeDisposable = onDidChange(() => this.render()); - - const onDidRemove = Event.filter(this.scmService.onDidRemoveRepository, e => e === repository); - const removeDisposable = onDidRemove(() => { - disposable.dispose(); - this.disposables = this.disposables.filter(d => d !== removeDisposable); - this.render(); - }); - - const disposable = combinedDisposable(changeDisposable, removeDisposable); - this.disposables.push(disposable); - } - - private render(): void { - this.badgeDisposable.clear(); - - const count = this.scmService.repositories.reduce((r, repository) => { - if (typeof repository.provider.count === 'number') { - return r + repository.provider.count; - } else { - return r + repository.provider.groups.elements.reduce((r, g) => r + g.elements.length, 0); - } - }, 0); - - // TODO@joao: remove - this.logService.trace('SCM#StatusUpdater.render', count); - - if (count > 0) { - const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); - this.badgeDisposable.value = this.activityService.showActivity(VIEWLET_ID, badge, 'scm-viewlet-label'); - } else { - this.badgeDisposable.clear(); - } - } - - dispose(): void { - this.badgeDisposable.dispose(); - this.disposables = dispose(this.disposables); +function getCount(repository: ISCMRepository): number { + if (typeof repository.provider.count === 'number') { + return repository.provider.count; + } else { + return repository.provider.groups.elements.reduce((r, g) => r + g.elements.length, 0); } } -export class StatusBarController implements IWorkbenchContribution { +export class SCMStatusController implements IWorkbenchContribution { private statusBarDisposable: IDisposable = Disposable.None; private focusDisposable: IDisposable = Disposable.None; private focusedRepository: ISCMRepository | undefined = undefined; private focusedProviderContextKey: IContextKey; + private readonly badgeDisposable = new MutableDisposable(); private disposables: IDisposable[] = []; constructor( @ISCMService private readonly scmService: ISCMService, @IStatusbarService private readonly statusbarService: IStatusbarService, - @IContextKeyService contextKeyService: IContextKeyService, - @IEditorService private readonly editorService: IEditorService + @IContextKeyService readonly contextKeyService: IContextKeyService, + @IActivityService private readonly activityService: IActivityService, + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { this.focusedProviderContextKey = contextKeyService.createKey('scmProvider', undefined); this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + const onDidChangeSCMCountBadge = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.countBadge')); + onDidChangeSCMCountBadge(this.renderActivityCount, this, this.disposables); + for (const repository of this.scmService.repositories) { this.onDidAddRepository(repository); } editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.disposables); + this.renderActivityCount(); } private onDidActiveEditorChange(): void { @@ -138,7 +91,11 @@ export class StatusBarController implements IWorkbenchContribution { } private onDidAddRepository(repository: ISCMRepository): void { - const changeDisposable = repository.onDidFocus(() => this.onDidFocusRepository(repository)); + const focusDisposable = repository.onDidFocus(() => this.onDidFocusRepository(repository)); + + const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources); + const changeDisposable = onDidChange(() => this.renderActivityCount()); + const onDidRemove = Event.filter(this.scmService.onDidRemoveRepository, e => e === repository); const removeDisposable = onDidRemove(() => { disposable.dispose(); @@ -149,9 +106,11 @@ export class StatusBarController implements IWorkbenchContribution { } else if (this.focusedRepository === repository) { this.scmService.repositories[0].focus(); } + + this.renderActivityCount(); }); - const disposable = combinedDisposable(changeDisposable, removeDisposable); + const disposable = combinedDisposable(focusDisposable, changeDisposable, removeDisposable); this.disposables.push(disposable); if (!this.focusedRepository) { @@ -169,13 +128,14 @@ export class StatusBarController implements IWorkbenchContribution { this.focusDisposable.dispose(); if (repository && repository.provider.onDidChangeStatusBarCommands) { - this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.render(repository)); + this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository)); } - this.render(repository); + this.renderStatusBar(repository); + this.renderActivityCount(); } - private render(repository: ISCMRepository | undefined): void { + private renderStatusBar(repository: ISCMRepository | undefined): void { this.statusBarDisposable.dispose(); if (!repository) { @@ -200,9 +160,31 @@ export class StatusBarController implements IWorkbenchContribution { this.statusBarDisposable = disposables; } + private renderActivityCount(): void { + this.badgeDisposable.clear(); + + const countBadgeType = this.configurationService.getValue<'all' | 'focused' | 'off'>('scm.countBadge'); + + let count = 0; + + if (countBadgeType === 'all') { + count = this.scmService.repositories.reduce((r, repository) => r + getCount(repository), 0); + } else if (countBadgeType === 'focused' && this.focusedRepository) { + count = getCount(this.focusedRepository); + } + + if (count > 0) { + const badge = new NumberBadge(count, num => localize('scmPendingChangesBadge', '{0} pending changes', num)); + this.badgeDisposable.value = this.activityService.showActivity(VIEWLET_ID, badge, 'scm-viewlet-label'); + } else { + this.badgeDisposable.clear(); + } + } + dispose(): void { this.focusDisposable.dispose(); this.statusBarDisposable.dispose(); + this.badgeDisposable.dispose(); this.disposables = dispose(this.disposables); } } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 00e6d9356ce..30ef53951d0 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -49,6 +49,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; +import { nextTick } from 'vs/base/common/process'; export interface ISpliceEvent { index: number; @@ -324,7 +325,7 @@ export class MainPanel extends ViewletPanel { } private onListSelectionChange(e: IListEvent): void { - if (e.elements.length > 0) { + if (e.browserEvent && e.elements.length > 0) { const scrollTop = this.list.scrollTop; this.viewModel.setVisibleRepositories(e.elements); this.list.scrollTop = scrollTop; @@ -332,7 +333,7 @@ export class MainPanel extends ViewletPanel { } private onListFocusChange(e: IListEvent): void { - if (e.elements.length > 0) { + if (e.browserEvent && e.elements.length > 0) { e.elements[0].focus(); } } @@ -797,7 +798,7 @@ export class RepositoryPanel extends ViewletPanel { const triggerValidation = () => validationDelayer.trigger(validate); - this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { flexibleHeight: true }); + this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { flexibleHeight: true, flexibleMaxHeight: 134 }); this.inputBox.setEnabled(this.isBodyVisible()); this._register(attachInputBoxStyler(this.inputBox, this.themeService)); this._register(this.inputBox); @@ -896,11 +897,8 @@ export class RepositoryPanel extends ViewletPanel { const listHeight = height - (editorHeight + 12 /* margin */); this.listContainer.style.height = `${listHeight}px`; this.list.layout(listHeight, width); - - toggleClass(this.inputBoxContainer, 'scroll', editorHeight >= 134); } else { addClass(this.inputBoxContainer, 'hidden'); - removeClass(this.inputBoxContainer, 'scroll'); this.listContainer.style.height = `${height}px`; this.list.layout(height, width); @@ -1105,6 +1103,9 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { } } + private readonly _onDidChangeRepositories = new Emitter(); + private readonly onDidFinishStartup = Event.once(Event.debounce(this._onDidChangeRepositories.event, () => null, 1000)); + constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @@ -1133,6 +1134,9 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { this.onDidChangeRepositories(); } })); + + this._register(this.onDidFinishStartup(this.onAfterStartup, this)); + this._register(this.viewsModel.onDidRemove(this.onDidHideView, this)); } create(parent: HTMLElement): void { @@ -1193,20 +1197,28 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { if (alwaysShowProviders && repositoryCount > 0) { this.viewsModel.setVisible(MainPanel.ID, true); - } else if (!alwaysShowProviders && repositoryCount === 1) { - this.viewsModel.setVisible(MainPanel.ID, false); - } else if (this.repositoryCount < 2 && repositoryCount >= 2) { - this.viewsModel.setVisible(MainPanel.ID, true); - } else if (this.repositoryCount >= 2 && repositoryCount === 1) { - this.viewsModel.setVisible(MainPanel.ID, false); - } - - if (repositoryCount === 1) { - this.viewsModel.setVisible(this.viewDescriptors[0].id, true); } toggleClass(this.el, 'empty', repositoryCount === 0); this.repositoryCount = repositoryCount; + + this._onDidChangeRepositories.fire(); + } + + private onAfterStartup(): void { + if (this.repositoryCount > 0 && this.viewDescriptors.every(d => !this.viewsModel.isVisible(d.id))) { + this.viewsModel.setVisible(this.viewDescriptors[0].id, true); + } + } + + private onDidHideView(): void { + nextTick(() => { + if (this.repositoryCount > 0 && this.viewDescriptors.every(d => !this.viewsModel.isVisible(d.id))) { + const alwaysShowProviders = this.configurationService.getValue('scm.alwaysShowProviders') || false; + this.viewsModel.setVisible(MainPanel.ID, alwaysShowProviders || this.repositoryCount > 1); + this.viewsModel.setVisible(this.viewDescriptors[0].id, true); + } + }); } focus(): void { diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index 3181a10d8d1..f530bce968a 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -60,6 +60,20 @@ display: inline-flex; } +.search-view .search-widget .replace-input { + position: relative; + display: flex; + display: -webkit-flex; + vertical-align: middle; + width: auto !important; +} + +.search-view .search-widget .replace-input > .controls { + position: absolute; + top: 3px; + right: 2px; +} + .search-view .search-widget .replace-container.disabled { display: none; } diff --git a/src/vs/workbench/contrib/search/browser/media/stop-dark.svg b/src/vs/workbench/contrib/search/browser/media/stop-dark.svg index 7e6319104da..890af298354 100644 --- a/src/vs/workbench/contrib/search/browser/media/stop-dark.svg +++ b/src/vs/workbench/contrib/search/browser/media/stop-dark.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/search/browser/media/stop-hc.svg b/src/vs/workbench/contrib/search/browser/media/stop-hc.svg index a879a194c7a..1c88dfb60a7 100644 --- a/src/vs/workbench/contrib/search/browser/media/stop-hc.svg +++ b/src/vs/workbench/contrib/search/browser/media/stop-hc.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/search/browser/media/stop-light.svg b/src/vs/workbench/contrib/search/browser/media/stop-light.svg index 10d05f5d8ab..7e41aeff589 100644 --- a/src/vs/workbench/contrib/search/browser/media/stop-light.svg +++ b/src/vs/workbench/contrib/search/browser/media/stop-light.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/search/browser/openFileHandler.ts b/src/vs/workbench/contrib/search/browser/openFileHandler.ts index 495381f6beb..39d390233dd 100644 --- a/src/vs/workbench/contrib/search/browser/openFileHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openFileHandler.ts @@ -44,7 +44,7 @@ export class FileQuickOpenModel extends QuickOpenModel { } export class FileEntry extends EditorQuickOpenEntry { - private range: IRange | null; + private range: IRange | null = null; constructor( private resource: URI, @@ -112,7 +112,7 @@ export interface IOpenFileOptions { } export class OpenFileHandler extends QuickOpenHandler { - private options: IOpenFileOptions; + private options: IOpenFileOptions | undefined; private queryBuilder: QueryBuilder; private cacheState: CacheState; @@ -277,7 +277,7 @@ export class CacheState { private query: IFileQuery; private loadingPhase = LoadingPhase.Created; - private promise: Promise; + private promise: Promise | undefined; constructor(cacheQuery: (cacheKey: string) => IFileQuery, private doLoad: (query: IFileQuery) => Promise, private doDispose: (cacheKey: string) => Promise, private previous: CacheState | null) { this.query = cacheQuery(this._cacheKey); diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 207662ab1ae..4a0a138d83b 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -27,7 +27,7 @@ import { Schemas } from 'vs/base/common/network'; import { IOpenerService } from 'vs/platform/opener/common/opener'; class SymbolEntry extends EditorQuickOpenEntry { - private bearingResolve: Promise; + private bearingResolve: Promise | undefined; constructor( private bearing: IWorkspaceSymbol, diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 8d827d437d3..db23b909109 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -213,37 +213,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -CommandsRegistry.registerCommand({ - id: Constants.RevealInSideBarForSearchResults, - handler: (accessor, fileMatch: FileMatch) => { - const viewletService = accessor.get(IViewletService); - const explorerService = accessor.get(IExplorerService); - const contextService = accessor.get(IWorkspaceContextService); - const uri = fileMatch.resource; - - viewletService.openViewlet(VIEWLET_ID_FILES, false).then((viewlet: ExplorerViewlet) => { - if (uri && contextService.isInsideWorkspace(uri)) { - const explorerView = viewlet.getExplorerView(); - if (explorerView) { - explorerView.setExpanded(true); - explorerService.select(uri, true).then(() => explorerView.focus(), onUnexpectedError); - } - } - }); - } -}); - -const RevealInSideBarForSearchResultsCommand: ICommandAction = { - id: Constants.RevealInSideBarForSearchResults, - title: nls.localize('revealInSideBar', "Reveal in Explorer") -}; - -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: RevealInSideBarForSearchResultsCommand, - when: ContextKeyExpr.and(Constants.FileFocusKey, Constants.HasSearchResults), - group: '2_files', -}); - MenuRegistry.appendMenuItem(MenuId.SearchContext, { command: { id: Constants.ReplaceActionId, @@ -343,6 +312,38 @@ CommandsRegistry.registerCommand({ handler: clearHistoryCommand }); +CommandsRegistry.registerCommand({ + id: Constants.RevealInSideBarForSearchResults, + handler: (accessor, fileMatch: FileMatch) => { + const viewletService = accessor.get(IViewletService); + const explorerService = accessor.get(IExplorerService); + const contextService = accessor.get(IWorkspaceContextService); + const uri = fileMatch.resource; + + viewletService.openViewlet(VIEWLET_ID_FILES, false).then((viewlet: ExplorerViewlet) => { + if (uri && contextService.isInsideWorkspace(uri)) { + const explorerView = viewlet.getExplorerView(); + if (explorerView) { + explorerView.setExpanded(true); + explorerService.select(uri, true).then(() => explorerView.focus(), onUnexpectedError); + } + } + }); + } +}); + +const RevealInSideBarForSearchResultsCommand: ICommandAction = { + id: Constants.RevealInSideBarForSearchResults, + title: nls.localize('revealInSideBar', "Reveal in Explorer") +}; + +MenuRegistry.appendMenuItem(MenuId.SearchContext, { + command: RevealInSideBarForSearchResultsCommand, + when: ContextKeyExpr.and(Constants.FileFocusKey, Constants.HasSearchResults), + group: 'search_3', + order: 1 +}); + const clearSearchHistoryLabel = nls.localize('clearSearchHistoryLabel', "Clear Search History"); const ClearSearchHistoryCommand: ICommandAction = { id: Constants.ClearSearchHistoryCommandId, diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index ca96a4cd1ee..9eac783512a 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -7,14 +7,11 @@ import * as DOM from 'vs/base/browser/dom'; import { Action } from 'vs/base/common/actions'; 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/path'; import { isWindows, OS } from 'vs/base/common/platform'; import { repeat } from 'vs/base/common/strings'; -import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ILabelService } from 'vs/platform/label/common/label'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -667,14 +664,11 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { } } -function uriToClipboardString(resource: URI): string { - return resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(resource.fsPath)) : resource.toString(); -} - export const copyPathCommand: ICommandHandler = async (accessor, fileMatch: FileMatch | FolderMatch) => { const clipboardService = accessor.get(IClipboardService); + const labelService = accessor.get(ILabelService); - const text = uriToClipboardString(fileMatch.resource); + const text = labelService.getUriLabel(fileMatch.resource, { noPrefix: true }); await clipboardService.writeText(text); }; @@ -706,25 +700,26 @@ function matchToString(match: Match, indent = 0): string { } const lineDelimiter = isWindows ? '\r\n' : '\n'; -function fileMatchToString(fileMatch: FileMatch, maxMatches: number): { text: string, count: number } { +function fileMatchToString(fileMatch: FileMatch, maxMatches: number, labelService: ILabelService): { text: string, count: number } { const matchTextRows = fileMatch.matches() .sort(searchMatchComparer) .slice(0, maxMatches) .map(match => matchToString(match, 2)); + const uriString = labelService.getUriLabel(fileMatch.resource, { noPrefix: true }); return { - text: `${uriToClipboardString(fileMatch.resource)}${lineDelimiter}${matchTextRows.join(lineDelimiter)}`, + text: `${uriString}${lineDelimiter}${matchTextRows.join(lineDelimiter)}`, count: matchTextRows.length }; } -function folderMatchToString(folderMatch: FolderMatch | BaseFolderMatch, maxMatches: number): { text: string, count: number } { +function folderMatchToString(folderMatch: FolderMatch | BaseFolderMatch, maxMatches: number, labelService: ILabelService): { text: string, count: number } { const fileResults: string[] = []; let numMatches = 0; const matches = folderMatch.matches().sort(searchMatchComparer); for (let i = 0; i < folderMatch.fileCount() && numMatches < maxMatches; i++) { - const fileResult = fileMatchToString(matches[i], maxMatches - numMatches); + const fileResult = fileMatchToString(matches[i], maxMatches - numMatches, labelService); numMatches += fileResult.count; fileResults.push(fileResult.text); } @@ -738,14 +733,15 @@ function folderMatchToString(folderMatch: FolderMatch | BaseFolderMatch, maxMatc const maxClipboardMatches = 1e4; export const copyMatchCommand: ICommandHandler = async (accessor, match: RenderableMatch) => { const clipboardService = accessor.get(IClipboardService); + const labelService = accessor.get(ILabelService); let text: string | undefined; if (match instanceof Match) { text = matchToString(match); } else if (match instanceof FileMatch) { - text = fileMatchToString(match, maxClipboardMatches).text; + text = fileMatchToString(match, maxClipboardMatches, labelService).text; } else if (match instanceof BaseFolderMatch) { - text = folderMatchToString(match, maxClipboardMatches).text; + text = folderMatchToString(match, maxClipboardMatches, labelService).text; } if (text) { @@ -753,12 +749,12 @@ export const copyMatchCommand: ICommandHandler = async (accessor, match: Rendera } }; -function allFolderMatchesToString(folderMatches: Array, maxMatches: number): string { +function allFolderMatchesToString(folderMatches: Array, maxMatches: number, labelService: ILabelService): string { const folderResults: string[] = []; let numMatches = 0; folderMatches = folderMatches.sort(searchMatchComparer); for (let i = 0; i < folderMatches.length && numMatches < maxMatches; i++) { - const folderResult = folderMatchToString(folderMatches[i], maxMatches - numMatches); + const folderResult = folderMatchToString(folderMatches[i], maxMatches - numMatches, labelService); if (folderResult.count) { numMatches += folderResult.count; folderResults.push(folderResult.text); @@ -772,12 +768,13 @@ export const copyAllCommand: ICommandHandler = async (accessor) => { const viewletService = accessor.get(IViewletService); const panelService = accessor.get(IPanelService); const clipboardService = accessor.get(IClipboardService); + const labelService = accessor.get(ILabelService); const searchView = getSearchView(viewletService, panelService); if (searchView) { const root = searchView.searchResult; - const text = allFolderMatchesToString(root.folderMatches(), maxClipboardMatches); + const text = allFolderMatchesToString(root.folderMatches(), maxClipboardMatches, labelService); await clipboardService.writeText(text); } }; diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 2daad95ca68..33e16e9f22d 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -61,6 +61,7 @@ import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/v import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; const $ = dom.$; @@ -153,6 +154,7 @@ export class SearchView extends ViewletPanel { @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IKeybindingService keybindingService: IKeybindingService, @IStorageService storageService: IStorageService, + @IOpenerService private readonly openerService: IOpenerService ) { super({ ...(options as IViewletPanelOptions), id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService); @@ -364,6 +366,7 @@ export class SearchView extends ViewletPanel { const searchHistory = history.search || this.viewletState['query.searchHistory'] || []; const replaceHistory = history.replace || this.viewletState['query.replaceHistory'] || []; const showReplace = typeof this.viewletState['view.showReplace'] === 'boolean' ? this.viewletState['view.showReplace'] : true; + const preserveCase = this.viewletState['query.preserveCase'] === true; this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, container, { value: contentPattern, @@ -372,7 +375,8 @@ export class SearchView extends ViewletPanel { isCaseSensitive: isCaseSensitive, isWholeWords: isWholeWords, searchHistory: searchHistory, - replaceHistory: replaceHistory + replaceHistory: replaceHistory, + preserveCase: preserveCase })); if (showReplace) { @@ -390,6 +394,12 @@ export class SearchView extends ViewletPanel { this.viewModel.replaceActive = state; this.refreshTree(); })); + + this._register(this.searchWidget.onPreserveCaseChange((state) => { + this.viewModel.preserveCase = state; + this.refreshTree(); + })); + this._register(this.searchWidget.onReplaceValueChanged((value) => { this.viewModel.replaceString = this.searchWidget.getReplaceValue(); this.delayedRefresh.trigger(() => this.refreshTree()); @@ -1455,7 +1465,7 @@ export class SearchView extends ViewletPanel { this.openSettings('.exclude'); } - private openSettings(query: string): Promise { + private openSettings(query: string): Promise { const options: ISettingsEditorOptions = { query }; return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? this.preferencesService.openWorkspaceSettings(undefined, options) : @@ -1465,7 +1475,7 @@ export class SearchView extends ViewletPanel { private onLearnMore = (e: MouseEvent): void => { dom.EventHelper.stop(e, false); - window.open('https://go.microsoft.com/fwlink/?linkid=853977'); + this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=853977')); } private updateSearchResultCount(disregardExcludesAndIgnores?: boolean): void { @@ -1641,6 +1651,7 @@ export class SearchView extends ViewletPanel { const patternExcludes = this.inputPatternExcludes.getValue().trim(); const patternIncludes = this.inputPatternIncludes.getValue().trim(); const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles(); + const preserveCase = this.viewModel.preserveCase; this.viewletState['query.contentPattern'] = contentPattern; this.viewletState['query.regex'] = isRegex; @@ -1649,6 +1660,7 @@ export class SearchView extends ViewletPanel { this.viewletState['query.folderExclusions'] = patternExcludes; this.viewletState['query.folderIncludes'] = patternIncludes; this.viewletState['query.useExcludesAndIgnoreFiles'] = useExcludesAndIgnoreFiles; + this.viewletState['query.preserveCase'] = preserveCase; const isReplaceShown = this.searchAndReplaceWidget.isReplaceShown(); this.viewletState['view.showReplace'] = isReplaceShown; diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 6cfbb4d5542..26b29b280c1 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -33,6 +33,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; export interface ISearchWidgetOptions { value?: string; @@ -42,6 +43,7 @@ export interface ISearchWidgetOptions { isWholeWords?: boolean; searchHistory?: string[]; replaceHistory?: string[]; + preserveCase?: boolean; } class ReplaceAllAction extends Action { @@ -97,6 +99,7 @@ export class SearchWidget extends Widget { replaceInputFocusTracker: dom.IFocusTracker; private replaceInputBoxFocused: IContextKey; private _replaceHistoryDelayer: Delayer; + private _preserveCase: Checkbox; private ignoreGlobalFindBufferOnNextFocus = false; private previousGlobalFindBufferValue: string; @@ -113,6 +116,9 @@ export class SearchWidget extends Widget { private _onReplaceStateChange = this._register(new Emitter()); readonly onReplaceStateChange: Event = this._onReplaceStateChange.event; + private _onPreserveCaseChange = this._register(new Emitter()); + readonly onPreserveCaseChange: Event = this._onPreserveCaseChange.event; + private _onReplaceValueChanged = this._register(new Emitter()); readonly onReplaceValueChanged: Event = this._onReplaceValueChanged.event; @@ -333,13 +339,34 @@ export class SearchWidget extends Widget { private renderReplaceInput(parent: HTMLElement, options: ISearchWidgetOptions): void { this.replaceContainer = dom.append(parent, dom.$('.replace-container.disabled')); - const replaceBox = dom.append(this.replaceContainer, dom.$('.input-box')); + const replaceBox = dom.append(this.replaceContainer, dom.$('.replace-input')); + this.replaceInput = this._register(new ContextScopedHistoryInputBox(replaceBox, this.contextViewService, { ariaLabel: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview or Escape to cancel'), placeholder: nls.localize('search.replace.placeHolder', "Replace"), history: options.replaceHistory || [], flexibleHeight: true }, this.contextKeyService)); + + this._preserveCase = this._register(new Checkbox({ + actionClassName: 'monaco-preserve-case', + title: nls.localize('label.preserveCaseCheckbox', "Preserve Case"), + isChecked: !!options.preserveCase, + })); + + this._register(this._preserveCase.onChange(viaKeyboard => { + if (!viaKeyboard) { + this.replaceInput.focus(); + this._onPreserveCaseChange.fire(this._preserveCase.checked); + } + })); + + let controls = document.createElement('div'); + controls.className = 'controls'; + controls.style.display = 'block'; + controls.appendChild(this._preserveCase.domNode); + replaceBox.appendChild(controls); + this._register(attachInputBoxStyler(this.replaceInput, this.themeService)); this.onkeydown(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent)); this.replaceInput.value = options.replaceValue || ''; diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 9c4d170c1d5..1962709126f 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -14,7 +14,7 @@ import * as objects from 'vs/base/common/objects'; import { lcut } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; -import { FindMatch, IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { FindMatch, IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness, MinimapPosition } from 'vs/editor/common/model'; 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'; @@ -22,11 +22,12 @@ import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; 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 { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { memoize } from 'vs/base/common/decorators'; export class Match { @@ -74,6 +75,7 @@ export class Match { return this._range; } + @memoize preview(): { before: string; inside: string; after: string; } { let before = this._oneLinePreviewText.substring(0, this._rangeInPreviewText.startColumn - 1), inside = this.getMatchString(), @@ -101,17 +103,17 @@ export class Match { } const fullMatchText = this.fullMatchText(); - let replaceString = searchModel.replacePattern.getReplaceString(fullMatchText); + let replaceString = searchModel.replacePattern.getReplaceString(fullMatchText, searchModel.preserveCase); // If match string is not matching then regex pattern has a lookahead expression if (replaceString === null) { const fullMatchTextWithTrailingContent = this.fullMatchText(true); - replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithTrailingContent); + replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithTrailingContent, searchModel.preserveCase); // Search/find normalize line endings - check whether \r prevents regex from matching if (replaceString === null) { const fullMatchTextWithoutCR = fullMatchTextWithTrailingContent.replace(/\r\n/g, '\n'); - replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithoutCR); + replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithoutCR, searchModel.preserveCase); } } @@ -155,6 +157,10 @@ export class FileMatch extends Disposable implements IFileMatch { overviewRuler: { color: themeColorFromId(overviewRulerFindMatchForeground), position: OverviewRulerLane.Center + }, + minimap: { + color: themeColorFromId(minimapFindMatch), + position: MinimapPosition.Inline } }); @@ -164,6 +170,10 @@ export class FileMatch extends Disposable implements IFileMatch { overviewRuler: { color: themeColorFromId(overviewRulerFindMatchForeground), position: OverviewRulerLane.Center + }, + minimap: { + color: themeColorFromId(minimapFindMatch), + position: MinimapPosition.Inline } }); @@ -713,11 +723,19 @@ export class SearchResult extends Disposable { this.disposeMatches(); } - remove(matches: FileMatch | FileMatch[]): void { + remove(matches: FileMatch | FolderMatch | (FileMatch | FolderMatch)[]): void { if (!Array.isArray(matches)) { matches = [matches]; } + matches.forEach(m => { + if (m instanceof FolderMatch) { + m.clear(); + } + }); + + matches = matches.filter(m => m instanceof FileMatch); + const { byFolder, other } = this.groupFilesByFolder(matches); byFolder.forEach(matches => { if (!matches.length) { @@ -732,10 +750,6 @@ export class SearchResult extends Disposable { } } - removeFolder(match: FolderMatch): void { - match.clear(); - } - replace(match: FileMatch): Promise { return this.getFolderMatch(match.resource).replace(match); } @@ -881,6 +895,7 @@ export class SearchModel extends Disposable { private _replaceActive: boolean = false; private _replaceString: string | null = null; private _replacePattern: ReplacePattern | null = null; + private _preserveCase: boolean = false; private readonly _onReplaceTermChanged: Emitter = this._register(new Emitter()); readonly onReplaceTermChanged: Event = this._onReplaceTermChanged.event; @@ -912,6 +927,14 @@ export class SearchModel extends Disposable { return this._replaceString || ''; } + set preserveCase(value: boolean) { + this._preserveCase = value; + } + + get preserveCase(): boolean { + return this._preserveCase; + } + set replaceString(replaceString: string) { this._replaceString = replaceString; if (this._searchQuery) { 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 0c14fd010f7..ed8fd458877 100644 --- a/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts @@ -14,6 +14,7 @@ import { IFolderQuery, IPatternInfo, QueryType, ITextQuery, IFileQuery } from 'v import { IWorkspaceContextService, toWorkspaceFolder, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; +import { isWindows } from 'vs/base/common/platform'; const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true, useGlobalIgnoreFiles: true }; @@ -1032,7 +1033,7 @@ function getUri(...slashPathParts: string[]): uri { } function fixPath(...slashPathParts: string[]): string { - if (process.platform === 'win32' && slashPathParts.length && !slashPathParts[0].match(/^c:/i)) { + if (isWindows && slashPathParts.length && !slashPathParts[0].match(/^c:/i)) { slashPathParts.unshift('c:'); } 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 ba00d3b20a1..7ad9e679d1f 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -18,6 +18,7 @@ import { IFileMatch, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchPro 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'; +import * as process from 'vs/base/common/process'; const nullEvent = new class { id: number; diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts index 5c84fba2775..e15578264ec 100644 --- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts @@ -168,13 +168,14 @@ class InsertSnippetAction extends EditorAction { return quickInputService.pick(picks, { matchOnDetail: true }).then(pick => resolve(pick && pick.snippet), reject); } }).then(async snippet => { + if (!snippet) { + return; + } let clipboardText: string | undefined; if (snippet.needsClipboard) { clipboardText = await clipboardService.readText(); } - if (snippet) { - SnippetController2.get(editor).insert(snippet.codeSnippet, { clipboardText }); - } + SnippetController2.get(editor).insert(snippet.codeSnippet, { clipboardText }); }); } } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index ec5d1ce087b..54888ea75b7 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -21,7 +21,7 @@ export class SnippetCompletion implements CompletionItem { label: string; detail: string; insertText: string; - documentation: MarkdownString; + documentation?: MarkdownString; range: IRange; sortText: string; kind: CompletionItemKind; diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts index 8154a9da364..569598138e8 100644 --- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts +++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts @@ -31,8 +31,8 @@ export class TabCompletionController implements editorCommon.IEditorContribution private _hasSnippets: IContextKey; private _activeSnippets: Snippet[] = []; - private _enabled: boolean; - private _selectionListener: IDisposable; + private _enabled?: boolean; + private _selectionListener?: IDisposable; private readonly _configListener: IDisposable; constructor( 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 179c565335d..011b14816fb 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -32,8 +32,8 @@ class PartsSplash { private readonly _disposables = new DisposableStore(); - private _didChangeTitleBarStyle: boolean; - private _lastBaseTheme: string; + private _didChangeTitleBarStyle?: boolean; + private _lastBaseTheme?: string; private _lastBackground?: string; constructor( diff --git a/src/vs/workbench/contrib/stats/common/workspaceStats.ts b/src/vs/workbench/contrib/stats/common/workspaceStats.ts new file mode 100644 index 00000000000..7bd69fc83cd --- /dev/null +++ b/src/vs/workbench/contrib/stats/common/workspaceStats.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; + +export type Tags = { [index: string]: boolean | number | string | undefined }; + +export const IWorkspaceStatsService = createDecorator('workspaceStatsService'); + +export interface IWorkspaceStatsService { + _serviceBrand: any; + + getTags(): Promise; + + /** + * Returns an id for the workspace, different from the id returned by the context service. A hash based + * on the folder uri or workspace configuration, not time-based, and undefined for empty workspaces. + */ + getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined; + + getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit?: boolean): Promise; +} diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts index 89baf8b21e4..7457e3b6bcd 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts @@ -14,7 +14,8 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { endsWith } from 'vs/base/common/strings'; import { ITextFileService, } from 'vs/workbench/services/textfile/common/textfiles'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; +import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; @@ -135,20 +136,6 @@ export function getHashedRemotesFromConfig(text: string, stripEndingDotGit: bool }); } -export function getHashedRemotesFromUri(workspaceUri: URI, fileService: IFileService, textFileService: ITextFileService, stripEndingDotGit: boolean = false): Promise { - const path = workspaceUri.path; - const uri = workspaceUri.with({ path: `${path !== '/' ? path : ''}/.git/config` }); - return fileService.exists(uri).then(exists => { - if (!exists) { - return []; - } - return textFileService.read(uri, { acceptTextOnly: true }).then( - content => getHashedRemotesFromConfig(content.value, stripEndingDotGit), - err => [] // ignore missing or binary file - ); - }); -} - export class WorkspaceStats implements IWorkbenchContribution { constructor( @@ -175,10 +162,20 @@ export class WorkspaceStats implements IWorkbenchContribution { this.reportProxyStats(); const diagnosticsChannel = this.sharedProcessService.getChannel('diagnostics'); - diagnosticsChannel.call('reportWorkspaceStats', this.contextService.getWorkspace()); + diagnosticsChannel.call('reportWorkspaceStats', this.getWorkspaceInformation()); } - + private getWorkspaceInformation(): IWorkspaceInformation { + const workspace = this.contextService.getWorkspace(); + const state = this.contextService.getWorkbenchState(); + const id = this.workspaceStatsService.getTelemetryWorkspaceId(workspace, state); + return { + id: workspace.id, + telemetryId: id, + folders: workspace.folders, + configuration: workspace.configuration + }; + } private reportWorkspaceTags(tags: Tags): void { /* __GDPR__ @@ -219,7 +216,7 @@ export class WorkspaceStats implements IWorkbenchContribution { private reportRemotes(workspaceUris: URI[]): void { Promise.all(workspaceUris.map(workspaceUri => { - return getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, true); + return this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, true); })).then(hashedRemotes => { /* __GDPR__ "workspace.hashedRemotes" : { diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts index ef0dc08081c..2cb249cba47 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts @@ -5,12 +5,11 @@ import * as crypto from 'crypto'; import { IFileService, IResolveFileResult, IFileStat } from 'vs/platform/files/common/files'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { INotificationService, IPromptChoice } from 'vs/platform/notification/common/notification'; +import { INotificationService, NeverShowAgainScope, INeverShowAgainOptions } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITextFileService, ITextFileContent } from 'vs/workbench/services/textfile/common/textfiles'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; @@ -18,11 +17,9 @@ import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspa import { localize } from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { joinPath } from 'vs/base/common/resources'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; - -export type Tags = { [index: string]: boolean | number | string | undefined }; - -const DISABLE_WORKSPACE_PROMPT_KEY = 'workspaces.dontPromptToOpen'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { getHashedRemotesFromConfig } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; const ModulesToLookFor = [ // Packages that suggest a node server @@ -92,14 +89,6 @@ const PyModulesToLookFor = [ 'botframework-connector' ]; -export const IWorkspaceStatsService = createDecorator('workspaceStatsService'); - -export interface IWorkspaceStatsService { - _serviceBrand: any; - getTags(): Promise; -} - - export class WorkspaceStatsService implements IWorkspaceStatsService { _serviceBrand: any; private _tags: Tags; @@ -111,7 +100,6 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { @IWindowService private readonly windowService: IWindowService, @INotificationService private readonly notificationService: INotificationService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IStorageService private readonly storageService: IStorageService, @ITextFileService private readonly textFileService: ITextFileService ) { } @@ -123,6 +111,42 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { return this._tags; } + public getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { + function createHash(uri: URI): string { + return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex'); + } + + let workspaceId: string | undefined; + switch (state) { + case WorkbenchState.EMPTY: + workspaceId = undefined; + break; + case WorkbenchState.FOLDER: + workspaceId = createHash(workspace.folders[0].uri); + break; + case WorkbenchState.WORKSPACE: + if (workspace.configuration) { + workspaceId = createHash(workspace.configuration); + } + } + + return workspaceId; + } + + getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit: boolean = false): Promise { + const path = workspaceUri.path; + const uri = workspaceUri.with({ path: `${path !== '/' ? path : ''}/.git/config` }); + return this.fileService.exists(uri).then(exists => { + if (!exists) { + return []; + } + return this.textFileService.read(uri, { acceptTextOnly: true }).then( + content => getHashedRemotesFromConfig(content.value, stripEndingDotGit), + err => [] // ignore missing or binary file + ); + }); + } + /* __GDPR__FRAGMENT__ "WorkspaceTags" : { "workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -225,25 +249,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { const state = this.contextService.getWorkbenchState(); const workspace = this.contextService.getWorkspace(); - function createHash(uri: URI): string { - return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex'); - } - - let workspaceId: string | undefined; - switch (state) { - case WorkbenchState.EMPTY: - workspaceId = undefined; - break; - case WorkbenchState.FOLDER: - workspaceId = createHash(workspace.folders[0].uri); - break; - case WorkbenchState.WORKSPACE: - if (workspace.configuration) { - workspaceId = createHash(workspace.configuration); - } - } - - tags['workspace.id'] = workspaceId; + tags['workspace.id'] = this.getTelemetryWorkspaceId(workspace, state); const { filesToOpenOrCreate, filesToDiff } = configuration; tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0; @@ -439,15 +445,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { } private doHandleWorkspaceFiles(folder: URI, workspaces: string[]): void { - if (this.storageService.getBoolean(DISABLE_WORKSPACE_PROMPT_KEY, StorageScope.WORKSPACE)) { - return; // prompt disabled by user - } - - const doNotShowAgain: IPromptChoice = { - label: localize('never again', "Don't Show Again"), - isSecondary: true, - run: () => this.storageService.store(DISABLE_WORKSPACE_PROMPT_KEY, true, StorageScope.WORKSPACE) - }; + const neverShowAgain: INeverShowAgainOptions = { id: 'workspaces.dontPromptToOpen', scope: NeverShowAgainScope.WORKSPACE, isSecondary: true }; // Prompt to open one workspace if (workspaces.length === 1) { @@ -456,7 +454,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { this.notificationService.prompt(Severity.Info, localize('workspaceFound', "This folder contains a workspace file '{0}'. Do you want to open it? [Learn more]({1}) about workspace files.", workspaceFile, 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{ label: localize('openWorkspace', "Open Workspace"), run: () => this.windowService.openWindow([{ workspaceUri: joinPath(folder, workspaceFile) }]) - }, doNotShowAgain]); + }], { neverShowAgain }); } // Prompt to select a workspace from many @@ -472,7 +470,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { } }); } - }, doNotShowAgain]); + }], { neverShowAgain }); } } @@ -503,3 +501,5 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { return arr.some(v => v.search(regEx) > -1) || undefined; } } + +registerSingleton(IWorkspaceStatsService, WorkspaceStatsService, true); diff --git a/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts index ec9ac3fb2b7..44cf53887ff 100644 --- a/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts @@ -16,6 +16,8 @@ import { ISurveyData } from 'vs/platform/product/common/product'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; class LanguageSurvey { @@ -25,7 +27,8 @@ class LanguageSurvey { notificationService: INotificationService, telemetryService: ITelemetryService, modelService: IModelService, - textFileService: ITextFileService + textFileService: ITextFileService, + openerService: IOpenerService ) { const SESSION_COUNT_KEY = `${data.surveyId}.sessionCount`; const LAST_SESSION_DATE_KEY = `${data.surveyId}.lastSessionDate`; @@ -94,7 +97,7 @@ class LanguageSurvey { run: () => { telemetryService.publicLog(`${data.surveyId}.survey/takeShortSurvey`); telemetryService.getTelemetryInfo().then(info => { - window.open(`${data.surveyUrl}?o=${encodeURIComponent(process.platform)}&v=${encodeURIComponent(pkg.version)}&m=${encodeURIComponent(info.machineId)}`); + openerService.open(URI.parse(`${data.surveyUrl}?o=${encodeURIComponent(process.platform)}&v=${encodeURIComponent(pkg.version)}&m=${encodeURIComponent(info.machineId)}`)); storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); }); @@ -126,11 +129,12 @@ class LanguageSurveysContribution implements IWorkbenchContribution { @INotificationService notificationService: INotificationService, @ITelemetryService telemetryService: ITelemetryService, @IModelService modelService: IModelService, - @ITextFileService textFileService: ITextFileService + @ITextFileService textFileService: ITextFileService, + @IOpenerService openerService: IOpenerService ) { product.surveys .filter(surveyData => surveyData.surveyId && surveyData.editCount && surveyData.languageId && surveyData.surveyUrl && surveyData.userProbability) - .map(surveyData => new LanguageSurvey(surveyData, storageService, notificationService, telemetryService, modelService, textFileService)); + .map(surveyData => new LanguageSurvey(surveyData, storageService, notificationService, telemetryService, modelService, textFileService, openerService)); } } diff --git a/src/vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts b/src/vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts index c11fd38db20..5e4433ce2ad 100644 --- a/src/vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts +++ b/src/vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts @@ -13,6 +13,8 @@ import pkg from 'vs/platform/product/node/package'; import product from 'vs/platform/product/node/product'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; const PROBABILITY = 0.15; const SESSION_COUNT_KEY = 'nps/sessionCount'; @@ -25,7 +27,8 @@ class NPSContribution implements IWorkbenchContribution { constructor( @IStorageService storageService: IStorageService, @INotificationService notificationService: INotificationService, - @ITelemetryService telemetryService: ITelemetryService + @ITelemetryService telemetryService: ITelemetryService, + @IOpenerService openerService: IOpenerService ) { const skipVersion = storageService.get(SKIP_VERSION_KEY, StorageScope.GLOBAL, ''); if (skipVersion) { @@ -64,7 +67,7 @@ class NPSContribution implements IWorkbenchContribution { label: nls.localize('takeSurvey', "Take Survey"), run: () => { telemetryService.getTelemetryInfo().then(info => { - window.open(`${product.npsSurveyUrl}?o=${encodeURIComponent(process.platform)}&v=${encodeURIComponent(pkg.version)}&m=${encodeURIComponent(info.machineId)}`); + openerService.open(URI.parse(`${product.npsSurveyUrl}?o=${encodeURIComponent(process.platform)}&v=${encodeURIComponent(pkg.version)}&m=${encodeURIComponent(info.machineId)}`)); storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); }); @@ -87,4 +90,4 @@ class NPSContribution implements IWorkbenchContribution { if (language === 'en' && product.npsSurveyUrl) { const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(NPSContribution, LifecyclePhase.Restored); -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 1dad734a227..461797d862c 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -73,6 +73,11 @@ import { RunAutomaticTasks } from 'vs/workbench/contrib/tasks/browser/runAutomat import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { format } from 'vs/base/common/jsonFormatter'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { applyEdits } from 'vs/base/common/jsonEdit'; +import { ITextEditor } from 'vs/workbench/common/editor'; +import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; @@ -174,10 +179,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private static nextHandle: number = 0; - private _schemaVersion: JsonSchemaVersion; - private _executionEngine: ExecutionEngine; - private _workspaceFolders: IWorkspaceFolder[]; - private _ignoredWorkspaceFolders: IWorkspaceFolder[]; + private _schemaVersion: JsonSchemaVersion | undefined; + private _executionEngine: ExecutionEngine | undefined; + private _workspaceFolders: IWorkspaceFolder[] | undefined; + private _ignoredWorkspaceFolders: IWorkspaceFolder[] | undefined; private _showIgnoreMessage?: boolean; private _providers: Map; private _providerTypes: Map; @@ -187,7 +192,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected _taskSystem?: ITaskSystem; protected _taskSystemListener?: IDisposable; - private _recentlyUsedTasks: LinkedMap; + private _recentlyUsedTasks: LinkedMap | undefined; protected _taskRunningState: IContextKey; @@ -220,7 +225,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @ITerminalInstanceService private readonly terminalInstanceService: ITerminalInstanceService, - @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @ITextModelService private readonly textModelResolverService: ITextModelService ) { super(); @@ -300,7 +306,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); CommandsRegistry.registerCommand('workbench.action.tasks.reRunTask', (accessor, arg) => { - this.reRunTaskCommand(arg); + this.reRunTaskCommand(); }); CommandsRegistry.registerCommand('workbench.action.tasks.restartTask', (accessor, arg) => { @@ -369,28 +375,28 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!this._workspaceFolders) { this.updateSetup(); } - return this._workspaceFolders; + return this._workspaceFolders!; } private get ignoredWorkspaceFolders(): IWorkspaceFolder[] { if (!this._ignoredWorkspaceFolders) { this.updateSetup(); } - return this._ignoredWorkspaceFolders; + return this._ignoredWorkspaceFolders!; } protected get executionEngine(): ExecutionEngine { if (this._executionEngine === undefined) { this.updateSetup(); } - return this._executionEngine; + return this._executionEngine!; } private get schemaVersion(): JsonSchemaVersion { if (this._schemaVersion === undefined) { this.updateSetup(); } - return this._schemaVersion; + return this._schemaVersion!; } private get showIgnoreMessage(): boolean { @@ -502,7 +508,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!this.versionAndEngineCompatible(filter)) { return Promise.resolve([]); } - return this.getGroupedTasks().then((map) => { + return this.getGroupedTasks(filter ? filter.type : undefined).then((map) => { if (!filter || !filter.type) { return map.all(); } @@ -769,6 +775,60 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return false; } + private openEditorAtTask(resource: URI | undefined, task: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | string | undefined): Promise { + if (resource === undefined) { + return Promise.resolve(undefined); + } + let selection: ITextEditorSelection | undefined; + return this.fileService.readFile(resource).then(content => content.value).then(async content => { + if (!content) { + return undefined; + } + if (task) { + const contentValue = content.toString(); + let stringValue: string; + if (typeof task === 'string') { + stringValue = task; + } else { + const model = (await this.textModelResolverService.createModelReference(resource)).object.textEditorModel; + const { tabSize, insertSpaces } = model.getOptions(); + const eol = model.getEOL(); + const edits = format(JSON.stringify(task), undefined, { eol, tabSize, insertSpaces }); + let stringified = applyEdits(JSON.stringify(task), edits); + const regex = new RegExp(eol + '\\t', 'g'); + stringified = stringified.replace(regex, eol + '\t\t\t'); + const twoTabs = '\t\t'; + stringValue = twoTabs + stringified.slice(0, stringified.length - 1) + twoTabs + stringified.slice(stringified.length - 1); + } + + const index = contentValue.indexOf(stringValue); + let startLineNumber = 1; + for (let i = 0; i < index; i++) { + if (contentValue.charAt(i) === '\n') { + startLineNumber++; + } + } + let endLineNumber = startLineNumber; + for (let i = 0; i < stringValue.length; i++) { + if (stringValue.charAt(i) === '\n') { + endLineNumber++; + } + } + selection = startLineNumber > 1 ? { startLineNumber, startColumn: startLineNumber === endLineNumber ? 4 : 3, endLineNumber, endColumn: startLineNumber === endLineNumber ? undefined : 4 } : undefined; + } + + return this.editorService.openEditor({ + resource, + options: { + pinned: false, + forceReload: true, // because content might have changed + selection, + revealInCenterIfOutsideViewport: !!selection + } + }); + }); + } + public customize(task: ContributedTask | CustomTask, properties?: CustomizationProperties, openConfig?: boolean): Promise { const workspaceFolder = task.getWorkspaceFolder(); if (!workspaceFolder) { @@ -796,6 +856,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length > 0 && Types.isStringArray(task.configurationProperties.problemMatchers)) { toCustomize.problemMatcher = task.configurationProperties.problemMatchers; } + if (task.configurationProperties.group) { + toCustomize.group = task.configurationProperties.group; + } } if (!toCustomize) { return Promise.resolve(undefined); @@ -864,14 +927,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer */ this.telemetryService.publicLog(AbstractTaskService.CustomizationTelemetryEventName, event); if (openConfig) { - let resource = workspaceFolder.toResource('.vscode/tasks.json'); - this.editorService.openEditor({ - resource, - options: { - pinned: false, - forceReload: true // because content might have changed - } - }); + this.openEditorAtTask(workspaceFolder.toResource('.vscode/tasks.json'), toCustomize); } }); } @@ -893,12 +949,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } else { resource = (this._workspaceFolders && (this._workspaceFolders.length > 0)) ? this._workspaceFolders[0].toResource('.vscode/tasks.json') : undefined; } - return this.editorService.openEditor({ - resource, - options: { - pinned: false - } - }).then(() => undefined); + return this.openEditorAtTask(resource, task ? task._label : undefined).then(() => undefined); } private createRunnableTask(tasks: TaskMap, group: TaskGroup): { task: Task; resolver: ITaskResolver } | undefined { @@ -1114,7 +1165,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract getTaskSystem(): ITaskSystem; - private getGroupedTasks(): Promise { + private getGroupedTasks(type?: string): Promise { return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), TaskDefinitionRegistry.onReady()]).then(() => { let validTypes: IStringDictionary = Object.create(null); TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); @@ -1149,10 +1200,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }; if (this.schemaVersion === JsonSchemaVersion.V2_0_0 && this._providers.size > 0) { - this._providers.forEach((provider) => { - counter++; - provider.provideTasks(validTypes).then(done, error); - }); + for (const [handle, provider] of this._providers) { + if ((type === undefined) || (type === this._providerTypes.get(handle))) { + counter++; + provider.provideTasks(validTypes).then(done, error); + } + } } else { resolve(result); } @@ -1567,7 +1620,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer nls.localize('TaskService.noWorkspace', "Tasks are only available on a workspace folder."), [{ label: nls.localize('TaskService.learnMore', "Learn More"), - run: () => window.open('https://code.visualstudio.com/docs/editor/tasks') + run: () => this.openerService.open(URI.parse('https://code.visualstudio.com/docs/editor/tasks')) }] ); return false; @@ -1757,7 +1810,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } - private reRunTaskCommand(arg?: any): void { + private reRunTaskCommand(): void { if (!this.canRunCommand()) { return; } diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index b3bccaf94e0..cbe96131cb7 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -12,6 +12,7 @@ import { RunOnOptions, Task, TaskRunSource } from 'vs/workbench/contrib/tasks/co import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; +import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic'; @@ -131,38 +132,27 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } -export class AllowAutomaticTaskRunning extends Action { +export class ManageAutomaticTaskRunning extends Action { - public static readonly ID = 'workbench.action.tasks.allowAutomaticRunning'; - public static readonly LABEL = nls.localize('workbench.action.tasks.allowAutomaticRunning', "Allow Automatic Tasks in Folder"); + public static readonly ID = 'workbench.action.tasks.manageAutomaticRunning'; + public static readonly LABEL = nls.localize('workbench.action.tasks.manageAutomaticRunning', "Manage Automatic Tasks in Folder"); constructor( id: string, label: string, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(id, label); } - public run(event?: any): Promise { - this.storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, true, StorageScope.WORKSPACE); - return Promise.resolve(undefined); - } -} - -export class DisallowAutomaticTaskRunning extends Action { - - public static readonly ID = 'workbench.action.tasks.disallowAutomaticRunning'; - public static readonly LABEL = nls.localize('workbench.action.tasks.disallowAutomaticRunning', "Disallow Automatic Tasks in Folder"); - - constructor( - id: string, label: string, - @IStorageService private readonly storageService: IStorageService - ) { - super(id, label); - } - - public run(event?: any): Promise { - this.storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, false, StorageScope.WORKSPACE); - return Promise.resolve(undefined); + public async run(event?: any): Promise { + const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.allowAutomaticTasks', "Allow Automatic Tasks in Folder") }; + const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.disallowAutomaticTasks', "Disallow Automatic Tasks in Folder") }; + const value = await this.quickInputService.pick([allowItem, disallowItem], { canPickMany: false }); + if (!value) { + return; + } + + this.storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, value === allowItem, StorageScope.WORKSPACE); } } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index de6fbc81085..92d2f493108 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -32,7 +32,7 @@ import { QuickOpenActionContributor } from '../browser/quickOpen'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { RunAutomaticTasks, AllowAutomaticTaskRunning, DisallowAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks'; +import { RunAutomaticTasks, ManageAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks'; let tasksCategory = nls.localize('tasksCategory', "Tasks"); @@ -40,8 +40,7 @@ const workbenchRegistry = Registry.as(Workbench workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowAutomaticTaskRunning, AllowAutomaticTaskRunning.ID, AllowAutomaticTaskRunning.LABEL), 'Tasks: Allow Automatic Tasks in Folder', tasksCategory); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowAutomaticTaskRunning, DisallowAutomaticTaskRunning.ID, DisallowAutomaticTaskRunning.LABEL), 'Tasks: Disallow Automatic Tasks in Folder', tasksCategory); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageAutomaticTaskRunning, ManageAutomaticTaskRunning.ID, ManageAutomaticTaskRunning.LABEL), 'Tasks: Manage Automatic Tasks in Folder', tasksCategory); export class TaskStatusBarContributions extends Disposable implements IWorkbenchContribution { private runningTasksStatusItem: IStatusbarEntryAccessor | undefined; diff --git a/src/vs/workbench/contrib/tasks/browser/taskService.ts b/src/vs/workbench/contrib/tasks/browser/taskService.ts index 17442169909..a7dbc2aaa3f 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskService.ts @@ -9,7 +9,8 @@ import { ITaskSystem } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { ExecutionEngine, TaskRunSource } from 'vs/workbench/contrib/tasks/common/tasks'; import { TerminalTaskSystem } from './terminalTaskSystem'; import { AbstractTaskService, WorkspaceFolderConfigurationResult } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; -import { TaskFilter } from 'vs/workbench/contrib/tasks/common/taskService'; +import { TaskFilter, ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class TaskService extends AbstractTaskService { private static readonly ProcessTaskSystemSupportMessage = nls.localize('taskService.processTaskSystem', 'Process task system is not support in the web.'); @@ -48,4 +49,6 @@ export class TaskService extends AbstractTaskService { protected versionAndEngineCompatible(filter?: TaskFilter): boolean { return this.executionEngine === ExecutionEngine.Terminal; } -} \ No newline at end of file +} + +registerSingleton(ITaskService, TaskService, true); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index efef7a28135..fb838d2e49e 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -155,9 +155,10 @@ export class TerminalTaskSystem implements ITaskSystem { private idleTaskTerminals: LinkedMap; private sameTaskTerminals: IStringDictionary; private taskSystemInfoResolver: TaskSystemInfoResolver; - private lastTask: VerifiedTask; - private currentTask: VerifiedTask; - private isRerun: boolean; + private lastTask: VerifiedTask | undefined; + // Should always be set in run + private currentTask!: VerifiedTask; + private isRerun: boolean = false; private readonly _onDidStateChange: Emitter; @@ -285,7 +286,7 @@ export class TerminalTaskSystem implements ITaskSystem { } return new Promise((resolve) => { - activeTerminal.terminal.rendererExit(result); + // activeTerminal.terminal.rendererExit(result); resolve(); }); } @@ -472,7 +473,7 @@ export class TerminalTaskSystem implements ITaskSystem { const resolvedVariables = this.resolveVariablesFromSet(systemInfo, workspaceFolder, task, variables); return resolvedVariables.then((resolvedVariables) => { - const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution) || (task.command.runtime === RuntimeType.CustomExecution2); + const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution2); if (resolvedVariables && (task.command !== undefined) && task.command.runtime && (isCustomExecution || (task.command.name !== undefined))) { this.currentTask.resolvedVariables = resolvedVariables; return this.executeInTerminal(task, trigger, new VariableResolver(workspaceFolder, systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder); @@ -485,28 +486,32 @@ export class TerminalTaskSystem implements ITaskSystem { } private reexecuteCommand(task: CustomTask | ContributedTask, trigger: string): Promise { - const workspaceFolder = this.currentTask.workspaceFolder = this.lastTask.workspaceFolder; + const lastTask = this.lastTask; + if (!lastTask) { + return Promise.reject(new Error('No task previously run')); + } + const workspaceFolder = this.currentTask.workspaceFolder = lastTask.workspaceFolder; let variables = new Set(); this.collectTaskVariables(variables, task); // Check that the task hasn't changed to include new variables let hasAllVariables = true; variables.forEach(value => { - if (value.substring(2, value.length - 1) in this.lastTask.getVerifiedTask().resolvedVariables) { + if (value.substring(2, value.length - 1) in lastTask.getVerifiedTask().resolvedVariables) { hasAllVariables = false; } }); if (!hasAllVariables) { - return this.resolveVariablesFromSet(this.lastTask.getVerifiedTask().systemInfo, this.lastTask.getVerifiedTask().workspaceFolder, task, variables).then((resolvedVariables) => { + return this.resolveVariablesFromSet(lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().workspaceFolder, task, variables).then((resolvedVariables) => { this.currentTask.resolvedVariables = resolvedVariables; - return this.executeInTerminal(task, trigger, new VariableResolver(this.lastTask.getVerifiedTask().workspaceFolder, this.lastTask.getVerifiedTask().systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); + return this.executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); }, reason => { return Promise.reject(reason); }); } else { - this.currentTask.resolvedVariables = this.lastTask.getVerifiedTask().resolvedVariables; - return this.executeInTerminal(task, trigger, new VariableResolver(this.lastTask.getVerifiedTask().workspaceFolder, this.lastTask.getVerifiedTask().systemInfo, this.lastTask.getVerifiedTask().resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); + this.currentTask.resolvedVariables = lastTask.getVerifiedTask().resolvedVariables; + return this.executeInTerminal(task, trigger, new VariableResolver(lastTask.getVerifiedTask().workspaceFolder, lastTask.getVerifiedTask().systemInfo, lastTask.getVerifiedTask().resolvedVariables.variables, this.configurationResolverService), workspaceFolder!); } } @@ -556,9 +561,7 @@ export class TerminalTaskSystem implements ITaskSystem { let processStartedSignaled = false; terminal.processReady.then(() => { if (!processStartedSignaled) { - if (task.command.runtime !== RuntimeType.CustomExecution) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); - } + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); processStartedSignaled = true; } }, (_error) => { @@ -608,9 +611,7 @@ export class TerminalTaskSystem implements ITaskSystem { processStartedSignaled = true; } - if (task.command.runtime !== RuntimeType.CustomExecution) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); - } + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); for (let i = 0; i < eventCounter; i++) { let event = TaskEvent.create(TaskEventKind.Inactive, task); @@ -635,9 +636,7 @@ export class TerminalTaskSystem implements ITaskSystem { let processStartedSignaled = false; terminal.processReady.then(() => { if (!processStartedSignaled) { - if (task.command.runtime !== RuntimeType.CustomExecution) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); - } + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); processStartedSignaled = true; } }, (_error) => { @@ -690,9 +689,8 @@ export class TerminalTaskSystem implements ITaskSystem { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal.processId!)); processStartedSignaled = true; } - if (task.command.runtime !== RuntimeType.CustomExecution) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); - } + + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task)); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task)); resolve({ exitCode }); @@ -773,7 +771,7 @@ export class TerminalTaskSystem implements ITaskSystem { let terminalName = this.createTerminalName(task, workspaceFolder); let originalCommand = task.command.name; if (isShellCommand) { - const defaultConfig = await this.terminalInstanceService.getDefaultShellAndArgs(platform); + const defaultConfig = await this.terminalInstanceService.getDefaultShellAndArgs(true, platform); shellLaunchConfig = { name: terminalName, executable: defaultConfig.shell, args: defaultConfig.args, waitOnExit }; let shellSpecified: boolean = false; let shellOptions: ShellConfiguration | undefined = task.command.options && task.command.options.shell; @@ -845,7 +843,7 @@ export class TerminalTaskSystem implements ITaskSystem { } } } else { - let commandExecutable = ((task.command.runtime !== RuntimeType.CustomExecution) && (task.command.runtime !== RuntimeType.CustomExecution2)) ? CommandString.value(command) : undefined; + let commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution2) ? CommandString.value(command) : undefined; let executable = !isShellCommand ? this.resolveVariable(variableResolver, '${' + TerminalTaskSystem.ProcessVarName + '}') : commandExecutable; @@ -917,14 +915,7 @@ export class TerminalTaskSystem implements ITaskSystem { let args: CommandString[] | undefined; let launchConfigs: IShellLaunchConfig | undefined; - if (task.command.runtime === RuntimeType.CustomExecution) { - this.currentTask.shellLaunchConfig = launchConfigs = { - isRendererOnly: true, - waitOnExit, - name: this.createTerminalName(task, workspaceFolder), - initialText: task.command.presentation && task.command.presentation.echo ? `\x1b[1m> Executing task: ${task._label} <\x1b[0m\n` : undefined - }; - } else if (task.command.runtime === RuntimeType.CustomExecution2) { + if (task.command.runtime === RuntimeType.CustomExecution2) { this.currentTask.shellLaunchConfig = launchConfigs = { isExtensionTerminal: true, waitOnExit, @@ -937,7 +928,7 @@ export class TerminalTaskSystem implements ITaskSystem { args = resolvedResult.args; commandExecutable = CommandString.value(command); - this.currentTask.shellLaunchConfig = launchConfigs = this.isRerun ? this.lastTask.getVerifiedTask().shellLaunchConfig : await this.createShellLaunchConfig(task, workspaceFolder, resolver, platform, options, command, args, waitOnExit); + this.currentTask.shellLaunchConfig = launchConfigs = (this.isRerun && this.lastTask) ? this.lastTask.getVerifiedTask().shellLaunchConfig : await this.createShellLaunchConfig(task, workspaceFolder, resolver, platform, options, command, args, waitOnExit); if (launchConfigs === undefined) { return [undefined, undefined, new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive using cmd.exe.'), TaskErrors.UnknownError)]; } @@ -1145,7 +1136,7 @@ export class TerminalTaskSystem implements ITaskSystem { private collectCommandVariables(variables: Set, command: CommandConfiguration, task: CustomTask | ContributedTask): void { // The custom execution should have everything it needs already as it provided // the callback. - if ((command.runtime === RuntimeType.CustomExecution) || (command.runtime === RuntimeType.CustomExecution2)) { + if (command.runtime === RuntimeType.CustomExecution2) { return; } diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index eac610b4d4a..30333ecdc3d 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -44,10 +44,10 @@ export abstract class AbstractProblemCollector implements IDisposable { private bufferLength: number; private openModels: IStringDictionary; private readonly modelListeners = new DisposableStore(); - private tail: Promise; + private tail: Promise | undefined; // [owner] -> ApplyToKind - private applyToByOwner: Map; + protected applyToByOwner: Map; // [owner] -> [resource] -> URI private resourcesToClean: Map>; // [owner] -> [resource] -> [markerkey] -> markerData @@ -278,9 +278,14 @@ export abstract class AbstractProblemCollector implements IDisposable { markersPerResource = new Map(); markersPerOwner.set(resourceAsString, markersPerResource); } - let key = IMarkerData.makeKey(marker); + let key = IMarkerData.makeKeyOptionalMessage(marker, false); + let existingMarker; if (!markersPerResource.has(key)) { markersPerResource.set(key, marker); + } else if (((existingMarker = markersPerResource.get(key)) !== undefined) && existingMarker.message.length < marker.message.length) { + // Most likely https://github.com/microsoft/vscode/issues/77475 + // Heuristic dictates that when the key is the same and message is smaller, we have hit this limitation. + markersPerResource.set(key, marker); } } @@ -344,8 +349,8 @@ export const enum ProblemHandlingStrategy { export class StartStopProblemCollector extends AbstractProblemCollector implements IProblemMatcher { private owners: string[]; - private currentOwner: string; - private currentResource: string; + private currentOwner: string | undefined; + private currentResource: string | undefined; constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, _strategy: ProblemHandlingStrategy = ProblemHandlingStrategy.Clean, fileService?: IFileService) { super(problemMatchers, markerService, modelService, fileService); @@ -397,8 +402,8 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement private _activeBackgroundMatchers: Set; // Current State - private currentOwner: string | null; - private currentResource: string | null; + private currentOwner: string | undefined; + private currentResource: string | undefined; constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, fileService?: IFileService) { super(problemMatchers, markerService, modelService, fileService); @@ -503,8 +508,8 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement private resetCurrentResource(): void { this.reportMarkersForCurrentResource(); - this.currentOwner = null; - this.currentResource = null; + this.currentOwner = undefined; + this.currentResource = undefined; } private reportMarkersForCurrentResource(): void { @@ -512,4 +517,11 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement this.deliverMarkersPerOwnerAndResource(this.currentOwner, this.currentResource); } } -} \ No newline at end of file + + public done(): void { + [...this.applyToByOwner.keys()].forEach(owner => { + this.recordResourcesToClean(owner); + }); + super.done(); + } +} diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index 1c4f457c5b9..ec7c7bcbb96 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 { join } from 'vs/base/common/path'; +import { join, normalize } from 'vs/base/common/path'; import * as Types from 'vs/base/common/types'; import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; @@ -220,6 +220,7 @@ export async function getResource(filename: string, matcher: ProblemMatcher, fil if (fullPath[0] !== '/') { fullPath = '/' + fullPath; } + fullPath = normalize(fullPath); if (matcher.uriProvider !== undefined) { return matcher.uriProvider(fullPath); } else { @@ -263,7 +264,7 @@ abstract class AbstractLineMatcher implements ILineMatcher { public abstract get matchLength(): number; - protected fillProblemData(data: ProblemData | null, pattern: ProblemPattern, matches: RegExpExecArray): data is ProblemData { + protected fillProblemData(data: ProblemData | undefined, pattern: ProblemPattern, matches: RegExpExecArray): data is ProblemData { if (data) { this.fillProperty(data, 'file', pattern, matches, true); this.appendProperty(data, 'message', pattern, matches, true); @@ -448,7 +449,7 @@ class SingleLineMatcher extends AbstractLineMatcher { class MultiLineMatcher extends AbstractLineMatcher { private patterns: ProblemPattern[]; - private data: ProblemData | null; + private data: ProblemData | undefined; constructor(matcher: ProblemMatcher, fileService?: IFileService) { super(matcher, fileService); @@ -479,7 +480,7 @@ class MultiLineMatcher extends AbstractLineMatcher { } let loop = !!this.patterns[this.patterns.length - 1].loop; if (!loop) { - this.data = null; + this.data = undefined; } const markerMatch = data ? this.getMarkerMatch(data) : null; return { match: markerMatch ? markerMatch : null, continue: loop }; @@ -490,7 +491,7 @@ class MultiLineMatcher extends AbstractLineMatcher { Assert.ok(pattern.loop === true && this.data !== null); let matches = pattern.regexp.exec(line); if (!matches) { - this.data = null; + this.data = undefined; return null; } let data = Objects.deepClone(this.data); @@ -793,7 +794,7 @@ export namespace Config { fileLocation?: string | string[]; /** - * The name of a predefined problem pattern, the inline definintion + * The name of a predefined problem pattern, the inline definition * of a problem pattern or an array of problem patterns to match * problems spread over multiple lines. */ diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 4e856c3b65c..d0e83e2e000 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -681,7 +681,7 @@ export namespace RunOptions { } } -class ParseContext { +interface ParseContext { workspaceFolder: IWorkspaceFolder; problemReporter: IProblemReporter; namedProblemMatchers: IStringDictionary; diff --git a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index b52a0229486..6a696c1666a 100644 --- a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -89,7 +89,7 @@ class TaskDefinitionRegistryImpl implements ITaskDefinitionRegistry { private taskTypes: IStringDictionary; private readyPromise: Promise; - private _schema: IJSONSchema; + private _schema: IJSONSchema | undefined; constructor() { this.taskTypes = Object.create(null); diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index decd333ab72..d1d4cc32eb4 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -273,8 +273,7 @@ export namespace PresentationOptions { export enum RuntimeType { Shell = 1, Process = 2, - CustomExecution = 3, - CustomExecution2 = 4 + CustomExecution2 = 3 } export namespace RuntimeType { @@ -284,8 +283,6 @@ export namespace RuntimeType { return RuntimeType.Shell; case 'process': return RuntimeType.Process; - case 'customExecution': - return RuntimeType.CustomExecution; case 'customExecution2': return RuntimeType.CustomExecution2; default: @@ -521,7 +518,7 @@ export abstract class CommonTask { /** * The cached label. */ - _label: string; + _label: string = ''; type?: string; @@ -617,7 +614,7 @@ export abstract class CommonTask { export class CustomTask extends CommonTask { - type: '$customized'; // CUSTOMIZED_TASK_TYPE + type!: '$customized'; // CUSTOMIZED_TASK_TYPE /** * Indicated the source of the task (e.g. tasks.json or extension) @@ -629,7 +626,7 @@ export class CustomTask extends CommonTask { /** * The command configuration */ - command: CommandConfiguration; + command: CommandConfiguration = {}; public constructor(id: string, source: WorkspaceTaskSource, label: string, type: string, command: CommandConfiguration | undefined, hasDefinedMatchers: boolean, runOptions: RunOptions, configurationProperties: ConfigurationProperties) { @@ -663,10 +660,6 @@ export class CustomTask extends CommonTask { type = 'process'; break; - case RuntimeType.CustomExecution: - type = 'customExecution'; - break; - case RuntimeType.CustomExecution2: type = 'customExecution2'; break; @@ -761,8 +754,9 @@ export class ContributedTask extends CommonTask { /** * Indicated the source of the task (e.g. tasks.json or extension) + * Set in the super constructor */ - _source: ExtensionTaskSource; + _source!: ExtensionTaskSource; defines: KeyedTaskIdentifier; @@ -831,7 +825,7 @@ export class InMemoryTask extends CommonTask { */ _source: InMemoryTaskSource; - type: 'inMemory'; + type!: 'inMemory'; public constructor(id: string, source: InMemoryTaskSource, label: string, type: string, runOptions: RunOptions, configurationProperties: ConfigurationProperties) { diff --git a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts index decffe55e63..4dbac3fbf91 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/taskService.ts @@ -13,7 +13,8 @@ import * as TaskConfig from '../common/taskConfiguration'; import { ProcessTaskSystem } from 'vs/workbench/contrib/tasks/node/processTaskSystem'; import { ProcessRunnerDetector } from 'vs/workbench/contrib/tasks/node/processRunnerDetector'; import { AbstractTaskService } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; -import { TaskFilter } from 'vs/workbench/contrib/tasks/common/taskService'; +import { TaskFilter, ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; interface WorkspaceFolderConfigurationResult { workspaceFolder: IWorkspaceFolder; @@ -132,4 +133,6 @@ export class TaskService extends AbstractTaskService { } return result; } -} \ No newline at end of file +} + +registerSingleton(ITaskService, TaskService, true); diff --git a/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts b/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts index 85b3465b41b..538cb57ad6c 100644 --- a/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts +++ b/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts @@ -59,9 +59,9 @@ class RegexpTaskMatcher implements TaskDetectorMatcher { } class GruntTaskMatcher implements TaskDetectorMatcher { - private tasksStart: boolean; - private tasksEnd: boolean; - private descriptionOffset: number | null; + private tasksStart!: boolean; + private tasksEnd!: boolean; + private descriptionOffset!: number | null; init() { this.tasksStart = false; @@ -179,7 +179,7 @@ export class ProcessRunnerDetector { commandExecutable, isShellCommand, config.matcher, ProcessRunnerDetector.DefaultProblemMatchers, list)); } else { if (detectSpecific) { - let detectorPromise: Promise; + let detectorPromise: Promise; if ('gulp' === detectSpecific) { detectorPromise = this.tryDetectGulp(this._workspaceRoot, list); } else if ('jake' === detectSpecific) { @@ -229,7 +229,7 @@ export class ProcessRunnerDetector { return result; } - private tryDetectGulp(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { + private tryDetectGulp(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { return Promise.resolve(this.fileService.resolve(workspaceFolder.toResource('gulpfile.js'))).then((stat) => { // TODO@Dirk (https://github.com/Microsoft/vscode/issues/29454) let config = ProcessRunnerDetector.detectorConfig('gulp'); let process = new LineProcess('gulp', [config.arg, '--no-color'], true, { cwd: this._cwd }); @@ -239,7 +239,7 @@ export class ProcessRunnerDetector { }); } - private tryDetectGrunt(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { + private tryDetectGrunt(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { return Promise.resolve(this.fileService.resolve(workspaceFolder.toResource('Gruntfile.js'))).then((stat) => { // TODO@Dirk (https://github.com/Microsoft/vscode/issues/29454) let config = ProcessRunnerDetector.detectorConfig('grunt'); let process = new LineProcess('grunt', [config.arg, '--no-color'], true, { cwd: this._cwd }); @@ -249,7 +249,7 @@ export class ProcessRunnerDetector { }); } - private tryDetectJake(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { + private tryDetectJake(workspaceFolder: IWorkspaceFolder, list: boolean): Promise { let run = () => { let config = ProcessRunnerDetector.detectorConfig('jake'); let process = new LineProcess('jake', [config.arg], true, { cwd: this._cwd }); diff --git a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts index 972b7ec3374..8110db9d80c 100644 --- a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts @@ -280,7 +280,7 @@ export class ProcessTaskSystem implements ITaskSystem { this.childProcessEnded(); watchingProblemMatcher.done(); watchingProblemMatcher.dispose(); - if (processStartedSignaled && task.command.runtime !== RuntimeType.CustomExecution) { + if (processStartedSignaled) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, success.cmdCode!)); } toDispose = dispose(toDispose!); @@ -336,7 +336,7 @@ export class ProcessTaskSystem implements ITaskSystem { startStopProblemMatcher.done(); startStopProblemMatcher.dispose(); this.checkTerminated(task, success); - if (processStartedSignaled && task.command.runtime !== RuntimeType.CustomExecution) { + if (processStartedSignaled) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, success.cmdCode!)); } this._onDidStateChange.fire(inactiveEvent); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts b/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts similarity index 56% rename from src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts rename to src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts index 77822137bcd..3c4069967ba 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts +++ b/src/vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Terminal, IMarker } from 'xterm'; -import { ITerminalCommandTracker } from 'vs/workbench/contrib/terminal/common/terminal'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Terminal, IMarker, ITerminalAddon } from 'xterm'; +import { ICommandTracker } from 'vs/workbench/contrib/terminal/common/terminal'; /** * The minimum size of the prompt in which to assume the line is a command. @@ -22,15 +21,15 @@ export const enum ScrollPosition { Middle } -export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposable { +export class CommandTrackerAddon implements ICommandTracker, ITerminalAddon { private _currentMarker: IMarker | Boundary = Boundary.Bottom; private _selectionStart: IMarker | Boundary | null = null; private _isDisposable: boolean = false; + private _terminal: Terminal | undefined; - constructor( - private _xterm: Terminal - ) { - this._xterm.onKey(e => this._onKey(e.key)); + public activate(terminal: Terminal): void { + this._terminal = terminal; + terminal.onKey(e => this._onKey(e.key)); } public dispose(): void { @@ -48,116 +47,138 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa } private _onEnter(): void { - if (this._xterm.buffer.cursorX >= MINIMUM_PROMPT_LENGTH) { - this._xterm.addMarker(0); + if (!this._terminal) { + return; + } + if (this._terminal.buffer.cursorX >= MINIMUM_PROMPT_LENGTH) { + this._terminal.addMarker(0); } } public scrollToPreviousCommand(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { + if (!this._terminal) { + return; + } if (!retainSelection) { this._selectionStart = null; } let markerIndex; if (this._currentMarker === Boundary.Bottom) { - markerIndex = this._xterm.markers.length - 1; + markerIndex = this._terminal.markers.length - 1; } else if (this._currentMarker === Boundary.Top) { markerIndex = -1; } else if (this._isDisposable) { - markerIndex = this._findPreviousCommand(); + markerIndex = this._findPreviousCommand(this._terminal); this._currentMarker.dispose(); this._isDisposable = false; } else { - markerIndex = this._xterm.markers.indexOf(this._currentMarker) - 1; + markerIndex = this._terminal.markers.indexOf(this._currentMarker) - 1; } if (markerIndex < 0) { this._currentMarker = Boundary.Top; - this._xterm.scrollToTop(); + this._terminal.scrollToTop(); return; } - this._currentMarker = this._xterm.markers[markerIndex]; + this._currentMarker = this._terminal.markers[markerIndex]; this._scrollToMarker(this._currentMarker, scrollPosition); } public scrollToNextCommand(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { + if (!this._terminal) { + return; + } if (!retainSelection) { this._selectionStart = null; } let markerIndex; if (this._currentMarker === Boundary.Bottom) { - markerIndex = this._xterm.markers.length; + markerIndex = this._terminal.markers.length; } else if (this._currentMarker === Boundary.Top) { markerIndex = 0; } else if (this._isDisposable) { - markerIndex = this._findNextCommand(); + markerIndex = this._findNextCommand(this._terminal); this._currentMarker.dispose(); this._isDisposable = false; } else { - markerIndex = this._xterm.markers.indexOf(this._currentMarker) + 1; + markerIndex = this._terminal.markers.indexOf(this._currentMarker) + 1; } - if (markerIndex >= this._xterm.markers.length) { + if (markerIndex >= this._terminal.markers.length) { this._currentMarker = Boundary.Bottom; - this._xterm.scrollToBottom(); + this._terminal.scrollToBottom(); return; } - this._currentMarker = this._xterm.markers[markerIndex]; + this._currentMarker = this._terminal.markers[markerIndex]; this._scrollToMarker(this._currentMarker, scrollPosition); } private _scrollToMarker(marker: IMarker, position: ScrollPosition): void { + if (!this._terminal) { + return; + } let line = marker.line; if (position === ScrollPosition.Middle) { - line = Math.max(line - Math.floor(this._xterm.rows / 2), 0); + line = Math.max(line - Math.floor(this._terminal.rows / 2), 0); } - this._xterm.scrollToLine(line); + this._terminal.scrollToLine(line); } public selectToPreviousCommand(): void { + if (!this._terminal) { + return; + } if (this._selectionStart === null) { this._selectionStart = this._currentMarker; } this.scrollToPreviousCommand(ScrollPosition.Middle, true); - this._selectLines(this._currentMarker, this._selectionStart); + this._selectLines(this._terminal, this._currentMarker, this._selectionStart); } public selectToNextCommand(): void { + if (!this._terminal) { + return; + } if (this._selectionStart === null) { this._selectionStart = this._currentMarker; } this.scrollToNextCommand(ScrollPosition.Middle, true); - this._selectLines(this._currentMarker, this._selectionStart); + this._selectLines(this._terminal, this._currentMarker, this._selectionStart); } public selectToPreviousLine(): void { + if (!this._terminal) { + return; + } if (this._selectionStart === null) { this._selectionStart = this._currentMarker; } - - this.scrollToPreviousLine(ScrollPosition.Middle, true); - this._selectLines(this._currentMarker, this._selectionStart); + this.scrollToPreviousLine(this._terminal, ScrollPosition.Middle, true); + this._selectLines(this._terminal, this._currentMarker, this._selectionStart); } public selectToNextLine(): void { + if (!this._terminal) { + return; + } if (this._selectionStart === null) { this._selectionStart = this._currentMarker; } - - this.scrollToNextLine(ScrollPosition.Middle, true); - this._selectLines(this._currentMarker, this._selectionStart); + this.scrollToNextLine(this._terminal, ScrollPosition.Middle, true); + this._selectLines(this._terminal, this._currentMarker, this._selectionStart); } - private _selectLines(start: IMarker | Boundary, end: IMarker | Boundary | null): void { + private _selectLines(xterm: Terminal, start: IMarker | Boundary, end: IMarker | Boundary | null): void { if (end === null) { end = Boundary.Bottom; } - let startLine = this._getLine(start); - let endLine = this._getLine(end); + let startLine = this._getLine(xterm, start); + let endLine = this._getLine(xterm, end); if (startLine > endLine) { const temp = startLine; @@ -169,13 +190,13 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa // command in the selection for the current command endLine -= 1; - this._xterm.selectLines(startLine, endLine); + xterm.selectLines(startLine, endLine); } - private _getLine(marker: IMarker | Boundary): number { + private _getLine(xterm: Terminal, marker: IMarker | Boundary): number { // Use the _second last_ row as the last row is likely the prompt if (marker === Boundary.Bottom) { - return this._xterm.buffer.baseY + this._xterm.rows - 1; + return xterm.buffer.baseY + xterm.rows - 1; } if (marker === Boundary.Top) { @@ -185,74 +206,74 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa return marker.line; } - public scrollToPreviousLine(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { + public scrollToPreviousLine(xterm: Terminal, scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { if (!retainSelection) { this._selectionStart = null; } if (this._currentMarker === Boundary.Top) { - this._xterm.scrollToTop(); + xterm.scrollToTop(); return; } if (this._currentMarker === Boundary.Bottom) { - this._currentMarker = this._xterm.addMarker(this._getOffset() - 1); + this._currentMarker = xterm.addMarker(this._getOffset(xterm) - 1); } else { - const offset = this._getOffset(); + const offset = this._getOffset(xterm); if (this._isDisposable) { this._currentMarker.dispose(); } - this._currentMarker = this._xterm.addMarker(offset - 1); + this._currentMarker = xterm.addMarker(offset - 1); } this._isDisposable = true; this._scrollToMarker(this._currentMarker, scrollPosition); } - public scrollToNextLine(scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { + public scrollToNextLine(xterm: Terminal, scrollPosition: ScrollPosition = ScrollPosition.Top, retainSelection: boolean = false): void { if (!retainSelection) { this._selectionStart = null; } if (this._currentMarker === Boundary.Bottom) { - this._xterm.scrollToBottom(); + xterm.scrollToBottom(); return; } if (this._currentMarker === Boundary.Top) { - this._currentMarker = this._xterm.addMarker(this._getOffset() + 1); + this._currentMarker = xterm.addMarker(this._getOffset(xterm) + 1); } else { - const offset = this._getOffset(); + const offset = this._getOffset(xterm); if (this._isDisposable) { this._currentMarker.dispose(); } - this._currentMarker = this._xterm.addMarker(offset + 1); + this._currentMarker = xterm.addMarker(offset + 1); } this._isDisposable = true; this._scrollToMarker(this._currentMarker, scrollPosition); } - private _getOffset(): number { + private _getOffset(xterm: Terminal): number { if (this._currentMarker === Boundary.Bottom) { return 0; } else if (this._currentMarker === Boundary.Top) { - return 0 - (this._xterm.buffer.baseY + this._xterm.buffer.cursorY); + return 0 - (xterm.buffer.baseY + xterm.buffer.cursorY); } else { - let offset = this._getLine(this._currentMarker); - offset -= this._xterm.buffer.baseY + this._xterm.buffer.cursorY; + let offset = this._getLine(xterm, this._currentMarker); + offset -= xterm.buffer.baseY + xterm.buffer.cursorY; return offset; } } - private _findPreviousCommand(): number { + private _findPreviousCommand(xterm: Terminal): number { if (this._currentMarker === Boundary.Top) { return 0; } else if (this._currentMarker === Boundary.Bottom) { - return this._xterm.markers.length - 1; + return xterm.markers.length - 1; } let i; - for (i = this._xterm.markers.length - 1; i >= 0; i--) { - if (this._xterm.markers[i].line < this._currentMarker.line) { + for (i = xterm.markers.length - 1; i >= 0; i--) { + if (xterm.markers[i].line < this._currentMarker.line) { return i; } } @@ -260,20 +281,20 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa return -1; } - private _findNextCommand(): number { + private _findNextCommand(xterm: Terminal): number { if (this._currentMarker === Boundary.Top) { return 0; } else if (this._currentMarker === Boundary.Bottom) { - return this._xterm.markers.length - 1; + return xterm.markers.length - 1; } let i; - for (i = 0; i < this._xterm.markers.length; i++) { - if (this._xterm.markers[i].line > this._currentMarker.line) { + for (i = 0; i < xterm.markers.length; i++) { + if (xterm.markers[i].line > this._currentMarker.line) { return i; } } - return this._xterm.markers.length; + return xterm.markers.length; } } diff --git a/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts b/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts index 74542173298..6997a4dbef1 100644 --- a/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/addons/navigationModeAddon.ts @@ -9,7 +9,7 @@ import { addDisposableListener } from 'vs/base/browser/dom'; import { INavigationMode } from 'vs/workbench/contrib/terminal/common/terminal'; export class NavigationModeAddon implements INavigationMode, ITerminalAddon { - private _terminal: Terminal; + private _terminal: Terminal | undefined; constructor( private _navigationModeContextKey: IContextKey @@ -22,11 +22,18 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon { dispose() { } exitNavigationMode(): void { + if (!this._terminal) { + return; + } this._terminal.scrollToBottom(); this._terminal.focus(); } focusPreviousLine(): void { + if (!this._terminal) { + return; + } + // Focus previous row if a row is already focused if (document.activeElement && document.activeElement.parentElement && document.activeElement.parentElement.classList.contains('xterm-accessibility-tree')) { const element = document.activeElement.previousElementSibling; @@ -66,6 +73,10 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon { } focusNextLine(): void { + if (!this._terminal) { + return; + } + // Focus previous row if a row is already focused if (document.activeElement && document.activeElement.parentElement && document.activeElement.parentElement.classList.contains('xterm-accessibility-tree')) { const element = document.activeElement.nextElementSibling; @@ -103,4 +114,4 @@ export class NavigationModeAddon implements INavigationMode, ITerminalAddon { }); this._navigationModeContextKey.set(true); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 08fc487af2f..99c526eedca 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -20,7 +20,7 @@ import * as panel from 'vs/workbench/browser/panel'; import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; -import { AllowWorkspaceShellTerminalCommand, ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, DisallowWorkspaceShellTerminalCommand, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, ITerminalService, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -71,6 +71,21 @@ configurationRegistry.registerConfiguration({ title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), type: 'object', properties: { + 'terminal.integrated.automationShell.linux': { + markdownDescription: nls.localize('terminal.integrated.automationShell.linux', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.linux`', '`shellArgs`', '`env`'), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.automationShell.osx': { + markdownDescription: nls.localize('terminal.integrated.automationShell.osx', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.osx`', '`shellArgs`', '`env`'), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.automationShell.windows': { + markdownDescription: nls.localize('terminal.integrated.automationShell.windows', "A path that when set will override {0} and ignore {1} and {2} values for automation-related terminal usage like tasks and debug.", '`terminal.integrated.shell.windows`', '`shellArgs`', '`env`'), + type: ['string', 'null'], + default: null + }, 'terminal.integrated.shellArgs.linux': { markdownDescription: nls.localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), type: 'array', @@ -283,6 +298,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize('terminal.integrated.experimentalRefreshOnResume', "An experimental setting that will refresh the terminal renderer when the system is resumed."), type: 'boolean', default: false + }, + 'terminal.integrated.experimentalUseTitleEvent': { + description: nls.localize('terminal.integrated.experimentalUseTitleEvent', "An experimental setting that will use the terminal title event for the dropdown title. This setting will only apply to new terminals."), + type: 'boolean', + default: false } } }); @@ -372,12 +392,14 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAct mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowWorkspaceShellTerminalCommand, AllowWorkspaceShellTerminalCommand.ID, AllowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Allow Workspace Shell Configuration', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand.ID, DisallowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Disallow Workspace Shell Configuration', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageWorkspaceShellPermissionsTerminalCommand, ManageWorkspaceShellPermissionsTerminalCommand.ID, ManageWorkspaceShellPermissionsTerminalCommand.LABEL), 'Terminal: Manage Workspace Shell Permissions', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_F }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Focus Find Widget', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyCode.KEY_F +}, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Focus Find Widget', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(HideTerminalFindWidgetAction, HideTerminalFindWidgetAction.ID, HideTerminalFindWidgetAction.LABEL, { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape] @@ -447,11 +469,11 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ResizePaneDownTe actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToPreviousCommandAction, ScrollToPreviousCommandAction.ID, ScrollToPreviousCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.UpArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll To Previous Command', category); +}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Previous Command', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollToNextCommandAction, ScrollToNextCommandAction.ID, ScrollToNextCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyCode.DownArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll To Next Command', category); +}, ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate())), 'Terminal: Scroll To Next Command', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectToPreviousCommandAction, SelectToPreviousCommandAction.ID, SelectToPreviousCommandAction.LABEL, { primary: 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index c9e3bdb74a3..42e98f25547 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -30,10 +30,10 @@ export interface ITerminalInstanceService { createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper; createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess; - getDefaultShellAndArgs(platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>; + getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride?: Platform): Promise<{ shell: string, args: string[] | string | undefined }>; getMainProcessParentEnv(): Promise; } export interface IBrowserTerminalConfigHelper extends ITerminalConfigHelper { - panelContainer: HTMLElement; + panelContainer: HTMLElement | undefined; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 23077803ab1..2492bd58041 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Action, IAction } from 'vs/base/common/actions'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction, ITerminalConfigHelper, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal'; import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -993,10 +993,10 @@ export class ClearSelectionTerminalAction extends Action { } } -export class AllowWorkspaceShellTerminalCommand extends Action { +export class ManageWorkspaceShellPermissionsTerminalCommand extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.WORKSPACE_SHELL_ALLOW; - public static readonly LABEL = nls.localize('workbench.action.terminal.allowWorkspaceShell', "Allow Workspace Shell Configuration"); + public static readonly ID = TERMINAL_COMMAND_ID.MANAGE_WORKSPACE_SHELL_PERMISSIONS; + public static readonly LABEL = nls.localize('workbench.action.terminal.manageWorkspaceShellPermissions', "Manage Workspace Shell Permissions"); constructor( id: string, label: string, @@ -1005,27 +1005,8 @@ export class AllowWorkspaceShellTerminalCommand extends Action { super(id, label); } - public run(event?: any): Promise { - this.terminalService.setWorkspaceShellAllowed(true); - return Promise.resolve(undefined); - } -} - -export class DisallowWorkspaceShellTerminalCommand extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.WORKSPACE_SHELL_DISALLOW; - public static readonly LABEL = nls.localize('workbench.action.terminal.disallowWorkspaceShell', "Disallow Workspace Shell Configuration"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - this.terminalService.setWorkspaceShellAllowed(false); - return Promise.resolve(undefined); + public async run(event?: any): Promise { + await this.terminalService.manageWorkspaceShellPermissions(); } } @@ -1053,7 +1034,7 @@ export class RenameTerminalAction extends Action { prompt: nls.localize('workbench.action.terminal.rename.prompt', "Enter terminal name"), }).then(name => { if (name) { - terminalInstance.setTitle(name, false); + terminalInstance.setTitle(name, TitleEventSource.Api); } }); } @@ -1168,7 +1149,7 @@ export class ScrollToPreviousCommandAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.scrollToPreviousCommand(); instance.focus(); } @@ -1189,7 +1170,7 @@ export class ScrollToNextCommandAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.scrollToNextCommand(); instance.focus(); } @@ -1210,7 +1191,7 @@ export class SelectToPreviousCommandAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.selectToPreviousCommand(); instance.focus(); } @@ -1231,7 +1212,7 @@ export class SelectToNextCommandAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.selectToNextCommand(); instance.focus(); } @@ -1252,7 +1233,7 @@ export class SelectToPreviousLineAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.selectToPreviousLine(); instance.focus(); } @@ -1273,7 +1254,7 @@ export class SelectToNextLineAction extends Action { public run(): Promise { const instance = this.terminalService.getActiveInstance(); - if (instance) { + if (instance && instance.commandTracker) { instance.commandTracker.selectToNextLine(); instance.focus(); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 0da7d6c690b..e4a307e4059 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -11,7 +11,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ITerminalConfiguration, ITerminalFont, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING, LinuxDistro, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; import Severity from 'vs/base/common/severity'; import { Terminal as XTermTerminal } from 'xterm'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, NeverShowAgainScope } from 'vs/platform/notification/common/notification'; import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal'; import { Emitter, Event } from 'vs/base/common/event'; import { basename } from 'vs/base/common/path'; @@ -26,11 +26,11 @@ const MAXIMUM_FONT_SIZE = 25; * specific test cases can be written. */ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { - public panelContainer: HTMLElement; + public panelContainer: HTMLElement | undefined; - private _charMeasureElement: HTMLElement; - private _lastFontMeasurement: ITerminalFont; - public config: ITerminalConfiguration; + private _charMeasureElement: HTMLElement | undefined; + private _lastFontMeasurement: ITerminalFont | undefined; + public config!: ITerminalConfiguration; private readonly _onWorkspacePermissionsChanged = new Emitter(); public get onWorkspacePermissionsChanged(): Event { return this._onWorkspacePermissionsChanged.event; } @@ -55,49 +55,55 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { } public configFontIsMonospace(): boolean { - this._createCharMeasureElementIfNecessary(); const fontSize = 15; const fontFamily = this.config.fontFamily || this._configurationService.getValue('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily; const i_rect = this._getBoundingRectFor('i', fontFamily, fontSize); const w_rect = this._getBoundingRectFor('w', fontFamily, fontSize); - const invalidBounds = !i_rect.width || !w_rect.width; - if (invalidBounds) { - // There is no reason to believe the font is not Monospace. + // Check for invalid bounds, there is no reason to believe the font is not monospace + if (!i_rect || !w_rect || !i_rect.width || !w_rect.width) { return true; } return i_rect.width === w_rect.width; } - private _createCharMeasureElementIfNecessary() { + private _createCharMeasureElementIfNecessary(): HTMLElement { + if (!this.panelContainer) { + throw new Error('Cannot measure element when terminal is not attached'); + } // Create charMeasureElement if it hasn't been created or if it was orphaned by its parent if (!this._charMeasureElement || !this._charMeasureElement.parentElement) { this._charMeasureElement = document.createElement('div'); this.panelContainer.appendChild(this._charMeasureElement); } + return this._charMeasureElement; } - private _getBoundingRectFor(char: string, fontFamily: string, fontSize: number): ClientRect | DOMRect { - const style = this._charMeasureElement.style; + private _getBoundingRectFor(char: string, fontFamily: string, fontSize: number): ClientRect | DOMRect | undefined { + let charMeasureElement: HTMLElement; + try { + charMeasureElement = this._createCharMeasureElementIfNecessary(); + } catch { + return undefined; + } + const style = charMeasureElement.style; style.display = 'inline-block'; style.fontFamily = fontFamily; style.fontSize = fontSize + 'px'; style.lineHeight = 'normal'; - this._charMeasureElement.innerText = char; - const rect = this._charMeasureElement.getBoundingClientRect(); + charMeasureElement.innerText = char; + const rect = charMeasureElement.getBoundingClientRect(); style.display = 'none'; return rect; } private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont { - this._createCharMeasureElementIfNecessary(); - const rect = this._getBoundingRectFor('X', fontFamily, fontSize); // Bounding client rect was invalid, use last font measurement if available. - if (this._lastFontMeasurement && !rect.width && !rect.height) { + if (this._lastFontMeasurement && (!rect || !rect.width || !rect.height)) { return this._lastFontMeasurement; } @@ -106,8 +112,8 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { fontSize, letterSpacing, lineHeight, - charWidth: rect.width, - charHeight: Math.ceil(rect.height) + charWidth: rect && rect.width ? rect.width : 0, + charHeight: rect && rect.height ? Math.ceil(rect.height) : 0 }; return this._lastFontMeasurement; } @@ -248,7 +254,6 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { return r; } - private readonly NO_RECOMMENDATIONS_KEY = 'terminalConfigHelper/launchRecommendationsIgnore'; private recommendationsShown = false; public async showRecommendations(shellLaunchConfig: IShellLaunchConfig): Promise { @@ -258,10 +263,6 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { this.recommendationsShown = true; if (platform.isWindows && shellLaunchConfig.executable && basename(shellLaunchConfig.executable).toLowerCase() === 'wsl.exe') { - if (this._storageService.getBoolean(this.NO_RECOMMENDATIONS_KEY, StorageScope.WORKSPACE, false)) { - return; - } - if (! await this.isExtensionInstalled('ms-vscode-remote.remote-wsl')) { this._notificationService.prompt( Severity.Info, @@ -270,16 +271,10 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { "Check out the 'Visual Studio Code Remote - WSL' extension for a great development experience in WSL. Click [here]({0}) to learn more.", 'https://go.microsoft.com/fwlink/?linkid=2097212' ), - [ - { - label: nls.localize('doNotShowAgain', "Don't Show Again"), - run: () => { - this._storageService.store(this.NO_RECOMMENDATIONS_KEY, true, StorageScope.WORKSPACE); - } - } - ], + [], { - sticky: true + sticky: true, + neverShowAgain: { id: 'terminalConfigHelper/launchRecommendationsIgnore', scope: NeverShowAgainScope.WORKSPACE } } ); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts index 1ab97705568..87ca61f2d55 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SimpleFindWidget } from 'vs/editor/contrib/find/simpleFindWidget'; +import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/terminal/common/terminal'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -78,4 +78,14 @@ export class TerminalFindWidget extends SimpleFindWidget { protected onFindInputFocusTrackerBlur() { this._findInputFocused.reset(); } + + public findFirst() { + const instance = this._terminalService.getActiveInstance(); + if (instance) { + if (instance.hasSelection()) { + instance.clearSelection(); + } + instance.findPrevious(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue() }); + } + } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 31cdb6fb01b..9ab3aa21384 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -9,7 +9,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { debounce } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; -import * as lifecycle from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; import * as nls from 'vs/nls'; @@ -25,18 +25,18 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; -import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal'; import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; -import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; +import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon'; import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addons/navigationModeAddon'; // How long in milliseconds should an average frame take to render for a notification to appear @@ -165,15 +165,15 @@ interface IGridDimensions { rows: number; } -export class TerminalInstance implements ITerminalInstance { +export class TerminalInstance extends Disposable implements ITerminalInstance { private static readonly EOL_REGEX = /\r?\n/g; private static _lastKnownCanvasDimensions: ICanvasDimensions | undefined; private static _lastKnownGridDimensions: IGridDimensions | undefined; private static _idCounter = 1; - private _processManager: ITerminalProcessManager | undefined; - private _pressAnyKeyToCloseListener: lifecycle.IDisposable | undefined; + private _processManager!: ITerminalProcessManager; + private _pressAnyKeyToCloseListener: IDisposable | undefined; private _id: number; private _isExiting: boolean; @@ -181,27 +181,26 @@ export class TerminalInstance implements ITerminalInstance { private _isVisible: boolean; private _isDisposed: boolean; private _skipTerminalCommands: string[]; - private _title: string; - private _wrapperElement: HTMLDivElement; - private _xterm: XTermTerminal; + private _title: string = ''; + private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined; + private _xterm: XTermTerminal | undefined; private _xtermSearch: SearchAddon | undefined; - private _xtermElement: HTMLDivElement; + private _xtermElement: HTMLDivElement | undefined; private _terminalHasTextContextKey: IContextKey; private _terminalA11yTreeFocusContextKey: IContextKey; - private _cols: number; - private _rows: number; + private _cols: number = 0; + private _rows: number = 0; private _dimensionsOverride: ITerminalDimensions | undefined; private _windowsShellHelper: IWindowsShellHelper | undefined; - private _xtermReadyPromise: Promise; + private _xtermReadyPromise: Promise; private _titleReadyPromise: Promise; - private _titleReadyComplete: (title: string) => any; + private _titleReadyComplete: ((title: string) => any) | undefined; - private readonly _disposables = new lifecycle.DisposableStore(); - private _messageTitleDisposable: lifecycle.IDisposable | undefined; + private _messageTitleDisposable: IDisposable | undefined; - private _widgetManager: TerminalWidgetManager; - private _linkHandler: TerminalLinkHandler; - private _commandTracker: TerminalCommandTracker; + private _widgetManager: TerminalWidgetManager | undefined; + private _linkHandler: TerminalLinkHandler | undefined; + private _commandTrackerAddon: CommandTrackerAddon | undefined; private _navigationModeAddon: INavigationMode & ITerminalAddon | undefined; public disableLayout: boolean; @@ -221,15 +220,15 @@ export class TerminalInstance implements ITerminalInstance { public get maxCols(): number { return this._cols; } public get maxRows(): number { return this._rows; } // TODO: Ideally processId would be merged into processReady - public get processId(): number | undefined { return this._processManager ? this._processManager.shellProcessId : undefined; } + public get processId(): number | undefined { return this._processManager.shellProcessId; } // TODO: How does this work with detached processes? // TODO: Should this be an event as it can fire twice? - public get processReady(): Promise { return this._processManager ? this._processManager.ptyProcessReady : Promise.resolve(undefined); } + public get processReady(): Promise { return this._processManager.ptyProcessReady; } public get title(): string { return this._title; } public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; } public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; } public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; } - public get commandTracker(): TerminalCommandTracker { return this._commandTracker; } + public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; } public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; } private readonly _onExit = new Emitter(); @@ -246,8 +245,6 @@ export class TerminalInstance implements ITerminalInstance { public get onData(): Event { return this._onData.event; } private readonly _onLineData = new Emitter(); public get onLineData(): Event { return this._onLineData.event; } - private readonly _onRendererInput = new Emitter(); - public get onRendererInput(): Event { return this._onRendererInput.event; } private readonly _onRequestExtHostProcess = new Emitter(); public get onRequestExtHostProcess(): Event { return this._onRequestExtHostProcess.event; } private readonly _onDimensionsChanged = new Emitter(); @@ -275,6 +272,8 @@ export class TerminalInstance implements ITerminalInstance { @IStorageService private readonly _storageService: IStorageService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { + super(); + this._skipTerminalCommands = []; this._isExiting = false; this._hadFocusOnExit = false; @@ -293,11 +292,7 @@ export class TerminalInstance implements ITerminalInstance { this._logService.trace(`terminalInstance#ctor (id: ${this.id})`, this._shellLaunchConfig); this._initDimensions(); - if (!this.shellLaunchConfig.isRendererOnly) { - this._createProcess(); - } else { - this.setTitle(this._shellLaunchConfig.name, false); - } + this._createProcess(); this._xtermReadyPromise = this._createXterm(); this._xtermReadyPromise.then(() => { @@ -321,8 +316,8 @@ export class TerminalInstance implements ITerminalInstance { })); } - public addDisposable(disposable: lifecycle.IDisposable): void { - this._disposables.add(disposable); + public addDisposable(disposable: IDisposable): void { + this._register(disposable); } private _initDimensions(): void { @@ -455,11 +450,11 @@ export class TerminalInstance implements ITerminalInstance { /** * Create xterm.js instance and attach data listeners. */ - protected async _createXterm(): Promise { + protected async _createXterm(): Promise { const Terminal = await this._getXtermConstructor(); const font = this._configHelper.getFont(undefined, true); const config = this._configHelper.config; - this._xterm = new Terminal({ + const xterm = new Terminal({ scrollback: config.scrollback, theme: this._getXtermTheme(), drawBoldTextInBrightColors: config.drawBoldTextInBrightColors, @@ -476,53 +471,48 @@ export class TerminalInstance implements ITerminalInstance { // TODO: Guess whether to use canvas or dom better rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType }); + this._xterm = xterm; this.updateAccessibilitySupport(); this._terminalInstanceService.getXtermSearchConstructor().then(Addon => { this._xtermSearch = new Addon(); - this._xterm.loadAddon(this._xtermSearch); + xterm.loadAddon(this._xtermSearch); }); if (this._shellLaunchConfig.initialText) { this._xterm.writeln(this._shellLaunchConfig.initialText); } this._xterm.onLineFeed(() => this._onLineFeed()); this._xterm.onKey(e => this._onKey(e.key, e.domEvent)); + this._xterm.onSelectionChange(async () => this._onSelectionChange()); - if (this._processManager) { - this._processManager.onProcessData(data => this._onProcessData(data)); - this._xterm.onData(data => this._processManager!.write(data)); - // TODO: How does the cwd work on detached processes? - this.processReady.then(async () => { - this._linkHandler.processCwd = await this._processManager!.getInitialCwd(); - }); - // Init winpty compat and link handler after process creation as they rely on the - // underlying process OS - this._processManager.onProcessReady(() => { - if (!this._processManager) { - return; - } - if (this._processManager.os === platform.OperatingSystem.Windows) { - this._xterm.setOption('windowsMode', true); - // Force line data to be sent when the cursor is moved, the main purpose for - // this is because ConPTY will often not do a line feed but instead move the - // cursor, in which case we still want to send the current line's data to tasks. - this._xterm.addCsiHandler('H', () => { - this._onCursorMove(); - return false; - }); - } - this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, this._processManager); - }); - } else if (this.shellLaunchConfig.isRendererOnly) { - this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, undefined); - } + this._processManager.onProcessData(data => this._onProcessData(data)); + this._xterm.onData(data => this._processManager.write(data)); + // TODO: How does the cwd work on detached processes? + this.processReady.then(async () => { + if (this._linkHandler) { + this._linkHandler.processCwd = await this._processManager.getInitialCwd(); + } + }); + // Init winpty compat and link handler after process creation as they rely on the + // underlying process OS + this._processManager.onProcessReady(() => { + if (this._processManager.os === platform.OperatingSystem.Windows) { + xterm.setOption('windowsMode', true); + // Force line data to be sent when the cursor is moved, the main purpose for + // this is because ConPTY will often not do a line feed but instead move the + // cursor, in which case we still want to send the current line's data to tasks. + xterm.addCsiHandler('H', () => { + this._onCursorMove(); + return false; + }); + } + this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, this._processManager, this._configHelper); + }); - // Register listener to trigger the onInput ext API if the terminal is a renderer only - if (this._shellLaunchConfig.isRendererOnly) { - this._xterm.onData(data => this._sendRendererInput(data)); - } + this._commandTrackerAddon = new CommandTrackerAddon(); + this._xterm.loadAddon(this._commandTrackerAddon); + this._register(this._themeService.onThemeChange(theme => this._updateTheme(xterm, theme))); - this._commandTracker = new TerminalCommandTracker(this._xterm); - this._disposables.add(this._themeService.onThemeChange(theme => this._updateTheme(theme))); + return xterm; } private _isScreenReaderOptimized(): boolean { @@ -562,7 +552,7 @@ export class TerminalInstance implements ITerminalInstance { } public _attachToElement(container: HTMLElement): void { - this._xtermReadyPromise.then(() => { + this._xtermReadyPromise.then(xterm => { if (this._wrapperElement) { throw new Error('The terminal instance has already been attached to a container'); } @@ -573,11 +563,11 @@ export class TerminalInstance implements ITerminalInstance { this._xtermElement = document.createElement('div'); // Attach the xterm object to the DOM, exposing it to the smoke tests - (this._wrapperElement).xterm = this._xterm; + this._wrapperElement.xterm = this._xterm; - this._xterm.open(this._xtermElement); - this._xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this)); - this._xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => { + xterm.open(this._xtermElement); + xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this)); + xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => { // Disable all input if the terminal is exiting if (this._isExiting) { return false; @@ -604,7 +594,7 @@ export class TerminalInstance implements ITerminalInstance { return true; }); - this._disposables.add(dom.addDisposableListener(this._xterm.element, 'mousedown', () => { + this._register(dom.addDisposableListener(xterm.element, 'mousedown', () => { // We need to listen to the mouseup event on the document since the user may release // the mouse button anywhere outside of _xterm.element. const listener = dom.addDisposableListener(document, 'mouseup', () => { @@ -616,17 +606,17 @@ export class TerminalInstance implements ITerminalInstance { })); // xterm.js currently drops selection on keyup as we need to handle this case. - this._disposables.add(dom.addDisposableListener(this._xterm.element, 'keyup', () => { + this._register(dom.addDisposableListener(xterm.element, 'keyup', () => { // Wait until keyup has propagated through the DOM before evaluating // the new selection state. setTimeout(() => this._refreshSelectionContextKey(), 0); })); - const xtermHelper: HTMLElement = this._xterm.element.querySelector('.xterm-helpers'); + const xtermHelper: HTMLElement = xterm.element.querySelector('.xterm-helpers'); const focusTrap: HTMLElement = document.createElement('div'); focusTrap.setAttribute('tabindex', '0'); dom.addClass(focusTrap, 'focus-trap'); - this._disposables.add(dom.addDisposableListener(focusTrap, 'focus', () => { + this._register(dom.addDisposableListener(focusTrap, 'focus', () => { let currentElement = focusTrap; while (!dom.hasClass(currentElement, 'part')) { currentElement = currentElement.parentElement!; @@ -634,20 +624,20 @@ export class TerminalInstance implements ITerminalInstance { const hidePanelElement = currentElement.querySelector('.hide-panel-action'); hidePanelElement.focus(); })); - xtermHelper.insertBefore(focusTrap, this._xterm.textarea); + xtermHelper.insertBefore(focusTrap, xterm.textarea); - this._disposables.add(dom.addDisposableListener(this._xterm.textarea, 'focus', () => { + this._register(dom.addDisposableListener(xterm.textarea, 'focus', () => { this._terminalFocusContextKey.set(true); this._onFocused.fire(this); })); - this._disposables.add(dom.addDisposableListener(this._xterm.textarea, 'blur', () => { + this._register(dom.addDisposableListener(xterm.textarea, 'blur', () => { this._terminalFocusContextKey.reset(); this._refreshSelectionContextKey(); })); - this._disposables.add(dom.addDisposableListener(this._xterm.element, 'focus', () => { + this._register(dom.addDisposableListener(xterm.element, 'focus', () => { this._terminalFocusContextKey.set(true); })); - this._disposables.add(dom.addDisposableListener(this._xterm.element, 'blur', () => { + this._register(dom.addDisposableListener(xterm.element, 'blur', () => { this._terminalFocusContextKey.reset(); this._refreshSelectionContextKey(); })); @@ -655,13 +645,13 @@ export class TerminalInstance implements ITerminalInstance { this._wrapperElement.appendChild(this._xtermElement); this._container.appendChild(this._wrapperElement); - if (this._processManager) { - this._widgetManager = new TerminalWidgetManager(this._wrapperElement); - this._processManager.onProcessReady(() => this._linkHandler.setWidgetManager(this._widgetManager)); - } else if (this._shellLaunchConfig.isRendererOnly) { - this._widgetManager = new TerminalWidgetManager(this._wrapperElement); - this._linkHandler.setWidgetManager(this._widgetManager); - } + const widgetManager = new TerminalWidgetManager(this._wrapperElement); + this._widgetManager = widgetManager; + this._processManager.onProcessReady(() => { + if (this._linkHandler) { + this._linkHandler.setWidgetManager(widgetManager); + } + }); const computedStyle = window.getComputedStyle(this._container); const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10); @@ -672,8 +662,8 @@ export class TerminalInstance implements ITerminalInstance { // If IShellLaunchConfig.waitOnExit was true and the process finished before the terminal // panel was initialized. - if (this._xterm.getOption('disableStdin')) { - this._attachPressAnyKeyToCloseListener(); + if (xterm.getOption('disableStdin')) { + this._attachPressAnyKeyToCloseListener(xterm); } const neverMeasureRenderTime = this._storageService.getBoolean(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, StorageScope.GLOBAL, false); @@ -683,9 +673,10 @@ export class TerminalInstance implements ITerminalInstance { }); } - private _measureRenderTime(): void { + private async _measureRenderTime(): Promise { + const xterm = await this._xtermReadyPromise; const frameTimes: number[] = []; - const textRenderLayer = this._xterm._core._renderService._renderer._renderLayers[0]; + const textRenderLayer = xterm._core._renderService._renderer._renderLayers[0]; const originalOnGridChanged = textRenderLayer.onGridChanged; const evaluateCanvasRenderer = () => { @@ -730,34 +721,41 @@ export class TerminalInstance implements ITerminalInstance { } public registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number, validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void): number { - return this._linkHandler.registerCustomLinkHandler(regex, handler, matchIndex, validationCallback); + return this._linkHandler!.registerCustomLinkHandler(regex, handler, matchIndex, validationCallback); } public deregisterLinkMatcher(linkMatcherId: number): void { - this._xterm.deregisterLinkMatcher(linkMatcherId); + this._xtermReadyPromise.then(xterm => xterm.deregisterLinkMatcher(linkMatcherId)); } public hasSelection(): boolean { - return this._xterm && this._xterm.hasSelection(); + return this._xterm ? this._xterm.hasSelection() : false; } public async copySelection(): Promise { + const xterm = await this._xtermReadyPromise; if (this.hasSelection()) { - await this._clipboardService.writeText(this._xterm.getSelection()); + await this._clipboardService.writeText(xterm.getSelection()); } else { this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy')); } } public get selection(): string | undefined { - return this.hasSelection() ? this._xterm.getSelection() : undefined; + return this._xterm && this.hasSelection() ? this._xterm.getSelection() : undefined; } public clearSelection(): void { + if (!this._xterm) { + return; + } this._xterm.clearSelection(); } public selectAll(): void { + if (!this._xterm) { + return; + } // Focus here to ensure the terminal context key is set this._xterm.focus(); this._xterm.selectAll(); @@ -778,6 +776,9 @@ export class TerminalInstance implements ITerminalInstance { } public notifyFindWidgetFocusChanged(isFocused: boolean): void { + if (!this._xterm) { + return; + } const terminalFocused = !isFocused && (document.activeElement === this._xterm.textarea || document.activeElement === this._xterm.element); this._terminalFocusContextKey.set(terminalFocused); } @@ -785,18 +786,18 @@ export class TerminalInstance implements ITerminalInstance { public dispose(immediate?: boolean): void { this._logService.trace(`terminalInstance#dispose (id: ${this.id})`); - lifecycle.dispose(this._windowsShellHelper); + dispose(this._windowsShellHelper); this._windowsShellHelper = undefined; - this._linkHandler = lifecycle.dispose(this._linkHandler); - this._commandTracker = lifecycle.dispose(this._commandTracker); - this._widgetManager = lifecycle.dispose(this._widgetManager); + this._linkHandler = dispose(this._linkHandler); + this._commandTrackerAddon = dispose(this._commandTrackerAddon); + this._widgetManager = dispose(this._widgetManager); if (this._xterm && this._xterm.element) { this._hadFocusOnExit = dom.hasClass(this._xterm.element, 'focus'); } if (this._wrapperElement) { - if ((this._wrapperElement).xterm) { - (this._wrapperElement).xterm = null; + if (this._wrapperElement.xterm) { + this._wrapperElement.xterm = undefined; } if (this._wrapperElement.parentElement) { this._container.removeChild(this._wrapperElement); @@ -813,39 +814,29 @@ export class TerminalInstance implements ITerminalInstance { this._pressAnyKeyToCloseListener = undefined; } - if (this._processManager) { - this._processManager.dispose(immediate); - } else { - // In cases where there is no associated process (for example executing an extension callback task) - // consumers still expect on onExit event to be fired. An example of this is terminating the extension callback - // task. - this._onExit.fire(0); - } + this._processManager.dispose(immediate); + // Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it + // hasn't happened yet + this._onProcessExit(undefined); if (!this._isDisposed) { this._isDisposed = true; this._onDisposed.fire(this); } - this._disposables.dispose(); - } - - public rendererExit(exitCode: number): void { - // The use of this API is for cases where there is no backing process behind a terminal - // instance (e.g. a custom execution task). - if (!this.shellLaunchConfig.isRendererOnly) { - throw new Error('rendererExit is only expected to be called on a renderer only terminal'); - } - - return this._onProcessExit(exitCode); + super.dispose(); } public forceRedraw(): void { + if (!this._xterm) { + return; + } if (this._configHelper.config.experimentalRefreshOnResume) { if (this._xterm.getOption('rendererType') !== 'dom') { this._xterm.setOption('rendererType', 'dom'); // Do this asynchronously to clear our the texture atlas as all terminals will not // be using canvas - setTimeout(() => this._xterm.setOption('rendererType', 'canvas'), 0); + const xterm = this._xterm; + setTimeout(() => xterm.setOption('rendererType', 'canvas'), 0); } } this._xterm.refresh(0, this._xterm.rows - 1); @@ -870,6 +861,9 @@ export class TerminalInstance implements ITerminalInstance { } public async paste(): Promise { + if (!this._xterm) { + return; + } this.focus(); this._xterm._core._coreService.triggerDataEvent(await this._clipboardService.readText(), true); } @@ -880,10 +874,6 @@ export class TerminalInstance implements ITerminalInstance { return; } this._xterm.write(text); - if (this._shellLaunchConfig.isRendererOnly) { - // Fire onData API in the extension host - this._onData.fire(text); - } }); } @@ -894,17 +884,8 @@ export class TerminalInstance implements ITerminalInstance { text += '\r'; } - if (this._shellLaunchConfig.isRendererOnly) { - // If the terminal is a renderer only, fire the onInput ext API - this._sendRendererInput(text); - } else { - // If the terminal has a process, send it to the process - if (this._processManager) { - this._processManager.ptyProcessReady.then(() => { - this._processManager!.write(text); - }); - } - } + // Send it to the process + this._processManager.ptyProcessReady.then(() => this._processManager.write(text)); } public setVisible(visible: boolean): void { @@ -936,31 +917,45 @@ export class TerminalInstance implements ITerminalInstance { } public scrollDownLine(): void { - this._xterm.scrollLines(1); + if (this._xterm) { + this._xterm.scrollLines(1); + } } public scrollDownPage(): void { - this._xterm.scrollPages(1); + if (this._xterm) { + this._xterm.scrollPages(1); + } } public scrollToBottom(): void { - this._xterm.scrollToBottom(); + if (this._xterm) { + this._xterm.scrollToBottom(); + } } public scrollUpLine(): void { - this._xterm.scrollLines(-1); + if (this._xterm) { + this._xterm.scrollLines(-1); + } } public scrollUpPage(): void { - this._xterm.scrollPages(-1); + if (this._xterm) { + this._xterm.scrollPages(-1); + } } public scrollToTop(): void { - this._xterm.scrollToTop(); + if (this._xterm) { + this._xterm.scrollToTop(); + } } public clear(): void { - this._xterm.clear(); + if (this._xterm) { + this._xterm.clear(); + } } private _refreshSelectionContextKey() { @@ -978,21 +973,32 @@ export class TerminalInstance implements ITerminalInstance { this._processManager.onProcessResolvedShellLaunchConfig(e => this._setResolvedShellLaunchConfig(e)); if (this._shellLaunchConfig.name) { - this.setTitle(this._shellLaunchConfig.name, false); + this.setTitle(this._shellLaunchConfig.name, TitleEventSource.Api); } else { // Only listen for process title changes when a name is not provided - this.setTitle(this._shellLaunchConfig.executable, true); - this._messageTitleDisposable = this._processManager.onProcessTitle(title => this.setTitle(title ? title : '', true)); + if (this._configHelper.config.experimentalUseTitleEvent) { + this._processManager.ptyProcessReady.then(() => { + this._terminalInstanceService.getDefaultShellAndArgs(false).then(e => { + this.setTitle(e.shell, TitleEventSource.Sequence); + }); + this._xtermReadyPromise.then(xterm => { + this._messageTitleDisposable = xterm.onTitleChange(e => this._onTitleChange(e)); + }); + }); + } else { + this.setTitle(this._shellLaunchConfig.executable, TitleEventSource.Process); + this._messageTitleDisposable = this._processManager.onProcessTitle(title => this.setTitle(title ? title : '', TitleEventSource.Process)); + } } if (platform.isWindows) { this._processManager.ptyProcessReady.then(() => { - if (this._processManager!.remoteAuthority) { + if (this._processManager.remoteAuthority) { return; } - this._xtermReadyPromise.then(() => { - if (!this._isDisposed) { - this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager!.shellProcessId, this, this._xterm); + this._xtermReadyPromise.then(xterm => { + if (!this._isDisposed && this._processManager && this._processManager.shellProcessId) { + this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, this, xterm); } }); }); @@ -1001,7 +1007,7 @@ export class TerminalInstance implements ITerminalInstance { // Create the process asynchronously to allow the terminal's container // to be created so dimensions are accurate setTimeout(() => { - this._processManager!.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized()); + this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized()); }, 0); } @@ -1021,13 +1027,13 @@ export class TerminalInstance implements ITerminalInstance { * through user action. */ private _onProcessExit(exitCode?: number): void { - this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`); - // Prevent dispose functions being triggered multiple times if (this._isExiting) { return; } + this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`); + this._isExiting = true; let exitCodeMessage: string | undefined; @@ -1039,7 +1045,7 @@ export class TerminalInstance implements ITerminalInstance { exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPathDirectory', 'The terminal shell path "{0}" is a directory', this._shellLaunchConfig.executable); } else if (exitCode === SHELL_CWD_INVALID_EXIT_CODE && this._shellLaunchConfig.cwd) { exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidCWD', 'The terminal shell CWD "{0}" does not exist', this._shellLaunchConfig.cwd.toString()); - } else if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { + } else if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { let args = ''; if (typeof this._shellLaunchConfig.args === 'string') { args = ` ${this._shellLaunchConfig.args}`; @@ -1061,29 +1067,31 @@ export class TerminalInstance implements ITerminalInstance { } } - this._logService.debug(`Terminal process exit (id: ${this.id})${this._processManager ? ' state ' + this._processManager.processState : ''}`); + this._logService.debug(`Terminal process exit (id: ${this.id}) state ${this._processManager.processState}`); // Only trigger wait on exit when the exit was *not* triggered by the // user (via the `workbench.action.terminal.kill` command). - if (this._shellLaunchConfig.waitOnExit && (!this._processManager || this._processManager.processState !== ProcessState.KILLED_BY_USER)) { - if (exitCodeMessage) { - this._xterm.writeln(exitCodeMessage); - } - if (typeof this._shellLaunchConfig.waitOnExit === 'string') { - let message = this._shellLaunchConfig.waitOnExit; - // Bold the message and add an extra new line to make it stand out from the rest of the output - message = `\r\n\x1b[1m${message}\x1b[0m`; - this._xterm.writeln(message); - } - // Disable all input if the terminal is exiting and listen for next keypress - this._xterm.setOption('disableStdin', true); - if (this._xterm.textarea) { - this._attachPressAnyKeyToCloseListener(); - } + if (this._shellLaunchConfig.waitOnExit && this._processManager.processState !== ProcessState.KILLED_BY_USER) { + this._xtermReadyPromise.then(xterm => { + if (exitCodeMessage) { + xterm.writeln(exitCodeMessage); + } + if (typeof this._shellLaunchConfig.waitOnExit === 'string') { + let message = this._shellLaunchConfig.waitOnExit; + // Bold the message and add an extra new line to make it stand out from the rest of the output + message = `\r\n\x1b[1m${message}\x1b[0m`; + xterm.writeln(message); + } + // Disable all input if the terminal is exiting and listen for next keypress + xterm.setOption('disableStdin', true); + if (xterm.textarea) { + this._attachPressAnyKeyToCloseListener(xterm); + } + }); } else { this.dispose(); if (exitCodeMessage) { - if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { + if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { this._notificationService.error(exitCodeMessage); } else { if (this._configHelper.config.showExitAlert) { @@ -1098,9 +1106,9 @@ export class TerminalInstance implements ITerminalInstance { this._onExit.fire(exitCode || 0); } - private _attachPressAnyKeyToCloseListener() { + private _attachPressAnyKeyToCloseListener(xterm: XTermTerminal) { if (!this._pressAnyKeyToCloseListener) { - this._pressAnyKeyToCloseListener = dom.addDisposableListener(this._xterm.textarea, 'keypress', (event: KeyboardEvent) => { + this._pressAnyKeyToCloseListener = dom.addDisposableListener(xterm.textarea, 'keypress', (event: KeyboardEvent) => { if (this._pressAnyKeyToCloseListener) { this._pressAnyKeyToCloseListener.dispose(); this._pressAnyKeyToCloseListener = undefined; @@ -1119,59 +1127,47 @@ export class TerminalInstance implements ITerminalInstance { } // Kill and clear up the process, making the process manager ready for a new process - if (this._processManager) { - this._processManager.dispose(); - this._processManager = undefined; + this._processManager.dispose(); + + if (this._xterm) { + // Ensure new processes' output starts at start of new line + this._xterm.write('\n\x1b[G'); + + // Print initialText if specified + if (shell.initialText) { + this._xterm.writeln(shell.initialText); + } + + // Clean up waitOnExit state + if (this._isExiting && this._shellLaunchConfig.waitOnExit) { + this._xterm.setOption('disableStdin', false); + this._isExiting = false; + } } - // Ensure new processes' output starts at start of new line - this._xterm.write('\n\x1b[G'); - - // Print initialText if specified - if (shell.initialText) { - this._xterm.writeln(shell.initialText); - } - - const oldTitle = this._title; - // Clean up waitOnExit state - if (this._isExiting && this._shellLaunchConfig.waitOnExit) { - this._xterm.setOption('disableStdin', false); - this._isExiting = false; - } + // HACK: Force initialText to be non-falsy for reused terminals such that the + // conptyInheritCursor flag is passed to the node-pty, this flag can cause a Window to hang + // in Windows 10 1903 so we only want to use it when something is definitely written to the + // terminal. + shell.initialText = ' '; // Set the new shell launch config this._shellLaunchConfig = shell; // Must be done before calling _createProcess() // Launch the process unless this is only a renderer. // In the renderer only cases, we still need to set the title correctly. - if (!this._shellLaunchConfig.isRendererOnly) { - this._createProcess(); - } else if (this._shellLaunchConfig.name) { - this.setTitle(this._shellLaunchConfig.name, false); - } + const oldTitle = this._title; + this._createProcess(); if (oldTitle !== this._title) { - this.setTitle(this._title, true); + this.setTitle(this._title, TitleEventSource.Process); } - if (this._processManager) { - // The "!" operator is required here because _processManager is set to undefiend earlier - // and TS does not know that createProcess sets it. - this._processManager!.onProcessData(data => this._onProcessData(data)); - } - } - - private _sendRendererInput(input: string): void { - if (this._processManager) { - throw new Error('onRendererInput attempted to be used on a regular terminal'); - } - - // For terminal renderers onData fires on keystrokes and when sendText is called. - this._onRendererInput.fire(input); + this._processManager.onProcessData(data => this._onProcessData(data)); } private _onLineFeed(): void { - const buffer = this._xterm.buffer; + const buffer = this._xterm!.buffer; const newLine = buffer.getLine(buffer.baseY + buffer.cursorY); if (newLine && !newLine.isWrapped) { this._sendLineData(buffer, buffer.baseY + buffer.cursorY - 1); @@ -1179,10 +1175,16 @@ export class TerminalInstance implements ITerminalInstance { } private _onCursorMove(): void { - const buffer = this._xterm.buffer; + const buffer = this._xterm!.buffer; this._sendLineData(buffer, buffer.baseY + buffer.cursorY); } + private _onTitleChange(title: string): void { + if (this.isTitleSetByProcess) { + this.setTitle(title, TitleEventSource.Sequence); + } + } + private _sendLineData(buffer: IBuffer, lineIndex: number): void { let line = buffer.getLine(lineIndex); if (!line) { @@ -1207,11 +1209,19 @@ export class TerminalInstance implements ITerminalInstance { } } + private async _onSelectionChange(): Promise { + if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) { + if (this.hasSelection()) { + await this.copySelection(); + } + } + } + @debounce(2000) private async _updateProcessCwd(): Promise { // reset cwd if it has changed, so file based url paths can be resolved const cwd = await this.getCwd(); - if (cwd) { + if (cwd && this._linkHandler) { this._linkHandler.processCwd = cwd; } return cwd; @@ -1234,14 +1244,14 @@ export class TerminalInstance implements ITerminalInstance { const isEnabled = this._isScreenReaderOptimized(); if (isEnabled) { this._navigationModeAddon = new NavigationModeAddon(this._terminalA11yTreeFocusContextKey); - this._xterm.loadAddon(this._navigationModeAddon); + this._xterm!.loadAddon(this._navigationModeAddon); } else { if (this._navigationModeAddon) { this._navigationModeAddon.dispose(); this._navigationModeAddon = undefined; } } - this._xterm.setOption('screenReaderMode', isEnabled); + this._xterm!.setOption('screenReaderMode', isEnabled); } private _setCursorBlink(blink: boolean): void { @@ -1352,37 +1362,36 @@ export class TerminalInstance implements ITerminalInstance { } } - if (this._processManager) { - this._processManager.ptyProcessReady.then(() => this._processManager!.setDimensions(cols, rows)); - } + this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(cols, rows)); } - public setTitle(title: string | undefined, eventFromProcess: boolean): void { + public setTitle(title: string | undefined, eventSource: TitleEventSource): void { if (!title) { return; } - if (eventFromProcess) { - title = path.basename(title); - if (platform.isWindows) { - // Remove the .exe extension - title = title.split('.exe')[0]; - } - } else { - // If the title has not been set by the API or the rename command, unregister the handler that - // automatically updates the terminal name - if (this._messageTitleDisposable) { - lifecycle.dispose(this._messageTitleDisposable); - lifecycle.dispose(this._windowsShellHelper); + switch (eventSource) { + case TitleEventSource.Process: + title = path.basename(title); + if (platform.isWindows) { + // Remove the .exe extension + title = title.split('.exe')[0]; + } + break; + case TitleEventSource.Api: + // If the title has not been set by the API or the rename command, unregister the handler that + // automatically updates the terminal name + dispose(this._messageTitleDisposable); this._messageTitleDisposable = undefined; + dispose(this._windowsShellHelper); this._windowsShellHelper = undefined; - } + break; } const didTitleChange = title !== this._title; - const oldTitle = this._title; this._title = title; if (didTitleChange) { - if (!oldTitle) { + if (this._titleReadyComplete) { this._titleReadyComplete(title); + this._titleReadyComplete = undefined; } this._onTitleChanged.fire(this); } @@ -1440,25 +1449,21 @@ export class TerminalInstance implements ITerminalInstance { }; } - private _updateTheme(theme?: ITheme): void { - this._xterm.setOption('theme', this._getXtermTheme(theme)); + private _updateTheme(xterm: XTermTerminal, theme?: ITheme): void { + xterm.setOption('theme', this._getXtermTheme(theme)); } - public toggleEscapeSequenceLogging(): void { - this._xterm.setOption('logLevel', 'debug'); + public async toggleEscapeSequenceLogging(): Promise { + const xterm = await this._xtermReadyPromise; + const isDebug = xterm.getOption('logLevel') === 'debug'; + xterm.setOption('logLevel', isDebug ? 'info' : 'debug'); } public getInitialCwd(): Promise { - if (!this._processManager) { - return Promise.resolve(''); - } return this._processManager.getInitialCwd(); } public getCwd(): Promise { - if (!this._processManager) { - return Promise.resolve(''); - } return this._processManager.getCwd(); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index 08980a94000..8eff2daf466 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -10,6 +10,7 @@ import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; let Terminal: typeof XTermTerminal; let WebLinksAddon: typeof XTermWebLinksAddon; @@ -52,11 +53,16 @@ export class TerminalInstanceService implements ITerminalInstanceService { throw new Error('Not implemented'); } - public getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }> { - return new Promise(r => this._onRequestDefaultShellAndArgs.fire((shell, args) => r({ shell, args }))); + public getDefaultShellAndArgs(useAutomationShell: boolean, ): Promise<{ shell: string, args: string[] | string | undefined }> { + return new Promise(r => this._onRequestDefaultShellAndArgs.fire({ + useAutomationShell, + callback: (shell, args) => r({ shell, args }) + })); } public async getMainProcessParentEnv(): Promise { return {}; } -} \ No newline at end of file +} + +registerSingleton(ITerminalInstanceService, TerminalInstanceService, true); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index a89f5bf7cdc..ba6ddcea32f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -5,15 +5,15 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ITerminalService, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessManager, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IFileService } from 'vs/platform/files/common/files'; -import { ILinkMatcherOptions } from 'xterm'; +import { Terminal, ILinkMatcherOptions } from 'xterm'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { posix, win32 } from 'vs/base/common/path'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -38,6 +38,7 @@ const winLocalLinkClause = '((' + winPathPrefix + '|(' + winExcludedPathCharacte replacing space with nonBreakningSpace or space ASCII code - 32. */ const lineAndColumnClause = [ '((\\S*)", line ((\\d+)( column (\\d+))?))', // "(file path)", line 45 [see #40468] + '((\\S*)",((\\d+)(:(\\d+))?))', // "(file path)",45 [see #78205] '((\\S*) on line ((\\d+)(, column (\\d+))?))', // (file path) on line 8, column 13 '((\\S*):line ((\\d+)(, column (\\d+))?))', // (file path):line 8, column 13 '(([^\\s\\(\\)]*)(\\s?[\\(\\[](\\d+)(,\\s?(\\d+))?)[\\)\\]])', // (file path)(45), (file path) (45), (file path)(45,18), (file path) (45,18), (file path)(45, 18), (file path) (45, 18), also with [] @@ -66,21 +67,20 @@ interface IPath { export class TerminalLinkHandler { private readonly _hoverDisposables = new DisposableStore(); - private _mouseMoveDisposable: IDisposable; - private _widgetManager: TerminalWidgetManager; - private _processCwd: string; + private _widgetManager: TerminalWidgetManager | undefined; + private _processCwd: string | undefined; private _gitDiffPreImagePattern: RegExp; private _gitDiffPostImagePattern: RegExp; private readonly _tooltipCallback: (event: MouseEvent, uri: string) => boolean | void; private readonly _leaveCallback: () => void; constructor( - private _xterm: any, + private _xterm: Terminal, private readonly _processManager: ITerminalProcessManager | undefined, + private readonly _configHelper: ITerminalConfigHelper, @IOpenerService private readonly _openerService: IOpenerService, @IEditorService private readonly _editorService: IEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @IFileService private readonly _fileService: IFileService ) { @@ -93,7 +93,7 @@ export class TerminalLinkHandler { if (!this._widgetManager) { return; } - if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { + if (this._configHelper.config.rendererType === 'dom') { const target = (e.target as HTMLElement); this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); } else { @@ -182,10 +182,7 @@ export class TerminalLinkHandler { } public dispose(): void { - this._xterm = null; - this._hoverDisposables.dispose(); - this._mouseMoveDisposable = dispose(this._mouseMoveDisposable); } private _wrapLinkHandler(handler: (uri: string) => boolean | void): XtermLinkMatcherHandler { @@ -194,9 +191,6 @@ export class TerminalLinkHandler { event.preventDefault(); // Require correct modifier on click if (!this._isLinkActivationModifierDown(event)) { - // If the modifier is not pressed, the terminal should be - // focused if it's not already - this._terminalService.getActiveInstance()!.focus(true); return false; } return handler(uri); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts b/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts index 2ed3246317c..ce98262677e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts @@ -6,6 +6,7 @@ import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; import { ITerminalNativeService, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; import { Emitter, Event } from 'vs/base/common/event'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class TerminalNativeService implements ITerminalNativeService { public _serviceBrand: any; @@ -30,4 +31,6 @@ export class TerminalNativeService implements ITerminalNativeService { public getWindowsBuildNumber(): number { throw new Error('Not implemented'); } -} \ No newline at end of file +} + +registerSingleton(ITerminalNativeService, TerminalNativeService, true); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index c79e4eedcb0..5c3eae374ef 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -29,14 +29,14 @@ const FIND_FOCUS_CLASS = 'find-focused'; export class TerminalPanel extends Panel { - private _actions: IAction[]; - private _copyContextMenuAction: IAction; - private _contextMenuActions: IAction[]; + private _actions: IAction[] | undefined; + private _copyContextMenuAction: IAction | undefined; + private _contextMenuActions: IAction[] | undefined; private _cancelContextMenu: boolean = false; - private _fontStyleElement: HTMLElement; - private _parentDomElement: HTMLElement; - private _terminalContainer: HTMLElement; - private _findWidget: TerminalFindWidget; + private _fontStyleElement: HTMLElement | undefined; + private _parentDomElement: HTMLElement | undefined; + private _terminalContainer: HTMLElement | undefined; + private _findWidget: TerminalFindWidget | undefined; constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -61,14 +61,13 @@ export class TerminalPanel extends Panel { dom.addClass(this._terminalContainer, 'terminal-outer-container'); this._findWidget = this._instantiationService.createInstance(TerminalFindWidget, this._terminalService.getFindState()); - this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer.classList.add(FIND_FOCUS_CLASS)); - this._findWidget.focusTracker.onDidBlur(() => this._terminalContainer.classList.remove(FIND_FOCUS_CLASS)); + this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer!.classList.add(FIND_FOCUS_CLASS)); this._parentDomElement.appendChild(this._fontStyleElement); this._parentDomElement.appendChild(this._terminalContainer); this._parentDomElement.appendChild(this._findWidget.getDomNode()); - this._attachEventListeners(); + this._attachEventListeners(this._parentDomElement, this._terminalContainer); this._terminalService.setContainers(this.getContainer(), this._terminalContainer); @@ -137,7 +136,7 @@ export class TerminalPanel extends Panel { } private _getContextMenuActions(): IAction[] { - if (!this._contextMenuActions) { + if (!this._contextMenuActions || !this._copyContextMenuAction) { this._copyContextMenuAction = this._instantiationService.createInstance(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.SHORT_LABEL); this._contextMenuActions = [ this._instantiationService.createInstance(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.SHORT_LABEL), @@ -179,31 +178,31 @@ export class TerminalPanel extends Panel { public focusFindWidget() { const activeInstance = this._terminalService.getActiveInstance(); if (activeInstance && activeInstance.hasSelection() && activeInstance.selection!.indexOf('\n') === -1) { - this._findWidget.reveal(activeInstance.selection); + this._findWidget!.reveal(activeInstance.selection); } else { - this._findWidget.reveal(); + this._findWidget!.reveal(); } } public hideFindWidget() { - this._findWidget.hide(); + this._findWidget!.hide(); } public showFindWidget() { const activeInstance = this._terminalService.getActiveInstance(); if (activeInstance && activeInstance.hasSelection() && activeInstance.selection!.indexOf('\n') === -1) { - this._findWidget.show(activeInstance.selection); + this._findWidget!.show(activeInstance.selection); } else { - this._findWidget.show(); + this._findWidget!.show(); } } public getFindWidget(): TerminalFindWidget { - return this._findWidget; + return this._findWidget!; } - private _attachEventListeners(): void { - this._register(dom.addDisposableListener(this._parentDomElement, 'mousedown', async (event: MouseEvent) => { + private _attachEventListeners(parentDomElement: HTMLElement, terminalContainer: HTMLElement): void { + this._register(dom.addDisposableListener(parentDomElement, 'mousedown', async (event: MouseEvent) => { if (this._terminalService.terminalInstances.length === 0) { return; } @@ -240,21 +239,7 @@ export class TerminalPanel extends Panel { } } })); - this._register(dom.addDisposableListener(this._parentDomElement, 'mouseup', async (event: MouseEvent) => { - if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) { - if (this._terminalService.terminalInstances.length === 0) { - return; - } - - if (event.which === 1) { - const terminal = this._terminalService.getActiveInstance(); - if (terminal && terminal.hasSelection()) { - await terminal.copySelection(); - } - } - } - })); - this._register(dom.addDisposableListener(this._parentDomElement, 'contextmenu', (event: MouseEvent) => { + this._register(dom.addDisposableListener(parentDomElement, 'contextmenu', (event: MouseEvent) => { if (!this._cancelContextMenu) { const standardEvent = new StandardMouseEvent(event); const anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy }; @@ -269,19 +254,19 @@ export class TerminalPanel extends Panel { this._cancelContextMenu = false; })); this._register(dom.addDisposableListener(document, 'keydown', (event: KeyboardEvent) => { - this._terminalContainer.classList.toggle('alt-active', !!event.altKey); + terminalContainer.classList.toggle('alt-active', !!event.altKey); })); this._register(dom.addDisposableListener(document, 'keyup', (event: KeyboardEvent) => { - this._terminalContainer.classList.toggle('alt-active', !!event.altKey); + terminalContainer.classList.toggle('alt-active', !!event.altKey); })); - this._register(dom.addDisposableListener(this._parentDomElement, 'keyup', (event: KeyboardEvent) => { + this._register(dom.addDisposableListener(parentDomElement, 'keyup', (event: KeyboardEvent) => { if (event.keyCode === 27) { // Keep terminal open on escape event.stopPropagation(); } })); - this._register(dom.addDisposableListener(this._parentDomElement, dom.EventType.DROP, async (e: DragEvent) => { - if (e.target === this._parentDomElement || dom.isAncestor(e.target as HTMLElement, this._parentDomElement)) { + this._register(dom.addDisposableListener(parentDomElement, dom.EventType.DROP, async (e: DragEvent) => { + if (e.target === this._parentDomElement || dom.isAncestor(e.target as HTMLElement, parentDomElement)) { if (!e.dataTransfer) { return; } @@ -291,7 +276,7 @@ export class TerminalPanel extends Panel { const resources = e.dataTransfer.getData(DataTransfers.RESOURCES); if (resources) { path = URI.parse(JSON.parse(resources)[0]).fsPath; - } else if (e.dataTransfer.files.length > 0) { + } else if (e.dataTransfer.files.length > 0 && e.dataTransfer.files[0].path /* Electron only */) { // Check if the file was dragged from the filesystem path = URI.file(e.dataTransfer.files[0].path).fsPath; } @@ -315,11 +300,13 @@ export class TerminalPanel extends Panel { theme = this.themeService.getTheme(); } - this._findWidget.updateTheme(theme); + if (this._findWidget) { + this._findWidget.updateTheme(theme); + } } private _updateFont(): void { - if (this._terminalService.terminalInstances.length === 0) { + if (this._terminalService.terminalInstances.length === 0 || !this._parentDomElement) { return; } // TODO: Can we support ligatures? diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 37dc39b51f6..d99d8de0ab1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -5,6 +5,7 @@ import * as platform from 'vs/base/common/platform'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; +import { env as processEnv } from 'vs/base/common/process'; import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal'; import { ILogService } from 'vs/platform/log/common/log'; import { Emitter, Event } from 'vs/base/common/event'; @@ -20,6 +21,7 @@ import { IProductService } from 'vs/platform/product/common/product'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { Disposable } from 'vs/base/common/lifecycle'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; @@ -42,10 +44,10 @@ enum ProcessType { * - Pty Process: The pseudoterminal master process (or the winpty agent process) * - Shell Process: The pseudoterminal slave process (ie. the shell) */ -export class TerminalProcessManager implements ITerminalProcessManager { +export class TerminalProcessManager extends Disposable implements ITerminalProcessManager { public processState: ProcessState = ProcessState.UNINITIALIZED; public ptyProcessReady: Promise; - public shellProcessId: number; + public shellProcessId: number | undefined; public remoteAuthority: string | undefined; public os: platform.OperatingSystem | undefined; public userHome: string | undefined; @@ -54,23 +56,22 @@ export class TerminalProcessManager implements ITerminalProcessManager { private _processType: ProcessType = ProcessType.Process; private _preLaunchInputQueue: string[] = []; private _latency: number = -1; - private _latencyRequest: Promise; private _latencyLastMeasured: number = 0; - private _initialCwd: string; + private _initialCwd: string | undefined; - private readonly _onProcessReady = new Emitter(); + private readonly _onProcessReady = this._register(new Emitter()); public get onProcessReady(): Event { return this._onProcessReady.event; } - private readonly _onBeforeProcessData = new Emitter(); + private readonly _onBeforeProcessData = this._register(new Emitter()); public get onBeforeProcessData(): Event { return this._onBeforeProcessData.event; } - private readonly _onProcessData = new Emitter(); + private readonly _onProcessData = this._register(new Emitter()); public get onProcessData(): Event { return this._onProcessData.event; } - private readonly _onProcessTitle = new Emitter(); + private readonly _onProcessTitle = this._register(new Emitter()); public get onProcessTitle(): Event { return this._onProcessTitle.event; } - private readonly _onProcessExit = new Emitter(); + private readonly _onProcessExit = this._register(new Emitter()); public get onProcessExit(): Event { return this._onProcessExit.event; } - private readonly _onProcessOverrideDimensions = new Emitter(); + private readonly _onProcessOverrideDimensions = this._register(new Emitter()); public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } - private readonly _onProcessOverrideShellLaunchConfig = new Emitter(); + private readonly _onProcessOverrideShellLaunchConfig = this._register(new Emitter()); public get onProcessResolvedShellLaunchConfig(): Event { return this._onProcessOverrideShellLaunchConfig.event; } constructor( @@ -87,6 +88,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService ) { + super(); this.ptyProcessReady = new Promise(c => { this.onProcessReady(() => { this._logService.debug(`Terminal process ready (shellProcessId: ${this.shellProcessId})`); @@ -105,6 +107,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { this._process.shutdown(immediate); this._process = null; } + super.dispose(); } public async createProcess( @@ -193,7 +196,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); const lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null; if (!shellLaunchConfig.executable) { - const defaultConfig = await this._terminalInstanceService.getDefaultShellAndArgs(); + const defaultConfig = await this._terminalInstanceService.getDefaultShellAndArgs(false); shellLaunchConfig.executable = defaultConfig.shell; shellLaunchConfig.args = defaultConfig.args; } else { @@ -223,7 +226,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { const envFromConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.env.${platformKey}`); const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions(); this._configHelper.showRecommendations(shellLaunchConfig); - const baseEnv = this._configHelper.config.inheritEnv ? process.env as platform.IProcessEnvironment : await this._terminalInstanceService.getMainProcessParentEnv(); + const baseEnv = this._configHelper.config.inheritEnv ? processEnv : await this._terminalInstanceService.getMainProcessParentEnv(); const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.setLocaleVariables, baseEnv); const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled; @@ -259,7 +262,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { } public getInitialCwd(): Promise { - return Promise.resolve(this._initialCwd); + return Promise.resolve(this._initialCwd ? this._initialCwd : ''); } public getCwd(): Promise { @@ -275,8 +278,8 @@ export class TerminalProcessManager implements ITerminalProcessManager { return Promise.resolve(0); } if (this._latencyLastMeasured === 0 || this._latencyLastMeasured + LATENCY_MEASURING_INTERVAL < Date.now()) { - this._latencyRequest = this._process.getLatency(); - this._latency = await this._latencyRequest; + const latencyRequest = this._process.getLatency(); + this._latency = await latencyRequest; this._latencyLastMeasured = Date.now(); } return Promise.resolve(this._latency); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 9ca71e3d27b..bfa25469cea 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -5,7 +5,7 @@ import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalService as CommonTerminalService } from 'vs/workbench/contrib/terminal/common/terminalService'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -49,18 +49,15 @@ export class TerminalService extends CommonTerminalService implements ITerminalS this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, this.terminalNativeService.linuxDistro); } - public createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance { - const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig); + public createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance { + const instance = this._instantiationService.createInstance(TerminalInstance, this._terminalFocusContextKey, this._configHelper, container, shellLaunchConfig); this._onInstanceCreated.fire(instance); return instance; } public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance { if (shell.hideFromUser) { - const instance = this.createInstance(this._terminalFocusContextKey, - this.configHelper, - undefined, - shell); + const instance = this.createInstance(undefined, shell); this._backgroundedTerminalInstances.push(instance); this._initInstanceListeners(instance); return instance; @@ -137,7 +134,7 @@ export class TerminalService extends CommonTerminalService implements ITerminalS public setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void { this._configHelper.panelContainer = panelContainer; this._terminalContainer = terminalContainer; - this._terminalTabs.forEach(tab => tab.attachToElement(this._terminalContainer)); + this._terminalTabs.forEach(tab => tab.attachToElement(terminalContainer)); } public hidePanel(): void { @@ -146,4 +143,4 @@ export class TerminalService extends CommonTerminalService implements ITerminalS this._layoutService.setPanelHidden(true); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts index e00296853ff..8f2fd4fbaee 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts @@ -18,7 +18,7 @@ const TERMINAL_MIN_USEFUL_SIZE = 250; class SplitPaneContainer extends Disposable { private _height: number; private _width: number; - private _splitView: SplitView; + private _splitView!: SplitView; private readonly _splitViewDisposables = this._register(new DisposableStore()); private _children: SplitPane[] = []; @@ -177,7 +177,6 @@ class SplitPane implements IView { public maximumSize: number = Number.MAX_VALUE; public orientation: Orientation | undefined; - protected _size: number; private _onDidChange: Event = Event.None; public get onDidChange(): Event { return this._onDidChange; } @@ -195,15 +194,14 @@ class SplitPane implements IView { public layout(size: number): void { // Only layout when both sizes are known - this._size = size; - if (!this._size || !this.orthogonalSize) { + if (!size || !this.orthogonalSize) { return; } if (this.orientation === Orientation.VERTICAL) { - this.instance.layout({ width: this.orthogonalSize, height: this._size }); + this.instance.layout({ width: this.orthogonalSize, height: size }); } else { - this.instance.layout({ width: this._size, height: this.orthogonalSize }); + this.instance.layout({ width: size, height: this.orthogonalSize }); } } @@ -215,22 +213,22 @@ class SplitPane implements IView { export class TerminalTab extends Disposable implements ITerminalTab { private _terminalInstances: ITerminalInstance[] = []; private _splitPaneContainer: SplitPaneContainer | undefined; - private _tabElement: HTMLElement | null; + private _tabElement: HTMLElement | undefined; private _panelPosition: Position = Position.BOTTOM; private _activeInstanceIndex: number; public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } - private readonly _onDisposed: Emitter = new Emitter(); + private readonly _onDisposed: Emitter = this._register(new Emitter()); public readonly onDisposed: Event = this._onDisposed.event; - private readonly _onInstancesChanged: Emitter = new Emitter(); + private readonly _onInstancesChanged: Emitter = this._register(new Emitter()); public readonly onInstancesChanged: Event = this._onInstancesChanged.event; constructor( terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, - private _container: HTMLElement, + private _container: HTMLElement | undefined, shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance, @ITerminalService private readonly _terminalService: ITerminalService, @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @@ -242,11 +240,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { if ('id' in shellLaunchConfigOrInstance) { instance = shellLaunchConfigOrInstance; } else { - instance = this._terminalService.createInstance( - terminalFocusContextKey, - configHelper, - undefined, - shellLaunchConfigOrInstance); + instance = this._terminalService.createInstance(undefined, shellLaunchConfigOrInstance); } this._terminalInstances.push(instance); this._initInstanceListeners(instance); @@ -259,9 +253,9 @@ export class TerminalTab extends Disposable implements ITerminalTab { public dispose(): void { super.dispose(); - if (this._tabElement) { + if (this._container && this._tabElement) { this._container.removeChild(this._tabElement); - this._tabElement = null; + this._tabElement = undefined; } this._terminalInstances = []; this._onInstancesChanged.fire(); @@ -380,15 +374,14 @@ export class TerminalTab extends Disposable implements ITerminalTab { configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig ): ITerminalInstance | undefined { + if (!this._container) { + throw new Error('Cannot split terminal that has not been attached'); + } const newTerminalSize = ((this._panelPosition === Position.BOTTOM ? this._container.clientWidth : this._container.clientHeight) / (this._terminalInstances.length + 1)); if (newTerminalSize < TERMINAL_MIN_USEFUL_SIZE) { return undefined; } - const instance = this._terminalService.createInstance( - terminalFocusContextKey, - configHelper, - undefined, - shellLaunchConfig); + const instance = this._terminalService.createInstance(undefined, shellLaunchConfig); this._terminalInstances.splice(this._activeInstanceIndex + 1, 0, instance); this._initInstanceListeners(instance); this._setActiveInstance(instance); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts index 8beb67cb689..ce9ed466a98 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts @@ -8,10 +8,10 @@ import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle' const WIDGET_HEIGHT = 29; export class TerminalWidgetManager implements IDisposable { - private _container: HTMLElement | null; - private _xtermViewport: HTMLElement | null; + private _container: HTMLElement | undefined; + private _xtermViewport: HTMLElement | undefined; - private _messageWidget: MessageWidget; + private _messageWidget: MessageWidget | undefined; private readonly _messageListeners = new DisposableStore(); constructor( @@ -27,9 +27,9 @@ export class TerminalWidgetManager implements IDisposable { public dispose(): void { if (this._container && this._container.parentElement) { this._container.parentElement.removeChild(this._container); - this._container = null; + this._container = undefined; } - this._xtermViewport = null; + this._xtermViewport = undefined; this._messageListeners.dispose(); } @@ -108,4 +108,4 @@ class MessageWidget { this._container.removeChild(this.domNode); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index f6413d9857e..48698da20ee 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -76,6 +76,11 @@ export interface ITerminalConfiguration { osx: string | null; windows: string | null; }; + automationShell: { + linux: string | null; + osx: string | null; + windows: string | null; + }; shellArgs: { linux: string[]; osx: string[]; @@ -112,6 +117,7 @@ export interface ITerminalConfiguration { splitCwd: 'workspaceRoot' | 'initial' | 'inherited'; windowsEnableConpty: boolean; experimentalRefreshOnResume: boolean; + experimentalUseTitleEvent: boolean; } export interface ITerminalConfigHelper { @@ -187,11 +193,6 @@ export interface IShellLaunchConfig { */ initialText?: string; - /** - * @deprecated use `isExtensionTerminal` - */ - isRendererOnly?: boolean; - /** * Whether an extension is controlling the terminal via a `vscode.Pseudoterminal`. */ @@ -244,16 +245,10 @@ export interface ITerminalService { */ createTerminal(shell?: IShellLaunchConfig): ITerminalInstance; - /** - * Creates a terminal renderer. - * @param name The name of the terminal. - */ - createTerminalRenderer(name: string): ITerminalInstance; - /** * Creates a raw terminal instance, this should not be used outside of the terminal part. */ - createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; + createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; getInstanceFromId(terminalId: number): ITerminalInstance | undefined; getInstanceFromIndex(terminalIndex: number): ITerminalInstance; getTabLabels(): string[]; @@ -285,7 +280,7 @@ export interface ITerminalService { selectDefaultWindowsShell(): Promise; setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; - setWorkspaceShellAllowed(isAllowed: boolean): void; + manageWorkspaceShellPermissions(): void; /** * Takes a path and returns the properly escaped path to send to the terminal. @@ -422,13 +417,6 @@ export interface ITerminalInstance { */ onData: Event; - /** - * Attach a listener to the "renderer" input event, this event fires for terminal renderers on - * keystrokes and when the Terminal.sendText extension API is used. - * @param listener The listener function. - */ - onRendererInput: Event; - /** * Attach a listener to listen for new lines added to this terminal instance. * @@ -483,7 +471,7 @@ export interface ITerminalInstance { * An object that tracks when commands are run and enables navigating and selecting between * them. */ - readonly commandTracker: ITerminalCommandTracker; + readonly commandTracker: ICommandTracker | undefined; readonly navigationMode: INavigationMode | undefined; @@ -497,14 +485,6 @@ export interface ITerminalInstance { */ dispose(immediate?: boolean): void; - /** - * Indicates that a consumer of a renderer only terminal is finished with it. - * - * @param exitCode The exit code of the terminal. Zero indicates success, non-zero indicates - * failure. - */ - rendererExit(exitCode: number): void; - /** * Forces the terminal to redraw its viewport. */ @@ -657,7 +637,7 @@ export interface ITerminalInstance { /** * Sets the title of the terminal instance. */ - setTitle(title: string, eventFromProcess: boolean): void; + setTitle(title: string, eventSource: TitleEventSource): void; waitForTitle(): Promise; @@ -671,7 +651,7 @@ export interface ITerminalInstance { getCwd(): Promise; } -export interface ITerminalCommandTracker { +export interface ICommandTracker { scrollToPreviousCommand(): void; scrollToNextCommand(): void; selectToPreviousCommand(): void; @@ -697,7 +677,7 @@ export interface IBeforeProcessDataEvent { export interface ITerminalProcessManager extends IDisposable { readonly processState: ProcessState; readonly ptyProcessReady: Promise; - readonly shellProcessId: number; + readonly shellProcessId: number | undefined; readonly remoteAuthority: string | undefined; readonly os: OperatingSystem | undefined; readonly userHome: string | undefined; @@ -780,7 +760,8 @@ export interface IAvailableShellsRequest { } export interface IDefaultShellAndArgsRequest { - (shell: string, args: string[] | string | undefined): void; + useAutomationShell: boolean; + callback: (shell: string, args: string[] | string | undefined) => void; } export enum LinuxDistro { @@ -789,6 +770,15 @@ export enum LinuxDistro { Unknown } +export enum TitleEventSource { + /** From the API or the rename command that overrides any other type */ + Api, + /** From the process name property*/ + Process, + /** From the VT sequence */ + Sequence +} + export interface IWindowsShellHelper extends IDisposable { getShellName(): Promise; } diff --git a/src/vs/workbench/contrib/terminal/common/terminalCommands.ts b/src/vs/workbench/contrib/terminal/common/terminalCommands.ts index 60da258805d..ddd108304ae 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalCommands.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalCommands.ts @@ -48,8 +48,7 @@ export const enum TERMINAL_COMMAND_ID { SCROLL_TO_TOP = 'workbench.action.terminal.scrollToTop', CLEAR = 'workbench.action.terminal.clear', CLEAR_SELECTION = 'workbench.action.terminal.clearSelection', - WORKSPACE_SHELL_ALLOW = 'workbench.action.terminal.allowWorkspaceShell', - WORKSPACE_SHELL_DISALLOW = 'workbench.action.terminal.disallowWorkspaceShell', + MANAGE_WORKSPACE_SHELL_PERMISSIONS = 'workbench.action.terminal.manageWorkspaceShellPermissions', RENAME = 'workbench.action.terminal.rename', FIND_WIDGET_FOCUS = 'workbench.action.terminal.focusFindWidget', FIND_WIDGET_HIDE = 'workbench.action.terminal.hideFindWidget', diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index a240585b4dc..6b9a3ec87b7 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -62,7 +62,7 @@ export function addTerminalEnvironmentKeys(env: platform.IProcessEnvironment, ve env['COLORTERM'] = 'truecolor'; } -function mergeNonNullKeys(env: platform.IProcessEnvironment, other: ITerminalEnvironment | NodeJS.ProcessEnv | undefined) { +function mergeNonNullKeys(env: platform.IProcessEnvironment, other: ITerminalEnvironment | undefined) { if (!other) { return; } @@ -78,7 +78,11 @@ function resolveConfigurationVariables(configurationResolverService: IConfigurat Object.keys(env).forEach((key) => { const value = env[key]; if (typeof value === 'string' && lastActiveWorkspaceRoot !== null) { - env[key] = configurationResolverService.resolve(lastActiveWorkspaceRoot, value); + try { + env[key] = configurationResolverService.resolve(lastActiveWorkspaceRoot, value); + } catch (e) { + env[key] = value; + } } }); return env; @@ -138,20 +142,22 @@ export function getCwd( if (!shell.ignoreConfigurationCwd && customCwd) { if (configurationResolverService) { try { - cwd = configurationResolverService.resolve(lastActiveWorkspace, customCwd); + customCwd = configurationResolverService.resolve(lastActiveWorkspace, customCwd); } catch (e) { - // There was an issue resolving a variable, just use the unresolved customCwd which - // which will fail, and log the error in the console. - cwd = customCwd; + // There was an issue resolving a variable, log the error in the console and + // fallback to the default. if (logService) { - logService.error('Resolving terminal.integrated.cwd', e); + logService.error('Could not resolve terminal.integrated.cwd', e); } + customCwd = undefined; } } - if (path.isAbsolute(customCwd) && !cwd) { - cwd = customCwd; - } else if (root && !cwd) { - cwd = path.join(root.fsPath, customCwd); + if (customCwd) { + if (path.isAbsolute(customCwd)) { + cwd = customCwd; + } else if (root) { + cwd = path.join(root.fsPath, customCwd); + } } } @@ -192,19 +198,27 @@ export function getDefaultShell( windir: string | undefined, lastActiveWorkspace: IWorkspaceFolder | undefined, configurationResolverService: IConfigurationResolverService | undefined, - platformOverride: platform.Platform = platform.platform, + logService: ILogService, + useAutomationShell: boolean, + platformOverride: platform.Platform = platform.platform ): string { - const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; - const shellConfigValue = fetchSetting(`terminal.integrated.shell.${platformKey}`); - let executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || (shellConfigValue.default || defaultShell); + let maybeExecutable: string | null = null; + if (useAutomationShell) { + // If automationShell is specified, this should override the normal setting + maybeExecutable = getShellSetting(fetchSetting, isWorkspaceShellAllowed, 'automationShell', platformOverride); + } + if (!maybeExecutable) { + maybeExecutable = getShellSetting(fetchSetting, isWorkspaceShellAllowed, 'shell', platformOverride); + } + let executable: string = maybeExecutable || defaultShell; // Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's // safe to assume that this was used by accident as Sysnative does not // exist and will break the terminal in non-WoW64 environments. if ((platformOverride === platform.Platform.Windows) && !isWoW64 && windir) { - const sysnativePath = path.join(windir, 'Sysnative').toLowerCase(); + const sysnativePath = path.join(windir, 'Sysnative').replace(/\//g, '\\').toLowerCase(); if (executable && executable.toLowerCase().indexOf(sysnativePath) === 0) { - executable = path.join(windir, 'System32', executable.substr(sysnativePath.length)); + executable = path.join(windir, 'System32', executable.substr(sysnativePath.length + 1)); } } @@ -214,7 +228,12 @@ export function getDefaultShell( } if (configurationResolverService) { - executable = configurationResolverService.resolve(lastActiveWorkspace, executable); + try { + executable = configurationResolverService.resolve(lastActiveWorkspace, executable); + } catch (e) { + logService.error(`Could not resolve shell`, e); + executable = executable; + } } return executable; @@ -223,10 +242,18 @@ export function getDefaultShell( export function getDefaultShellArgs( fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined }, isWorkspaceShellAllowed: boolean, + useAutomationShell: boolean, lastActiveWorkspace: IWorkspaceFolder | undefined, configurationResolverService: IConfigurationResolverService | undefined, + logService: ILogService, platformOverride: platform.Platform = platform.platform, ): string | string[] { + if (useAutomationShell) { + if (!!getShellSetting(fetchSetting, isWorkspaceShellAllowed, 'automationShell', platformOverride)) { + return []; + } + } + const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`); let args = ((isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.user) || shellArgsConfigValue.default); @@ -236,13 +263,30 @@ export function getDefaultShellArgs( if (configurationResolverService) { const resolvedArgs: string[] = []; for (const arg of args) { - resolvedArgs.push(configurationResolverService.resolve(lastActiveWorkspace, arg)); + try { + resolvedArgs.push(configurationResolverService.resolve(lastActiveWorkspace, arg)); + } catch (e) { + logService.error(`Could not resolve terminal.integrated.shellArgs.${platformKey}`, e); + resolvedArgs.push(arg); + } } args = resolvedArgs; } return args; } +function getShellSetting( + fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined }, + isWorkspaceShellAllowed: boolean, + type: 'automationShell' | 'shell', + platformOverride: platform.Platform = platform.platform, +): string | null { + const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; + const shellConfigValue = fetchSetting(`terminal.integrated.${type}.${platformKey}`); + const executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || (shellConfigValue.default); + return executable; +} + export function createTerminalEnvironment( shellLaunchConfig: IShellLaunchConfig, lastActiveWorkspace: IWorkspaceFolder | null, diff --git a/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts index 80923d84486..dfc07d6efe7 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts @@ -18,13 +18,13 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal public readonly onProcessData: Event = this._onProcessData.event; private readonly _onProcessExit = this._register(new Emitter()); public readonly onProcessExit: Event = this._onProcessExit.event; - private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>(); + private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>()); public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } private readonly _onProcessTitleChanged = this._register(new Emitter()); public readonly onProcessTitleChanged: Event = this._onProcessTitleChanged.event; - private readonly _onProcessOverrideDimensions = new Emitter(); + private readonly _onProcessOverrideDimensions = this._register(new Emitter()); public get onProcessOverrideDimensions(): Event { return this._onProcessOverrideDimensions.event; } - private readonly _onProcessResolvedShellLaunchConfig = new Emitter(); + private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter()); public get onProcessResolvedShellLaunchConfig(): Event { return this._onProcessResolvedShellLaunchConfig.event; } private readonly _onInput = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index 8bf6a5bfa5b..d2dc8ec3c59 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -35,7 +35,7 @@ export abstract class TerminalService implements ITerminalService { protected _isShuttingDown: boolean; protected _terminalFocusContextKey: IContextKey; protected _findWidgetVisible: IContextKey; - protected _terminalContainer: HTMLElement; + protected _terminalContainer: HTMLElement | undefined; protected _terminalTabs: ITerminalTab[] = []; protected _backgroundedTerminalInstances: ITerminalInstance[] = []; protected get _terminalInstances(): ITerminalInstance[] { @@ -123,13 +123,9 @@ export abstract class TerminalService implements ITerminalService { protected abstract _showBackgroundTerminal(instance: ITerminalInstance): void; public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; - public abstract createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; + public abstract createInstance(container: HTMLElement, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance; public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; - public createTerminalRenderer(name: string): ITerminalInstance { - return this.createTerminal({ name, isRendererOnly: true }); - } - public getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance { const activeInstance = this.getActiveInstance(); return activeInstance ? activeInstance : this.createTerminal(undefined, wasNewTerminalAction); @@ -483,22 +479,28 @@ export abstract class TerminalService implements ITerminalService { return terminalIndex; } - public setWorkspaceShellAllowed(isAllowed: boolean): void { - this.configHelper.setWorkspaceShellAllowed(isAllowed); + public async manageWorkspaceShellPermissions(): Promise { + const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.terminal.allowWorkspaceShell', "Allow Workspace Shell Configuration") }; + const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.terminal.disallowWorkspaceShell', "Disallow Workspace Shell Configuration") }; + const value = await this._quickInputService.pick([allowItem, disallowItem], { canPickMany: false }); + if (!value) { + return; + } + this.configHelper.setWorkspaceShellAllowed(value === allowItem); } - protected _showTerminalCloseConfirmation(): Promise { - let message; + protected async _showTerminalCloseConfirmation(): Promise { + let message: string; if (this.terminalInstances.length === 1) { message = nls.localize('terminalService.terminalCloseConfirmationSingular', "There is an active terminal session, do you want to kill it?"); } else { message = nls.localize('terminalService.terminalCloseConfirmationPlural', "There are {0} active terminal sessions, do you want to kill them?", this.terminalInstances.length); } - - return this._dialogService.confirm({ + const res = await this._dialogService.confirm({ message, type: 'warning', - }).then(res => !res.confirmed); + }); + return !res.confirmed; } protected _showNotEnoughSpaceToast(): void { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index e9b13c0d68f..a7c3813e987 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -20,6 +20,7 @@ import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/term import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ILogService } from 'vs/platform/log/common/log'; let Terminal: typeof XTermTerminal; let WebLinksAddon: typeof XTermWebLinksAddon; @@ -34,7 +35,8 @@ export class TerminalInstanceService implements ITerminalInstanceService { @IStorageService private readonly _storageService: IStorageService, @IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, - @IHistoryService private readonly _historyService: IHistoryService + @IHistoryService private readonly _historyService: IHistoryService, + @ILogService private readonly _logService: ILogService ) { } @@ -71,7 +73,7 @@ export class TerminalInstanceService implements ITerminalInstanceService { return this._storageService.getBoolean(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, StorageScope.WORKSPACE, false); } - public getDefaultShellAndArgs(platformOverride: Platform = platform): Promise<{ shell: string, args: string | string[] }> { + public getDefaultShellAndArgs(useAutomationShell: boolean, platformOverride: Platform = platform): Promise<{ shell: string, args: string | string[] }> { const isWorkspaceShellAllowed = this._isWorkspaceShellAllowed(); const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); let lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : undefined; @@ -84,13 +86,17 @@ export class TerminalInstanceService implements ITerminalInstanceService { process.env.windir, lastActiveWorkspace, this._configurationResolverService, + this._logService, + useAutomationShell, platformOverride ); const args = getDefaultShellArgs( (key) => this._configurationService.inspect(key), isWorkspaceShellAllowed, + useAutomationShell, lastActiveWorkspace, this._configurationResolverService, + this._logService, platformOverride ); return Promise.resolve({ shell, args }); @@ -99,4 +105,4 @@ export class TerminalInstanceService implements ITerminalInstanceService { public getMainProcessParentEnv(): Promise { return getMainProcessParentEnv(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/node/terminal.ts b/src/vs/workbench/contrib/terminal/node/terminal.ts index f686213d1cb..e7fc03e6bcd 100644 --- a/src/vs/workbench/contrib/terminal/node/terminal.ts +++ b/src/vs/workbench/contrib/terminal/node/terminal.ts @@ -120,10 +120,11 @@ async function detectAvailableWindowsShells(): Promise { `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`, ], - Cygwin: [ - `${process.env['HOMEDRIVE']}\\cygwin64\\bin\\bash.exe`, - `${process.env['HOMEDRIVE']}\\cygwin\\bin\\bash.exe` - ] + // See #75945 + // Cygwin: [ + // `${process.env['HOMEDRIVE']}\\cygwin64\\bin\\bash.exe`, + // `${process.env['HOMEDRIVE']}\\cygwin\\bin\\bash.exe` + // ] }; const promises: PromiseLike[] = []; Object.keys(expectedLocations).forEach(key => promises.push(validateShellPaths(key, expectedLocations[key]))); @@ -170,4 +171,4 @@ async function getShellPathFromRegistry(shellName: string): Promise { } catch (error) { return ''; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index 8b61a562d8d..86a63a0b843 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -10,7 +10,7 @@ import * as pty from 'node-pty'; import * as fs from 'fs'; import { Event, Emitter } from 'vs/base/common/event'; import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IShellLaunchConfig, ITerminalChildProcess, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal'; import { exec } from 'child_process'; import { ILogService } from 'vs/platform/log/common/log'; @@ -18,23 +18,23 @@ import { stat } from 'vs/base/node/pfs'; import { findExecutable } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; import { URI } from 'vs/base/common/uri'; -export class TerminalProcess implements ITerminalChildProcess, IDisposable { - private _exitCode: number; +export class TerminalProcess extends Disposable implements ITerminalChildProcess { + private _exitCode: number | undefined; private _closeTimeout: any; private _ptyProcess: pty.IPty | undefined; private _currentTitle: string = ''; - private _processStartupComplete: Promise; + private _processStartupComplete: Promise | undefined; private _isDisposed: boolean = false; private _titleInterval: NodeJS.Timer | null = null; private _initialCwd: string; - private readonly _onProcessData = new Emitter(); + private readonly _onProcessData = this._register(new Emitter()); public get onProcessData(): Event { return this._onProcessData.event; } - private readonly _onProcessExit = new Emitter(); + private readonly _onProcessExit = this._register(new Emitter()); public get onProcessExit(): Event { return this._onProcessExit.event; } - private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>(); + private readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>()); public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; } - private readonly _onProcessTitleChanged = new Emitter(); + private readonly _onProcessTitleChanged = this._register(new Emitter()); public get onProcessTitleChanged(): Event { return this._onProcessTitleChanged.event; } constructor( @@ -46,6 +46,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { windowsEnableConpty: boolean, @ILogService private readonly _logService: ILogService ) { + super(); let shellName: string; if (os.platform() === 'win32') { shellName = path.basename(shellLaunchConfig.executable || ''); @@ -65,7 +66,8 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { cols, rows, experimentalUseConpty: useConpty, - conptyInheritCursor: true + // This option will force conpty to not redraw the whole viewport on launch + conptyInheritCursor: useConpty && !!shellLaunchConfig.initialText }; const cwdVerification = stat(cwd).then(async stat => { @@ -87,7 +89,9 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { }, async (err) => { if (err && err.code === 'ENOENT') { let cwd = shellLaunchConfig.cwd instanceof URI ? shellLaunchConfig.cwd.path : shellLaunchConfig.cwd!; - const executable = await findExecutable(shellLaunchConfig.executable!, cwd); + // Try to get path + const envPaths: string[] | undefined = (shellLaunchConfig.env && shellLaunchConfig.env.PATH) ? shellLaunchConfig.env.PATH.split(path.delimiter) : undefined; + const executable = await findExecutable(shellLaunchConfig.executable!, cwd, envPaths); if (!executable) { return Promise.reject(SHELL_PATH_INVALID_EXIT_CODE); } @@ -143,6 +147,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { this._onProcessExit.dispose(); this._onProcessReady.dispose(); this._onProcessTitleChanged.dispose(); + super.dispose(); } private _setupTitlePolling(ptyProcess: pty.IPty) { @@ -172,7 +177,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { private _kill(): void { // Wait to kill to process until the start up code has run. This prevents us from firing a process exit before a // process start. - this._processStartupComplete.then(() => { + this._processStartupComplete!.then(() => { if (this._isDisposed) { return; } @@ -186,7 +191,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { } catch (ex) { // Swallow, the pty has already been killed } - this._onProcessExit.fire(this._exitCode); + this._onProcessExit.fire(this._exitCode || 0); this.dispose(); }); } diff --git a/src/vs/workbench/contrib/terminal/node/terminalRemote.ts b/src/vs/workbench/contrib/terminal/node/terminalRemote.ts index e264cdfdd5d..3ebea6304cf 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalRemote.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalRemote.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { TERMINAL_ACTION_CATEGORY, ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TERMINAL_ACTION_CATEGORY, ITerminalService, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal'; import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; import { Action } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; @@ -39,7 +39,7 @@ export class CreateNewLocalTerminalAction extends Action { const disposable = instance.onTitleChanged(() => { if (instance.title && instance.title.trim().length > 0) { disposable.dispose(); - instance.setTitle(`${instance.title} (Local)`, false); + instance.setTitle(`${instance.title} (Local)`, TitleEventSource.Api); } }); diff --git a/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts index ced2d3ee265..0b20c735a89 100644 --- a/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts +++ b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts @@ -5,9 +5,10 @@ import * as platform from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; -import { ITerminalInstance, IWindowsShellHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalInstance, IWindowsShellHelper, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal'; import { Terminal as XTermTerminal } from 'xterm'; -import WindowsProcessTreeType = require('windows-process-tree'); +import * as WindowsProcessTreeType from 'windows-process-tree'; +import { Disposable } from 'vs/base/common/lifecycle'; const SHELL_EXECUTABLES = [ 'cmd.exe', @@ -24,17 +25,19 @@ const SHELL_EXECUTABLES = [ let windowsProcessTree: typeof WindowsProcessTreeType; -export class WindowsShellHelper implements IWindowsShellHelper { - private _onCheckShell: Emitter | undefined>; +export class WindowsShellHelper extends Disposable implements IWindowsShellHelper { + private _onCheckShell: Emitter | undefined> = this._register(new Emitter | undefined>()); private _isDisposed: boolean; - private _currentRequest: Promise | null; - private _newLineFeed: boolean; + private _currentRequest: Promise | undefined; + private _newLineFeed: boolean = false; public constructor( private _rootProcessId: number, private _terminalInstance: ITerminalInstance, private _xterm: XTermTerminal ) { + super(); + if (!platform.isWindows) { throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`); } @@ -47,7 +50,6 @@ export class WindowsShellHelper implements IWindowsShellHelper { } windowsProcessTree = mod; - this._onCheckShell = new Emitter>(); // The debounce is necessary to prevent multiple processes from spawning when // the enter key or output is spammed Event.debounce(this._onCheckShell.event, (l, e) => e, 150, true)(() => { @@ -65,6 +67,7 @@ export class WindowsShellHelper implements IWindowsShellHelper { this._xterm.onCursorMove(() => { if (this._newLineFeed) { this._onCheckShell.fire(undefined); + this._newLineFeed = false; } }); @@ -77,7 +80,7 @@ export class WindowsShellHelper implements IWindowsShellHelper { if (platform.isWindows && this._terminalInstance.isTitleSetByProcess) { this.getShellName().then(title => { if (!this._isDisposed) { - this._terminalInstance.setTitle(title, true); + this._terminalInstance.setTitle(title, TitleEventSource.Process); } }); } @@ -111,6 +114,7 @@ export class WindowsShellHelper implements IWindowsShellHelper { public dispose(): void { this._isDisposed = true; + super.dispose(); } /** @@ -127,7 +131,7 @@ export class WindowsShellHelper implements IWindowsShellHelper { this._currentRequest = new Promise(resolve => { windowsProcessTree.getProcessTree(this._rootProcessId, (tree) => { const name = this.traverseTree(tree); - this._currentRequest = null; + this._currentRequest = undefined; resolve(name); }); }); diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts index 6216173e3a2..2c74cd54682 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Terminal, TerminalCore } from 'xterm'; -import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker'; +import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon'; import { isWindows } from 'vs/base/common/platform'; interface TestTerminalCore extends TerminalCore { @@ -28,7 +28,7 @@ const COLS = 10; suite('Workbench - TerminalCommandTracker', () => { let xterm: TestTerminal; - let commandTracker: TerminalCommandTracker; + let commandTracker: CommandTrackerAddon; setup(() => { xterm = (new Terminal({ @@ -39,7 +39,8 @@ suite('Workbench - TerminalCommandTracker', () => { for (let i = 0; i < ROWS - 1; i++) { syncWrite(xterm, `${i}\n`); } - commandTracker = new TerminalCommandTracker(xterm); + commandTracker = new CommandTrackerAddon(); + xterm.loadAddon(commandTracker); }); suite('Command tracking', () => { @@ -151,4 +152,4 @@ suite('Workbench - TerminalCommandTracker', () => { assert.equal(xterm.getSelection(), isWindows ? '0\r\n1\r\n2' : '0\n1\n2'); }); }); -}); \ No newline at end of file +}); 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 96daea4ad44..1aeea762f4f 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 @@ -65,7 +65,7 @@ interface LinkFormatInfo { suite('Workbench - TerminalLinkHandler', () => { suite('localLinkRegex', () => { test('Windows', () => { - const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: '' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -121,7 +121,8 @@ suite('Workbench - TerminalLinkHandler', () => { { urlFormat: '{0}[{1},{2}]', line: '5', column: '3' }, { urlFormat: '{0} [{1},{2}]', line: '5', column: '3' }, { urlFormat: '{0}[{1}, {2}]', line: '5', column: '3' }, - { urlFormat: '{0} [{1}, {2}]', line: '5', column: '3' } + { urlFormat: '{0} [{1}, {2}]', line: '5', column: '3' }, + { urlFormat: '"{0}",{1}', line: '5' } ]; linkUrls.forEach(linkUrl => { @@ -140,7 +141,7 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('Linux', () => { - const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -185,7 +186,8 @@ suite('Workbench - TerminalLinkHandler', () => { { urlFormat: '{0}[{1}]', line: '5' }, { urlFormat: '{0} [{1}]', line: '5' }, { urlFormat: '{0}[{1},{2}]', line: '5', column: '3' }, - { urlFormat: '{0} [{1},{2}]', line: '5', column: '3' } + { urlFormat: '{0} [{1},{2}]', line: '5', column: '3' }, + { urlFormat: '"{0}",{1}', line: '5' } ]; linkUrls.forEach(linkUrl => { @@ -207,7 +209,7 @@ suite('Workbench - TerminalLinkHandler', () => { suite('preprocessPath', () => { test('Windows', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\Me' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -220,7 +222,7 @@ suite('Workbench - TerminalLinkHandler', () => { assert.equal(linkHandler.preprocessPath('C:\\absolute\\path\\file5'), 'C:\\absolute\\path\\file5'); }); test('Windows - spaces', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\M e' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -234,7 +236,7 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('Linux', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -247,7 +249,7 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('No Workspace', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); @@ -261,7 +263,7 @@ suite('Workbench - TerminalLinkHandler', () => { test('gitDiffLinkRegex', () => { // The platform is irrelevant because the links generated by Git are the same format regardless of platform - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); diff --git a/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts b/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts index 2368badf9b8..1f8ecd44171 100644 --- a/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts +++ b/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts @@ -128,4 +128,48 @@ suite('Workbench - TerminalEnvironment', () => { assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, '/userHome/', undefined, undefined, Uri.file('/bar'), '/foo'), '/bar'); }); }); + + suite('getDefaultShell', () => { + test('should change Sysnative to System32 in non-WoW64 systems', () => { + const shell = terminalEnvironment.getDefaultShell(key => { + return ({ + 'terminal.integrated.shell.windows': { user: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, default: undefined } + } as any)[key]; + }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows); + assert.equal(shell, 'C:\\Windows\\System32\\cmd.exe'); + }); + + test('should not change Sysnative to System32 in WoW64 systems', () => { + const shell = terminalEnvironment.getDefaultShell(key => { + return ({ + 'terminal.integrated.shell.windows': { user: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, default: undefined } + } as any)[key]; + }, false, 'DEFAULT', true, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows); + assert.equal(shell, 'C:\\Windows\\Sysnative\\cmd.exe'); + }); + + test('should use automationShell when specified', () => { + const shell1 = terminalEnvironment.getDefaultShell(key => { + return ({ + 'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined }, + 'terminal.integrated.automationShell.windows': { user: undefined, value: undefined, default: undefined } + } as any)[key]; + }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows); + assert.equal(shell1, 'shell', 'automationShell was false'); + const shell2 = terminalEnvironment.getDefaultShell(key => { + return ({ + 'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined }, + 'terminal.integrated.automationShell.windows': { user: undefined, value: undefined, default: undefined } + } as any)[key]; + }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows); + assert.equal(shell2, 'shell', 'automationShell was true'); + const shell3 = terminalEnvironment.getDefaultShell(key => { + return ({ + 'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined }, + 'terminal.integrated.automationShell.windows': { user: 'automationShell', value: undefined, default: undefined } + } as any)[key]; + }, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows); + assert.equal(shell3, 'automationShell', 'automationShell was true and specified in settings'); + }); + }); }); diff --git a/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts index 65d12c80877..b91e684cfda 100644 --- a/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts @@ -28,24 +28,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { generateUuid } from 'vs/base/common/uuid'; -function renderBody( - body: string, - css: string -): string { - const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-resource://'); - return ` - - - - - - - - - ${body} - `; -} - export class ReleaseNotesManager { private readonly _releaseNotesCache = new Map>(); @@ -188,8 +170,18 @@ export class ReleaseNotesManager { const content = await this.renderContent(text); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; - const body = renderBody(content, css); - return body; + const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-resource://'); + return ` + + + + + + + + + ${content} + `; } private async renderContent(text: string): Promise { diff --git a/src/vs/workbench/contrib/update/electron-browser/update.ts b/src/vs/workbench/contrib/update/electron-browser/update.ts index ae89e3f4719..64d43209612 100644 --- a/src/vs/workbench/contrib/update/electron-browser/update.ts +++ b/src/vs/workbench/contrib/update/electron-browser/update.ts @@ -19,7 +19,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IUpdateService, State as UpdateState, StateType, IUpdate } from 'vs/platform/update/common/update'; import * as semver from 'semver-umd'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { INotificationService, INotificationHandle, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ReleaseNotesManager } from './releaseNotesEditor'; @@ -162,32 +162,8 @@ export class ProductContribution implements IWorkbenchContribution { } } -class NeverShowAgain { - - private readonly key: string; - - readonly action = new Action(`neverShowAgain:${this.key}`, nls.localize('neveragain', "Don't Show Again"), undefined, true, (notification: INotificationHandle) => { - - // Hide notification - notification.close(); - - this.storageService.store(this.key, true, StorageScope.GLOBAL); - - return Promise.resolve(true); - }); - - constructor(key: string, @IStorageService private readonly storageService: IStorageService) { - this.key = `neverShowAgain:${key}`; - } - - shouldShow(): boolean { - return !this.storageService.getBoolean(this.key, StorageScope.GLOBAL, false); - } -} - export class Win3264BitContribution implements IWorkbenchContribution { - private static readonly KEY = 'update/win32-64bits'; private static readonly URL = 'https://code.visualstudio.com/updates/v1_15#_windows-64-bit'; private static readonly INSIDER_URL = 'https://github.com/Microsoft/vscode-docs/blob/vnext/release-notes/v1_15.md#windows-64-bit'; @@ -200,28 +176,18 @@ export class Win3264BitContribution implements IWorkbenchContribution { return; } - const neverShowAgain = new NeverShowAgain(Win3264BitContribution.KEY, storageService); - - if (!neverShowAgain.shouldShow()) { - return; - } - const url = product.quality === 'insider' ? Win3264BitContribution.INSIDER_URL : Win3264BitContribution.URL; - const handle = notificationService.prompt( + notificationService.prompt( severity.Info, nls.localize('64bitisavailable', "{0} for 64-bit Windows is now available! Click [here]({1}) to learn more.", product.nameShort, url), - [{ - label: nls.localize('neveragain', "Don't Show Again"), - isSecondary: true, - run: () => { - neverShowAgain.action.run(handle); - neverShowAgain.action.dispose(); - } - }], - { sticky: true } + [], + { + sticky: true, + neverShowAgain: { id: 'neverShowAgain:update/win32-64bits', isSecondary: true } + } ); } } @@ -396,23 +362,13 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu } // windows fast updates (target === system) - const neverShowAgain = new NeverShowAgain('update/win32-fast-updates', this.storageService); - - if (!neverShowAgain.shouldShow()) { - return; - } - - const handle = this.notificationService.prompt( + this.notificationService.prompt( severity.Info, nls.localize('updateInstalling', "{0} {1} is being installed in the background; we'll let you know when it's done.", product.nameLong, update.productVersion), - [{ - label: nls.localize('neveragain', "Don't Show Again"), - isSecondary: true, - run: () => { - neverShowAgain.action.run(handle); - neverShowAgain.action.dispose(); - } - }] + [], + { + neverShowAgain: { id: 'neverShowAgain:update/win32-fast-updates', isSecondary: true } + } ); } @@ -484,7 +440,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu group: '5_update', command: { id: 'update.checking', - title: nls.localize('checkingForUpdates', "Checking For Updates..."), + title: nls.localize('checkingForUpdates', "Checking for Updates..."), precondition: FalseContext }, when: CONTEXT_UPDATE_STATE.isEqualTo(StateType.CheckingForUpdates) diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index cbed9bef6a3..475265542b1 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -4,13 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IURLService } from 'vs/platform/url/common/url'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; export class OpenUrlAction extends Action { @@ -34,5 +37,97 @@ export class OpenUrlAction extends Action { } } -Registry.as(ActionExtensions.WorkbenchActions) - .registerWorkbenchAction(new SyncActionDescriptor(OpenUrlAction, OpenUrlAction.ID, OpenUrlAction.LABEL), 'Open URL', localize('developer', "Developer")); \ No newline at end of file +Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction( + new SyncActionDescriptor(OpenUrlAction, OpenUrlAction.ID, OpenUrlAction.LABEL), + 'Open URL', + localize('developer', 'Developer') +); + +const VSCODE_DOMAIN = 'https://code.visualstudio.com'; + +const configureTrustedDomainsHandler = ( + quickInputService: IQuickInputService, + storageService: IStorageService, + domainToConfigure?: string +) => { + let trustedDomains: string[] = [VSCODE_DOMAIN]; + + try { + const trustedDomainsSrc = storageService.get('http.trustedDomains', StorageScope.GLOBAL); + if (trustedDomainsSrc) { + trustedDomains = JSON.parse(trustedDomainsSrc); + } + } catch (err) { } + + const domainQuickPickItems: IQuickPickItem[] = trustedDomains + .filter(d => d !== '*') + .map(d => { + return { + type: 'item', + label: d, + id: d, + picked: true, + }; + }); + + const specialQuickPickItems: IQuickPickItem[] = [ + { + type: 'item', + label: localize('openAllLinksWithoutPrompt', 'Open all links without prompt'), + id: '*', + picked: trustedDomains.indexOf('*') !== -1 + } + ]; + + let domainToConfigureItem: IQuickPickItem | undefined = undefined; + if (domainToConfigure && trustedDomains.indexOf(domainToConfigure) === -1) { + domainToConfigureItem = { + type: 'item', + label: domainToConfigure, + id: domainToConfigure, + picked: true, + description: localize('trustDomainAndOpenLink', 'Trust domain and open link') + }; + specialQuickPickItems.push(domainToConfigureItem); + } + + const quickPickItems: (IQuickPickItem | IQuickPickSeparator)[] = domainQuickPickItems.length === 0 + ? specialQuickPickItems + : [...specialQuickPickItems, { type: 'separator' }, ...domainQuickPickItems]; + + return quickInputService.pick(quickPickItems, { + canPickMany: true, + activeItem: domainToConfigureItem + }).then(result => { + if (result) { + const pickedDomains = result.map(r => r.id); + storageService.store('http.trustedDomains', JSON.stringify(pickedDomains), StorageScope.GLOBAL); + + return pickedDomains; + } + + return []; + }); +}; + +const configureTrustedDomainCommand = { + id: 'workbench.action.configureTrustedDomains', + description: { + description: localize('configureTrustedDomains', 'Configure Trusted Domains'), + args: [{ name: 'domainToConfigure', schema: { type: 'string' } }] + }, + handler: (accessor: ServicesAccessor, domainToConfigure?: string) => { + const quickInputService = accessor.get(IQuickInputService); + const storageService = accessor.get(IStorageService); + + return configureTrustedDomainsHandler(quickInputService, storageService, domainToConfigure); + } +}; + +CommandsRegistry.registerCommand(configureTrustedDomainCommand); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: configureTrustedDomainCommand.id, + title: configureTrustedDomainCommand.description.description + } +}); diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index 54c9b8620f5..5a1ee63bf03 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./watermark'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { isMacintosh, OS } from 'vs/base/common/platform'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -37,51 +37,17 @@ interface WatermarkEntry { mac?: boolean; } -const showCommands: WatermarkEntry = { - text: nls.localize('watermark.showCommands', "Show All Commands"), - id: ShowAllCommandsAction.ID -}; -const quickOpen: WatermarkEntry = { - text: nls.localize('watermark.quickOpen', "Go to File"), - id: QUICKOPEN_ACTION_ID -}; -const openFileNonMacOnly: WatermarkEntry = { - text: nls.localize('watermark.openFile', "Open File"), - id: OpenFileAction.ID, - mac: false -}; -const openFolderNonMacOnly: WatermarkEntry = { - text: nls.localize('watermark.openFolder', "Open Folder"), - id: OpenFolderAction.ID, - mac: false -}; -const openFileOrFolderMacOnly: WatermarkEntry = { - text: nls.localize('watermark.openFileFolder', "Open File or Folder"), - id: OpenFileFolderAction.ID, - mac: true -}; -const openRecent: WatermarkEntry = { - text: nls.localize('watermark.openRecent', "Open Recent"), - id: 'workbench.action.openRecent' -}; -const newUntitledFile: WatermarkEntry = { - text: nls.localize('watermark.newUntitledFile', "New Untitled File"), - id: GlobalNewUntitledFileAction.ID -}; +const showCommands: WatermarkEntry = { text: nls.localize('watermark.showCommands', "Show All Commands"), id: ShowAllCommandsAction.ID }; +const quickOpen: WatermarkEntry = { text: nls.localize('watermark.quickOpen', "Go to File"), id: QUICKOPEN_ACTION_ID }; +const openFileNonMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFile', "Open File"), id: OpenFileAction.ID, mac: false }; +const openFolderNonMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFolder', "Open Folder"), id: OpenFolderAction.ID, mac: false }; +const openFileOrFolderMacOnly: WatermarkEntry = { text: nls.localize('watermark.openFileFolder', "Open File or Folder"), id: OpenFileFolderAction.ID, mac: true }; +const openRecent: WatermarkEntry = { text: nls.localize('watermark.openRecent', "Open Recent"), id: 'workbench.action.openRecent' }; +const newUntitledFile: WatermarkEntry = { text: nls.localize('watermark.newUntitledFile', "New Untitled File"), id: GlobalNewUntitledFileAction.ID }; const newUntitledFileMacOnly: WatermarkEntry = assign({ mac: true }, newUntitledFile); -const toggleTerminal: WatermarkEntry = { - text: nls.localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), - id: TERMINAL_COMMAND_ID.TOGGLE -}; - -const findInFiles: WatermarkEntry = { - text: nls.localize('watermark.findInFiles', "Find in Files"), - id: FindInFilesActionId -}; -const startDebugging: WatermarkEntry = { - text: nls.localize('watermark.startDebugging', "Start Debugging"), - id: StartAction.ID -}; +const toggleTerminal: WatermarkEntry = { text: nls.localize({ key: 'watermark.toggleTerminal', comment: ['toggle is a verb here'] }, "Toggle Terminal"), id: TERMINAL_COMMAND_ID.TOGGLE }; +const findInFiles: WatermarkEntry = { text: nls.localize('watermark.findInFiles', "Find in Files"), id: FindInFilesActionId }; +const startDebugging: WatermarkEntry = { text: nls.localize('watermark.startDebugging', "Start Debugging"), id: StartAction.ID }; const noFolderEntries = [ showCommands, @@ -103,13 +69,13 @@ const folderEntries = [ const WORKBENCH_TIPS_ENABLED_KEY = 'workbench.tips.enabled'; export class WatermarkContribution extends Disposable implements IWorkbenchContribution { - private watermark: HTMLElement; + private watermarkDisposable = this._register(new DisposableStore()); private enabled: boolean; private workbenchState: WorkbenchState; constructor( - @ILifecycleService lifecycleService: ILifecycleService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -117,13 +83,20 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService ) { super(); - this.workbenchState = contextService.getWorkbenchState(); - lifecycleService.onShutdown(this.dispose, this); + this.workbenchState = contextService.getWorkbenchState(); this.enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); + + this.registerListeners(); + if (this.enabled) { this.create(); } + } + + private registerListeners(): void { + this.lifecycleService.onShutdown(this.dispose, this); + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(WORKBENCH_TIPS_ENABLED_KEY)) { const enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); @@ -137,6 +110,7 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr } } })); + this._register(this.contextService.onDidChangeWorkbenchState(e => { const previousWorkbenchState = this.workbenchState; this.workbenchState = this.contextService.getWorkbenchState(); @@ -157,6 +131,7 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr const selected = folder ? folderEntries : noFolderEntries .filter(entry => !('mac' in entry) || entry.mac === isMacintosh) .filter(entry => !!CommandsRegistry.getCommand(entry.id)); + const update = () => { dom.clearNode(box); selected.map(entry => { @@ -169,10 +144,14 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr dd.innerHTML = keybinding.element.outerHTML; }); }; + update(); + dom.prepend(container.firstElementChild as HTMLElement, this.watermark); - this._register(this.keybindingService.onDidUpdateKeybindings(update)); - this._register(this.editorGroupsService.onDidLayout(dimension => this.handleEditorPartSize(container, dimension))); + + this.watermarkDisposable.add(this.keybindingService.onDidUpdateKeybindings(update)); + this.watermarkDisposable.add(this.editorGroupsService.onDidLayout(dimension => this.handleEditorPartSize(container, dimension))); + this.handleEditorPartSize(container, this.editorGroupsService.contentDimension); } @@ -187,9 +166,11 @@ export class WatermarkContribution extends Disposable implements IWorkbenchContr private destroy(): void { if (this.watermark) { this.watermark.remove(); + const container = this.layoutService.getContainer(Parts.EDITOR_PART); container.classList.remove('has-watermark'); - this.dispose(); + + this.watermarkDisposable.clear(); } } diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index c86d25f7724..94d8bfa1f36 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -6,7 +6,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; +import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { memoize } from 'vs/base/common/decorators'; diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index ac53ce590e2..e301f5ea90d 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -3,9 +3,6 @@ - - Virtual Document @@ -16,4 +13,4 @@ - \ No newline at end of file + diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 63585fc25c2..c766b83c114 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -188,6 +188,11 @@ return; } + // Prevent middle clicks opening a broken link in the browser + if (event.button == 1) { + event.preventDefault(); + } + let baseElement = event.view.document.getElementsByTagName('base')[0]; /** @type {any} */ let node = event.target; @@ -280,6 +285,17 @@ applyStyles(newDocument, newDocument.body); + // Check for CSP + const csp = newDocument.querySelector('meta[http-equiv="Content-Security-Policy"]'); + if (!csp) { + host.postMessage('no-csp-found'); + } else { + // Rewrite vscode-resource in csp + if (data.endpoint) { + csp.setAttribute('content', csp.getAttribute('content').replace(/vscode-resource:/g, data.endpoint)); + } + } + // set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off // and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden return '\n' + newDocument.documentElement.outerHTML; @@ -443,7 +459,7 @@ }); // Bubble out link clicks - newFrame.contentWindow.addEventListener('click', handleInnerClick); + newFrame.contentWindow.addEventListener('mousedown', handleInnerClick); if (host.onIframeLoaded) { host.onIframeLoaded(newFrame); diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index 40dac14b615..56017d4348f 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -15,7 +15,7 @@ import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } fro import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/common/webview'; +import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview'; import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetCommand } from '../browser/webviewCommands'; import { WebviewEditor } from '../browser/webviewEditor'; import { WebviewEditorInput } from '../browser/webviewEditorInput'; diff --git a/src/vs/workbench/contrib/webview/common/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts similarity index 97% rename from src/vs/workbench/contrib/webview/common/webview.ts rename to src/vs/workbench/contrib/webview/browser/webview.ts index d6a73ff5d61..ea7ecb5a720 100644 --- a/src/vs/workbench/contrib/webview/common/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -41,7 +41,6 @@ export interface IWebviewService { export const WebviewResourceScheme = 'vscode-resource'; export interface WebviewOptions { - readonly allowSvgs?: boolean; readonly extension?: { readonly location: URI; readonly id?: ExtensionIdentifier; @@ -53,7 +52,6 @@ export interface WebviewOptions { export interface WebviewContentOptions { readonly allowScripts?: boolean; - readonly svgWhiteList?: string[]; readonly localResourceRoots?: ReadonlyArray; readonly portMapping?: ReadonlyArray; readonly enableCommandUris?: boolean; diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index d01dc751d00..b8d7c6dd48e 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -15,7 +15,7 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions } from 'vs/workbench/common/editor'; import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/common/webview'; +import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index c91e93d915f..316c112a8bd 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { EditorInput, EditorModel, GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor'; -import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/common/webview'; +import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { UnownedDisposable as Unowned } from 'vs/base/common/lifecycle'; class WebviewIconsManager { @@ -39,11 +39,11 @@ class WebviewIconsManager { this._icons.forEach((value, key) => { const webviewSelector = `.show-file-icons .webview-${key}-name-file-icon::before`; if (URI.isUri(value)) { - cssRules.push(`${webviewSelector} { content: ""; background-image: url(${dom.asDomUri(value).toString()}); }`); + cssRules.push(`${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value)}; }`); } else { - cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: url(${dom.asDomUri(value.light).toString()}); }`); - cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: url(${dom.asDomUri(value.dark).toString()}); }`); + cssRules.push(`.vs ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.light)}; }`); + cssRules.push(`.vs-dark ${webviewSelector} { content: ""; background-image: ${dom.asCSSUrl(value.dark)}; }`); } }); this._styleElement.innerHTML = cssRules.join('\n'); diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts index b5f3ce40819..a8dbabc2c87 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts @@ -45,7 +45,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { const data: SerializedWebview = { viewType: input.viewType, title: input.getName(), - options: input.webview.options, + options: { ...input.webview.options, ...input.webview.contentOptions }, extensionLocation: input.extension ? input.extension.location : undefined, extensionId: input.extension && input.extension.id ? input.extension.id.value : undefined, state: input.webview.state, @@ -118,4 +118,4 @@ function reviveState(state: unknown | undefined): undefined | string { return (state as any).state; } return undefined; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts index e81418df66a..6fdda6cfb1c 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts @@ -10,11 +10,12 @@ import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { GroupIdentifier } from 'vs/workbench/common/editor'; -import { IWebviewService, WebviewOptions, WebviewContentOptions } from 'vs/workbench/contrib/webview/common/webview'; +import { IWebviewService, WebviewOptions, WebviewContentOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { RevivedWebviewEditorInput, WebviewEditorInput } from './webviewEditorInput'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export const IWebviewEditorService = createDecorator('webviewEditorService'); @@ -243,7 +244,6 @@ export class WebviewEditorService implements IWebviewEditorService { private createWebiew(id: string, extension: { location: URI; id: ExtensionIdentifier; } | undefined, options: WebviewInputOptions) { return this._webviewService.createWebviewEditorOverlay(id, { - allowSvgs: true, extension: extension, enableFindWidget: options.enableFindWidget, retainContextWhenHidden: options.retainContextWhenHidden @@ -264,3 +264,5 @@ export class WebviewEditorService implements IWebviewEditorService { return rootPaths; } } + +registerSingleton(IWebviewEditorService, WebviewEditorService, true); diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index aa4473313d4..7a0015be053 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -5,7 +5,7 @@ import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; +import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; @@ -202,7 +202,8 @@ export class IFrameWebview extends Disposable implements Webview { this._send('content', { contents: this.content.html, options: this.content.options, - state: this.content.state + state: this.content.state, + endpoint: this.endpoint, }); } @@ -213,7 +214,7 @@ export class IFrameWebview extends Disposable implements Webview { } } - initialScrollProgress: number; + initialScrollProgress: number = 0; private readonly _onDidFocus = this._register(new Emitter()); public readonly onDidFocus = this._onDidFocus.event; diff --git a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts index 1f337cbc8e3..0d8e6804bc0 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SimpleFindWidget } from 'vs/editor/contrib/find/simpleFindWidget'; +import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -54,4 +54,6 @@ export class WebviewFindWidget extends SimpleFindWidget { protected onFindInputFocusTrackerFocus() { } protected onFindInputFocusTrackerBlur() { } -} \ No newline at end of file + + protected findFirst() { } +} diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts index 1995a857f5e..eed1445c492 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -5,8 +5,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; +import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { DynamicWebviewEditorOverlay } from './dynamicWebviewEditorOverlay'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class WebviewService implements IWebviewService { _serviceBrand: any; @@ -31,3 +32,5 @@ export class WebviewService implements IWebviewService { return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions); } } + +registerSingleton(IWebviewService, WebviewService, true); diff --git a/src/vs/workbench/contrib/webview/common/portMapping.ts b/src/vs/workbench/contrib/webview/common/portMapping.ts index 964c0ac6da0..e47390dcf05 100644 --- a/src/vs/workbench/contrib/webview/common/portMapping.ts +++ b/src/vs/workbench/contrib/webview/common/portMapping.ts @@ -47,16 +47,16 @@ export class WebviewPortMappingManager extends Disposable { if (this.extensionLocation && this.extensionLocation.scheme === REMOTE_HOST_SCHEME) { const tunnel = await this.getOrCreateTunnel(mapping.extensionHostPort); if (tunnel) { - return uri.with({ + return encodeURI(uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}`, - }).toString(); + }).toString(true)); } } if (mapping.webviewPort !== mapping.extensionHostPort) { - return uri.with({ + return encodeURI(uri.with({ authority: `${requestLocalHostInfo.address}:${mapping.extensionHostPort}` - }).toString(); + }).toString(true)); } } } @@ -84,4 +84,4 @@ export class WebviewPortMappingManager extends Disposable { } return tunnel; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/webview/common/themeing.ts b/src/vs/workbench/contrib/webview/common/themeing.ts index 5f0649247a8..7c39b7a4bdd 100644 --- a/src/vs/workbench/contrib/webview/common/themeing.ts +++ b/src/vs/workbench/contrib/webview/common/themeing.ts @@ -31,7 +31,7 @@ export function getWebviewThemeData( }, {} as { [key: string]: string }); const styles = { - 'vscode-font-family': '-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", ans-serif', + 'vscode-font-family': '-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', 'vscode-font-weight': 'normal', 'vscode-font-size': '13px', 'vscode-editor-font-family': editorFontFamily, @@ -60,4 +60,4 @@ namespace ApiThemeClassName { return ApiThemeClassName.highContrast; } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts index 084163f0459..627d57bd3fd 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts @@ -13,7 +13,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; -import { IWebviewService, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/common/webview'; +import { IWebviewService, webviewDeveloperCategory } from 'vs/workbench/contrib/webview/browser/webview'; import * as webviewCommands from 'vs/workbench/contrib/webview/electron-browser/webviewCommands'; import { ElectronWebviewService } from 'vs/workbench/contrib/webview/electron-browser/webviewService'; @@ -89,4 +89,4 @@ function registerWebViewCommands(editorId: string): void { } } -registerWebViewCommands(WebviewEditor.ID); \ No newline at end of file +registerWebViewCommands(WebviewEditor.ID); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index a1d8ecda01a..066dbbecf5e 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -9,7 +9,7 @@ import { Command, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; -import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/common/webview'; +import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; export class OpenWebviewDeveloperToolsAction extends Action { static readonly ID = 'workbench.action.webview.openDeveloperTools'; @@ -101,4 +101,4 @@ function withActiveWebviewBasedWebview(accessor: ServicesAccessor, f: (webview: } }); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 3c030feb235..bdd1c7e3956 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -9,17 +9,18 @@ import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; -import { endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping'; import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing'; -import { Webview, WebviewContentOptions, WebviewOptions, WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/webview'; +import { Webview, WebviewContentOptions, WebviewOptions, WebviewResourceScheme } from 'vs/workbench/contrib/webview/browser/webview'; import { registerFileProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols'; import { areWebviewInputOptionsEqual } from '../browser/webviewEditorService'; import { WebviewFindWidget } from '../browser/webviewFindWidget'; @@ -135,51 +136,6 @@ class WebviewPortMappingProvider extends Disposable { } } -class SvgBlocker extends Disposable { - - private readonly _onDidBlockSvg = this._register(new Emitter()); - public readonly onDidBlockSvg = this._onDidBlockSvg.event; - - constructor( - session: WebviewSession, - private readonly _options: WebviewContentOptions, - ) { - super(); - - session.onBeforeRequest(async (details) => { - if (details.url.indexOf('.svg') > 0) { - const uri = URI.parse(details.url); - if (uri && !uri.scheme.match(/file/i) && endsWith(uri.path, '.svg') && !this.isAllowedSvg(uri)) { - this._onDidBlockSvg.fire(); - return { cancel: true }; - } - } - - return undefined; - }); - - session.onHeadersReceived((details) => { - const headers: any = details.responseHeaders; - const contentType: string[] = headers['content-type'] || headers['Content-Type']; - if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) { - const uri = URI.parse(details.url); - if (uri && !this.isAllowedSvg(uri)) { - this._onDidBlockSvg.fire(); - return { cancel: true }; - } - } - return undefined; - }); - } - - private isAllowedSvg(uri: URI): boolean { - if (this._options.svgWhiteList) { - return this._options.svgWhiteList.indexOf(uri.authority.toLowerCase()) >= 0; - } - return false; - } -} - class WebviewKeyboardHandler extends Disposable { private _ignoreMenuShortcut = false; @@ -284,6 +240,8 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { @IFileService fileService: IFileService, @ITunnelService tunnelService: ITunnelService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IEnvironmentService private readonly _environementService: IEnvironmentService, ) { super(); this.content = { @@ -331,11 +289,6 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { tunnelService, )); - if (!this._options.allowSvgs) { - const svgBlocker = this._register(new SvgBlocker(session, this.content.options)); - svgBlocker.onDidBlockSvg(() => this.onDidBlockSvg()); - } - this._register(new WebviewKeyboardHandler(this._webview)); this._register(addDisposableListener(this._webview, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) { @@ -374,12 +327,19 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { { const rawEvent = event.args[0]; const bounds = this._webview.getBoundingClientRect(); - window.dispatchEvent(new MouseEvent(rawEvent.type, { - ...rawEvent, - clientX: rawEvent.clientX + bounds.left, - clientY: rawEvent.clientY + bounds.top, - })); - return; + try { + window.dispatchEvent(new MouseEvent(rawEvent.type, { + ...rawEvent, + clientX: rawEvent.clientX + bounds.left, + clientY: rawEvent.clientY + bounds.top, + })); + return; + } + catch (TypeError) { + // CustomEvent was treated as MouseEvent so don't do anything - https://github.com/microsoft/vscode/issues/78915 + return; + } + } case 'did-set-content': @@ -412,6 +372,10 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { case 'did-blur': this.handleFocusChange(false); return; + + case 'no-csp-found': + this.handleNoCspFound(); + return; } })); this._register(addDisposableListener(this._webview, 'devtools-opened', () => { @@ -532,7 +496,11 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { if (!this._webview) { return; } - this._webview.focus(); + try { + this._webview.focus(); + } catch { + // noop + } this._send('focus'); // Handle focus change programmatically (do not rely on event from ) @@ -546,14 +514,34 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { } } - public sendMessage(data: any): void { - this._send('message', data); + private _hasAlertedAboutMissingCsp = false; + + private handleNoCspFound(): void { + if (this._hasAlertedAboutMissingCsp) { + return; + } + this._hasAlertedAboutMissingCsp = true; + + if (this._options.extension && this._options.extension.id) { + if (this._environementService.isExtensionDevelopment) { + console.warn(`${this._options.extension.id.value} created a webview without a content security policy: https://aka.ms/vscode-webview-missing-csp`); + } + + type TelemetryClassification = { + extension?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' } + }; + type TelemetryData = { + extension?: string, + }; + + this._telemetryService.publicLog2('webviewMissingCsp', { + extension: this._options.extension.id.value + }); + } } - private onDidBlockSvg() { - this.sendMessage({ - name: 'vscode-did-block-svg' - }); + public sendMessage(data: any): void { + this._send('message', data); } private style(theme: ITheme): void { @@ -566,7 +554,7 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview { } public layout(): void { - if (!this._webview) { + if (!this._webview || this._webview.style.width === '0px') { return; } const contents = this._webview.getWebContents(); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts index ae8eb72d06f..dedb445ab5c 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewService.ts @@ -7,7 +7,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DynamicWebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay'; import { IFrameWebview } from 'vs/workbench/contrib/webview/browser/webviewElement'; -import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; +import { IWebviewService, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; export class ElectronWebviewService implements IWebviewService { @@ -38,4 +38,4 @@ export class ElectronWebviewService implements IWebviewService { ): WebviewEditorOverlay { return this._instantiationService.createInstance(DynamicWebviewEditorOverlay, id, options, contentOptions); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.ts index 2c0d6778d39..200de4fa5c9 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.ts @@ -9,6 +9,8 @@ import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import * as platform from 'vs/base/common/platform'; import product from 'vs/platform/product/node/product'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; export class GettingStarted implements IWorkbenchContribution { @@ -20,7 +22,8 @@ export class GettingStarted implements IWorkbenchContribution { constructor( @IStorageService private readonly storageService: IStorageService, @IEnvironmentService environmentService: IEnvironmentService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IOpenerService private readonly openerService: IOpenerService ) { this.appName = product.nameLong; @@ -50,7 +53,7 @@ export class GettingStarted implements IWorkbenchContribution { if (platform.isLinux && platform.isRootUser()) { return; } - window.open(url); + this.openerService.open(URI.parse(url)); } private handleWelcome(): void { diff --git a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts index a56cff41c02..2cfebd34775 100644 --- a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts +++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts @@ -9,7 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ShowAllCommandsAction } from 'vs/workbench/contrib/quickopen/browser/commandsHandler'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; @@ -168,10 +168,8 @@ class WelcomeOverlay extends Disposable { } private create(): void { - const container = this.layoutService.getContainer(Parts.EDITOR_PART)!; - const offset = this.layoutService.getTitleBarOffset(); - this._overlay = dom.append(container.parentElement!, $('.welcomeOverlay')); + this._overlay = dom.append(this.layoutService.getWorkbenchElement(), $('.welcomeOverlay')); this._overlay.style.top = `${offset}px`; this._overlay.style.height = `calc(100% - ${offset}px)`; this._overlay.style.display = 'none'; diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index ab0d4193c40..f549552738b 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as strings from 'vs/base/common/strings'; import { ICommandService } from 'vs/platform/commands/common/commands'; import * as arrays from 'vs/base/common/arrays'; -import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput'; +import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts index 7a95ffda9e4..8493b87f2cd 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts @@ -8,7 +8,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { Action } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; -import { WalkThroughInput, WalkThroughInputOptions } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput'; +import { WalkThroughInput, WalkThroughInputOptions } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput'; import { Schemas } from 'vs/base/common/network'; import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md index 43e8bc83a89..ffd95c89718 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md @@ -77,6 +77,9 @@ new Book("The Martian", "Andy Weir"); /** * Represents a book. + * + * @param {string} title Title of the book + * @param {string} author Who wrote the book */ function Book(title, author) { this.title = title; @@ -84,7 +87,7 @@ function Book(title, author) { } ``` -> **JSDoc Tip:** The example above also showcased another way to get IntelliSense hints by using `JSDoc` comments. You can try this out by invoking the `Book` function and seeing the enhanced context in the IntelliSense menu for the function as well as parameters. +> **JSDoc Tip:** VS Code's IntelliSense uses JSDoc comments to provide richer suggestions. The types and documentation from JSDoc comments show up when you hover over a reference to `Book` or in IntelliSense when you create a new instance of `Book`. ### Refactoring via Extraction diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts index 9eed29a774f..8a91091c6b7 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput'; +import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput'; import { WalkThroughPart } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart'; import { WalkThroughArrowUp, WalkThroughArrowDown, WalkThroughPageUp, WalkThroughPageDown } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions'; import { WalkThroughContentProvider, WalkThroughSnippetContentProvider } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider'; @@ -55,4 +55,4 @@ MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { title: localize({ key: 'miInteractivePlayground', comment: ['&& denotes a mnemonic'] }, "I&&nteractive Playground") }, order: 2 -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts similarity index 100% rename from src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts rename to src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index afd74dc859b..71faece75fd 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -12,7 +12,7 @@ import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/com import { EditorOptions, IEditorMemento } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput'; +import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import * as marked from 'vs/base/common/marked/marked'; import { IModelService } from 'vs/editor/common/services/modelService'; diff --git a/src/vs/workbench/electron-browser/actions/helpActions.ts b/src/vs/workbench/electron-browser/actions/helpActions.ts index 3c5d242f92f..ce5e237ef08 100644 --- a/src/vs/workbench/electron-browser/actions/helpActions.ts +++ b/src/vs/workbench/electron-browser/actions/helpActions.ts @@ -8,6 +8,8 @@ import * as nls from 'vs/nls'; import product from 'vs/platform/product/node/product'; import { isMacintosh, isLinux, language } from 'vs/base/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; export class KeybindingsReferenceAction extends Action { @@ -19,13 +21,14 @@ export class KeybindingsReferenceAction extends Action { constructor( id: string, - label: string + label: string, + @IOpenerService private readonly openerService: IOpenerService ) { super(id, label); } run(): Promise { - window.open(KeybindingsReferenceAction.URL); + this.openerService.open(URI.parse(KeybindingsReferenceAction.URL)); return Promise.resolve(); } @@ -41,13 +44,14 @@ export class OpenDocumentationUrlAction extends Action { constructor( id: string, - label: string + label: string, + @IOpenerService private readonly openerService: IOpenerService ) { super(id, label); } run(): Promise { - window.open(OpenDocumentationUrlAction.URL); + this.openerService.open(URI.parse(OpenDocumentationUrlAction.URL)); return Promise.resolve(); } @@ -63,13 +67,14 @@ export class OpenIntroductoryVideosUrlAction extends Action { constructor( id: string, - label: string + label: string, + @IOpenerService private readonly openerService: IOpenerService ) { super(id, label); } run(): Promise { - window.open(OpenIntroductoryVideosUrlAction.URL); + this.openerService.open(URI.parse(OpenIntroductoryVideosUrlAction.URL)); return Promise.resolve(); } @@ -85,13 +90,14 @@ export class OpenTipsAndTricksUrlAction extends Action { constructor( id: string, - label: string + label: string, + @IOpenerService private readonly openerService: IOpenerService ) { super(id, label); } run(): Promise { - window.open(OpenTipsAndTricksUrlAction.URL); + this.openerService.open(URI.parse(OpenTipsAndTricksUrlAction.URL)); return Promise.resolve(); } } @@ -107,6 +113,7 @@ export class OpenNewsletterSignupUrlAction extends Action { constructor( id: string, label: string, + @IOpenerService private readonly openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService ) { super(id, label); @@ -116,7 +123,7 @@ export class OpenNewsletterSignupUrlAction extends Action { async run(): Promise { const info = await this.telemetryService.getTelemetryInfo(); - window.open(`${OpenNewsletterSignupUrlAction.URL}?machineId=${encodeURIComponent(info.machineId)}`); + this.openerService.open(URI.parse(`${OpenNewsletterSignupUrlAction.URL}?machineId=${encodeURIComponent(info.machineId)}`)); } } @@ -127,14 +134,15 @@ export class OpenTwitterUrlAction extends Action { constructor( id: string, - label: string + label: string, + @IOpenerService private readonly openerService: IOpenerService ) { super(id, label); } run(): Promise { if (product.twitterUrl) { - window.open(product.twitterUrl); + this.openerService.open(URI.parse(product.twitterUrl)); } return Promise.resolve(); @@ -148,14 +156,15 @@ export class OpenRequestFeatureUrlAction extends Action { constructor( id: string, - label: string + label: string, + @IOpenerService private readonly openerService: IOpenerService ) { super(id, label); } run(): Promise { if (product.requestFeatureUrl) { - window.open(product.requestFeatureUrl); + this.openerService.open(URI.parse(product.requestFeatureUrl)); } return Promise.resolve(); @@ -169,7 +178,8 @@ export class OpenLicenseUrlAction extends Action { constructor( id: string, - label: string + label: string, + @IOpenerService private readonly openerService: IOpenerService ) { super(id, label); } @@ -178,9 +188,9 @@ export class OpenLicenseUrlAction extends Action { if (product.licenseUrl) { if (language) { const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?'; - window.open(`${product.licenseUrl}${queryArgChar}lang=${language}`); + this.openerService.open(URI.parse(`${product.licenseUrl}${queryArgChar}lang=${language}`)); } else { - window.open(product.licenseUrl); + this.openerService.open(URI.parse(product.licenseUrl)); } } @@ -195,7 +205,8 @@ export class OpenPrivacyStatementUrlAction extends Action { constructor( id: string, - label: string + label: string, + @IOpenerService private readonly openerService: IOpenerService ) { super(id, label); } @@ -204,9 +215,9 @@ export class OpenPrivacyStatementUrlAction extends Action { if (product.privacyStatementUrl) { if (language) { const queryArgChar = product.privacyStatementUrl.indexOf('?') > 0 ? '&' : '?'; - window.open(`${product.privacyStatementUrl}${queryArgChar}lang=${language}`); + this.openerService.open(URI.parse(`${product.privacyStatementUrl}${queryArgChar}lang=${language}`)); } else { - window.open(product.privacyStatementUrl); + this.openerService.open(URI.parse(product.privacyStatementUrl)); } } diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index 7bb652eb2f5..3203c32d897 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -15,7 +15,6 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IQuickInputService, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; -import product from 'vs/platform/product/node/product'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -55,8 +54,12 @@ export class NewWindowAction extends Action { } export abstract class BaseZoomAction extends Action { + private static readonly SETTING_KEY = 'window.zoomLevel'; + private static readonly MAX_ZOOM_LEVEL = 9; + private static readonly MIN_ZOOM_LEVEL = -8; + constructor( id: string, label: string, @@ -68,6 +71,10 @@ export abstract class BaseZoomAction extends Action { protected async setConfiguredZoomLevel(level: number): Promise { level = Math.round(level); // when reaching smallest zoom, prevent fractional zoom levels + if (level > BaseZoomAction.MAX_ZOOM_LEVEL || level < BaseZoomAction.MIN_ZOOM_LEVEL) { + return; // https://github.com/microsoft/vscode/issues/48357 + } + const applyZoom = () => { webFrame.setZoomLevel(level); browser.setZoomFactor(webFrame.getZoomFactor()); @@ -267,25 +274,6 @@ export class QuickSwitchWindow extends BaseSwitchWindow { } } - -export class ShowAboutDialogAction extends Action { - - static readonly ID = 'workbench.action.showAboutDialog'; - static LABEL = nls.localize('about', "About {0}", product.applicationName); - - constructor( - id: string, - label: string, - @IWindowsService private readonly windowsService: IWindowsService - ) { - super(id, label); - } - - run(): Promise { - return this.windowsService.openAboutDialog(); - } -} - export const NewWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { return accessor.get(IWindowsService).newWindowTab(); }; diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts similarity index 91% rename from src/vs/workbench/electron-browser/main.contribution.ts rename to src/vs/workbench/electron-browser/desktop.contribution.ts index 10557fccab0..6cc5770cf50 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -13,17 +13,16 @@ import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, OpenNewsletterSignupUrlAction } from 'vs/workbench/electron-browser/actions/helpActions'; import { ToggleSharedProcessAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; -import { ShowAboutDialogAction, ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; -import { AddRootFolderAction, GlobalRemoveRootFolderAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, CloseWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; +import { AddRootFolderAction, GlobalRemoveRootFolderAction, SaveWorkspaceAsAction, DuplicateWorkspaceInNewWindowAction, CloseWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import product from 'vs/platform/product/node/product'; // Actions (function registerActions(): void { @@ -53,7 +52,7 @@ import product from 'vs/platform/product/node/product'; registry.registerWorkbenchAction(new SyncActionDescriptor(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.closeWindow', // close the window when the last editor is closed by reusing the same keybinding + id: CloseCurrentWindowAction.ID, // close the window when the last editor is closed by reusing the same keybinding weight: KeybindingWeight.WorkbenchContrib, when: ContextKeyExpr.and(NoEditorsVisibleContext, SingleEditorGroupsContext), primary: KeyMod.CtrlCmd | KeyCode.KEY_W, @@ -84,18 +83,6 @@ import product from 'vs/platform/product/node/product'; registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory, SupportsWorkspacesContext); registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); - - CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run(); - }); - - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: OpenWorkspaceConfigFileAction.ID, - title: { value: `${workspacesCategory}: ${OpenWorkspaceConfigFileAction.LABEL}`, original: 'Workspaces: Open Workspace Configuration File' }, - }, - when: WorkbenchStateContext.isEqualTo('workspace') - }); })(); // Actions: macOS Native Tabs @@ -163,7 +150,6 @@ import product from 'vs/platform/product/node/product'; registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), `Help: About ${product.applicationName}`, helpCategory); })(); })(); @@ -272,7 +258,7 @@ import product from 'vs/platform/product/node/product'; MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '1_welcome', command: { - id: 'workbench.action.openDocumentationUrl', + id: OpenDocumentationUrlAction.ID, title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation") }, order: 3 @@ -291,7 +277,7 @@ import product from 'vs/platform/product/node/product'; MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '2_reference', command: { - id: 'workbench.action.keybindingsReference', + id: KeybindingsReferenceAction.ID, title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference") }, order: 1 @@ -300,7 +286,7 @@ import product from 'vs/platform/product/node/product'; MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '2_reference', command: { - id: 'workbench.action.openIntroductoryVideosUrl', + id: OpenIntroductoryVideosUrlAction.ID, title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos") }, order: 2 @@ -309,7 +295,7 @@ import product from 'vs/platform/product/node/product'; MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '2_reference', command: { - id: 'workbench.action.openTipsAndTricksUrl', + id: OpenTipsAndTricksUrlAction.ID, title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "Tips and Tri&&cks") }, order: 3 @@ -319,7 +305,7 @@ import product from 'vs/platform/product/node/product'; MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '3_feedback', command: { - id: 'workbench.action.openTwitterUrl', + id: OpenTwitterUrlAction.ID, title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join Us on Twitter") }, order: 1 @@ -328,7 +314,7 @@ import product from 'vs/platform/product/node/product'; MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '3_feedback', command: { - id: 'workbench.action.openRequestFeatureUrl', + id: OpenRequestFeatureUrlAction.ID, title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") }, order: 2 @@ -347,7 +333,7 @@ import product from 'vs/platform/product/node/product'; MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '4_legal', command: { - id: 'workbench.action.openLicenseUrl', + id: OpenLicenseUrlAction.ID, title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") }, order: 1 @@ -356,7 +342,7 @@ import product from 'vs/platform/product/node/product'; MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '4_legal', command: { - id: 'workbench.action.openPrivacyStatementUrl', + id: OpenPrivacyStatementUrlAction.ID, title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "Privac&&y Statement") }, order: 2 @@ -366,7 +352,7 @@ import product from 'vs/platform/product/node/product'; MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '5_tools', command: { - id: 'workbench.action.toggleDevTools', + id: ToggleDevToolsAction.ID, title: nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools") }, order: 1 @@ -380,17 +366,6 @@ import product from 'vs/platform/product/node/product'; }, order: 2 }); - - // About - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: 'z_about', - command: { - id: 'workbench.action.showAboutDialog', - title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") - }, - order: 1, - when: IsMacContext.toNegated() - }); })(); // Configuration @@ -506,7 +481,7 @@ import product from 'vs/platform/product/node/product'; 'default': true, 'description': nls.localize('window.nativeFullScreen', "Controls if native full-screen should be used on macOS. Disable this option to prevent macOS from creating a new space when going full-screen."), 'scope': ConfigurationScope.APPLICATION, - 'included': false /* isMacintosh */ + 'included': isMacintosh }, 'window.clickThroughInactive': { 'type': 'boolean', @@ -533,4 +508,4 @@ import product from 'vs/platform/product/node/product'; } } }); -})(); \ No newline at end of file +})(); diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/desktop.main.ts similarity index 95% rename from src/vs/workbench/electron-browser/main.ts rename to src/vs/workbench/electron-browser/desktop.main.ts index 61c920df264..a6836c38075 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -51,10 +51,11 @@ import { SignService } from 'vs/platform/sign/node/signService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { basename } from 'vs/base/common/resources'; +import { IProductService } from 'vs/platform/product/common/product'; +import product from 'vs/platform/product/node/product'; class CodeRendererMain extends Disposable { - private workbench: Workbench; private readonly environmentService: WorkbenchEnvironmentService; constructor(configuration: IWindowConfiguration) { @@ -116,17 +117,17 @@ class CodeRendererMain extends Disposable { mark('willStartWorkbench'); // Create Workbench - this.workbench = new Workbench(document.body, services.serviceCollection, services.logService); + const workbench = new Workbench(document.body, services.serviceCollection, services.logService); // Layout - this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true))); + this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true, workbench))); // Workbench Lifecycle - this._register(this.workbench.onShutdown(() => this.dispose())); - this._register(this.workbench.onWillShutdown(event => event.join(services.storageService.close()))); + this._register(workbench.onShutdown(() => this.dispose())); + this._register(workbench.onWillShutdown(event => event.join(services.storageService.close()))); // Startup - const instantiationService = this.workbench.startup(); + const instantiationService = workbench.startup(); // Window this._register(instantiationService.createInstance(ElectronWindow)); @@ -145,7 +146,7 @@ class CodeRendererMain extends Disposable { services.logService.trace('workbench configuration', JSON.stringify(this.environmentService.configuration)); } - private onWindowResize(e: Event, retry: boolean): void { + private onWindowResize(e: Event, retry: boolean, workbench: Workbench): 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 @@ -154,12 +155,12 @@ class CodeRendererMain extends Disposable { // call at the next animation frame once, in the hope that the dimensions are // proper then. if (retry) { - scheduleAtNextAnimationFrame(() => this.onWindowResize(e, false)); + scheduleAtNextAnimationFrame(() => this.onWindowResize(e, false, workbench)); } return; } - this.workbench.layout(); + workbench.layout(); } } @@ -168,7 +169,7 @@ class CodeRendererMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE. - // CONTRIBUTE IT VIA WORKBENCH.MAIN.TS AND registerSingleton(). + // CONTRIBUTE IT VIA WORKBENCH.DESKTOP.MAIN.TS AND registerSingleton(). // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Main Process @@ -178,6 +179,9 @@ class CodeRendererMain extends Disposable { // Environment serviceCollection.set(IWorkbenchEnvironmentService, this.environmentService); + // Product + serviceCollection.set(IProductService, { _serviceBrand: undefined, ...product }); + // Log const logService = this._register(this.createLogService(mainProcessService, this.environmentService)); serviceCollection.set(ILogService, logService); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index e10a49baee6..7f13270ca83 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -14,7 +14,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { toResource, IUntitledResourceInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowsService, IWindowService, IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IRunKeybindingInWindowRequest } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IRunKeybindingInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; @@ -24,7 +24,7 @@ import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -32,7 +32,7 @@ import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecyc import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; -import { isRootUser, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; +import { isRootUser, isWindows, isMacintosh, isLinux, isWeb } from 'vs/base/common/platform'; import product from 'vs/platform/product/node/product'; import pkg from 'vs/platform/product/node/package'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -45,6 +45,17 @@ import { coalesce } from 'vs/base/common/arrays'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { isEqual } from 'vs/base/common/resources'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { MenubarControl } from '../browser/parts/titlebar/menubarControl'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IUpdateService } from 'vs/platform/update/common/update'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IPreferencesService } from '../services/preferences/common/preferences'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IMenubarService, IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/node/menubar'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { Schemas } from 'vs/base/common/network'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -91,7 +102,9 @@ export class ElectronWindow extends Disposable { @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @ITextFileService private readonly textFileService: ITextFileService + @ITextFileService private readonly textFileService: ITextFileService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IOpenerService private readonly openerService: IOpenerService ) { super(); @@ -213,6 +226,8 @@ export class ElectronWindow extends Disposable { this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('window.zoomLevel')) { this.updateWindowZoomLevel(); + } else if (e.affectsConfiguration('keyboard.touchbar.enabled') || e.affectsConfiguration('keyboard.touchbar.ignored')) { + this.updateTouchbarMenu(); } })); @@ -295,13 +310,13 @@ export class ElectronWindow extends Disposable { private create(): void { - // Handle window.open() calls - const $this = this; - window.open = function (url: string, target: string, features: string, replace: boolean): Window | null { - $this.windowsService.openExternal(url); + // Native menu controller + if (isMacintosh || getTitleBarStyle(this.configurationService, this.environmentService) === 'native') { + this._register(this.instantiationService.createInstance(NativeMenubarControl)); + } - return null; - }; + // Handle open calls + this.setupOpenHandlers(); // Emit event when vscode is ready this.lifecycleService.when(LifecyclePhase.Ready).then(() => ipc.send('vscode:workbenchReady', this.windowService.windowId)); @@ -339,12 +354,42 @@ export class ElectronWindow extends Disposable { } } + private setupOpenHandlers(): void { + + // Block window.open() calls + const $this = this; + window.open = function (): Window | null { + throw new Error('Prevented call to window.open(). Use IOpenerService instead!'); + }; + + // Handle internal open() calls + this.openerService.registerOpener({ + async open(resource: URI, options?: { openToSide?: boolean; openExternal?: boolean; } | undefined): Promise { + + // If either the caller wants to open externally or the + // scheme is one where we prefer to open externally + // we handle this resource by delegating the opening to + // the main process to prevent window focus issues. + const scheme = resource.scheme.toLowerCase(); + const preferOpenExternal = (scheme === Schemas.mailto || scheme === Schemas.http || scheme === Schemas.https); + if ((options && options.openExternal) || preferOpenExternal) { + const success = await $this.windowsService.openExternal(encodeURI(resource.toString(true))); + if (!success && resource.scheme === Schemas.file) { + // if opening failed, and this is a file, we can still try to reveal it + await $this.windowsService.showItemInFolder(resource); + } + + return true; + } + + return false; // not handled by us + } + }); + } + private updateTouchbarMenu(): void { - if ( - !isMacintosh || // macOS only - !this.configurationService.getValue('keyboard.touchbar.enabled') // disabled via setting - ) { - return; + if (!isMacintosh) { + return; // macOS only } // Dispose old @@ -366,31 +411,40 @@ export class ElectronWindow extends Disposable { const actions: Array = []; + const disabled = this.configurationService.getValue('keyboard.touchbar.enabled') === false; + const ignoredItems = this.configurationService.getValue('keyboard.touchbar.ignored') || []; + // Fill actions into groups respecting order this.touchBarDisposables.add(createAndFillInActionBarActions(this.touchBarMenu, undefined, actions)); // Convert into command action multi array const items: ICommandAction[][] = []; let group: ICommandAction[] = []; - for (const action of actions) { + if (!disabled) { + for (const action of actions) { - // Command - if (action instanceof MenuItemAction) { - group.push(action.item); - } + // Command + if (action instanceof MenuItemAction) { + if (ignoredItems.indexOf(action.item.id) >= 0) { + continue; // ignored + } - // Separator - else if (action instanceof Separator) { - if (group.length) { - items.push(group); + group.push(action.item); } - group = []; - } - } + // Separator + else if (action instanceof Separator) { + if (group.length) { + items.push(group); + } - if (group.length) { - items.push(group); + group = []; + } + } + + if (group.length) { + items.push(group); + } } // Only update if the actions have changed @@ -530,3 +584,192 @@ export class ElectronWindow extends Disposable { return this.editorService.openEditors(resources); } } + +class NativeMenubarControl extends MenubarControl { + constructor( + @IMenuService menuService: IMenuService, + @IWindowService windowService: IWindowService, + @IWindowsService windowsService: IWindowsService, + @IContextKeyService contextKeyService: IContextKeyService, + @IKeybindingService keybindingService: IKeybindingService, + @IConfigurationService configurationService: IConfigurationService, + @ILabelService labelService: ILabelService, + @IUpdateService updateService: IUpdateService, + @IStorageService storageService: IStorageService, + @INotificationService notificationService: INotificationService, + @IPreferencesService preferencesService: IPreferencesService, + @IEnvironmentService environmentService: IEnvironmentService, + @IAccessibilityService accessibilityService: IAccessibilityService, + @IMenubarService private readonly menubarService: IMenubarService + ) { + super( + menuService, + windowService, + windowsService, + contextKeyService, + keybindingService, + configurationService, + labelService, + updateService, + storageService, + notificationService, + preferencesService, + environmentService, + accessibilityService); + + if (isMacintosh && !isWeb) { + this.menus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService)); + this.topLevelTitles['Preferences'] = nls.localize('mPreferences', "Preferences"); + } + + for (const topLevelMenuName of Object.keys(this.topLevelTitles)) { + const menu = this.menus[topLevelMenuName]; + if (menu) { + this._register(menu.onDidChange(() => this.updateMenubar())); + } + } + + this.windowService.getRecentlyOpened().then((recentlyOpened) => { + this.recentlyOpened = recentlyOpened; + + this.doUpdateMenubar(true); + }); + + this.registerListeners(); + } + + protected doUpdateMenubar(firstTime: boolean): void { + + // Send menus to main process to be rendered by Electron + const menubarData = { menus: {}, keybindings: {} }; + if (this.getMenubarMenus(menubarData)) { + this.menubarService.updateMenubar(this.windowService.windowId, menubarData); + } + } + + private getMenubarMenus(menubarData: IMenubarData): boolean { + if (!menubarData) { + return false; + } + + menubarData.keybindings = this.getAdditionalKeybindings(); + for (const topLevelMenuName of Object.keys(this.topLevelTitles)) { + const menu = this.menus[topLevelMenuName]; + if (menu) { + const menubarMenu: IMenubarMenu = { items: [] }; + this.populateMenuItems(menu, menubarMenu, menubarData.keybindings); + if (menubarMenu.items.length === 0) { + return false; // Menus are incomplete + } + menubarData.menus[topLevelMenuName] = menubarMenu; + } + } + + return true; + } + + private populateMenuItems(menu: IMenu, menuToPopulate: IMenubarMenu, keybindings: { [id: string]: IMenubarKeybinding | undefined }) { + let groups = menu.getActions(); + for (let group of groups) { + const [, actions] = group; + + actions.forEach(menuItem => { + + if (menuItem instanceof SubmenuItemAction) { + const submenu = { items: [] }; + + if (!this.menus[menuItem.item.submenu]) { + this.menus[menuItem.item.submenu] = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService); + this._register(this.menus[menuItem.item.submenu]!.onDidChange(() => this.updateMenubar())); + } + + const menuToDispose = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService); + this.populateMenuItems(menuToDispose, submenu, keybindings); + + let menubarSubmenuItem: IMenubarMenuItemSubmenu = { + id: menuItem.id, + label: menuItem.label, + submenu: submenu + }; + + menuToPopulate.items.push(menubarSubmenuItem); + menuToDispose.dispose(); + } else { + if (menuItem.id === 'workbench.action.openRecent') { + const actions = this.getOpenRecentActions().map(this.transformOpenRecentAction); + menuToPopulate.items.push(...actions); + } + + let menubarMenuItem: IMenubarMenuItemAction = { + id: menuItem.id, + label: menuItem.label + }; + + if (menuItem.checked) { + menubarMenuItem.checked = true; + } + + if (!menuItem.enabled) { + menubarMenuItem.enabled = false; + } + + menubarMenuItem.label = this.calculateActionLabel(menubarMenuItem); + keybindings[menuItem.id] = this.getMenubarKeybinding(menuItem.id); + menuToPopulate.items.push(menubarMenuItem); + } + }); + + menuToPopulate.items.push({ id: 'vscode.menubar.separator' }); + } + + if (menuToPopulate.items.length > 0) { + menuToPopulate.items.pop(); + } + } + + private transformOpenRecentAction(action: Separator | (IAction & { uri: URI })): MenubarMenuItem { + if (action instanceof Separator) { + return { id: 'vscode.menubar.separator' }; + } + + return { + id: action.id, + uri: action.uri, + enabled: action.enabled, + label: action.label + }; + } + + private getAdditionalKeybindings(): { [id: string]: IMenubarKeybinding } { + const keybindings: { [id: string]: IMenubarKeybinding } = {}; + if (isMacintosh) { + const keybinding = this.getMenubarKeybinding('workbench.action.quit'); + if (keybinding) { + keybindings['workbench.action.quit'] = keybinding; + } + } + + return keybindings; + } + + private getMenubarKeybinding(id: string): IMenubarKeybinding | undefined { + const binding = this.keybindingService.lookupKeybinding(id); + if (!binding) { + return undefined; + } + + // first try to resolve a native accelerator + const electronAccelerator = binding.getElectronAccelerator(); + if (electronAccelerator) { + return { label: electronAccelerator, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) }; + } + + // we need this fallback to support keybindings that cannot show in electron menus (e.g. chords) + const acceleratorLabel = binding.getLabel(); + if (acceleratorLabel) { + return { label: acceleratorLabel, isNative: false, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) }; + } + + return undefined; + } +} diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/node/accessibilityService.ts index 02b215b5a09..983db9c1e67 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/node/accessibilityService.ts @@ -9,9 +9,12 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { AbstractAccessibilityService } from 'vs/platform/accessibility/common/abstractAccessibilityService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class AccessibilityService extends AbstractAccessibilityService implements IAccessibilityService { - _serviceBrand: any; + + _serviceBrand!: ServiceIdentifier; private _accessibilitySupport = AccessibilitySupport.Unknown; @@ -59,4 +62,6 @@ export class AccessibilityService extends AbstractAccessibilityService implement return this._accessibilitySupport; } -} \ No newline at end of file +} + +registerSingleton(IAccessibilityService, AccessibilityService, true); diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index 3193fee9594..6725c5e2bb9 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -106,20 +106,15 @@ export class BackupFilesModel implements IBackupFilesModel { export class BackupFileService implements IBackupFileService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private impl: IBackupFileService; constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IFileService fileService: IFileService + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, + @IFileService protected fileService: IFileService ) { - const backupWorkspaceResource = environmentService.configuration.backupWorkspaceResource; - if (backupWorkspaceResource) { - this.impl = new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, fileService); - } else { - this.impl = new InMemoryBackupFileService(this.hashPath); - } + this.initialize(); } protected hashPath(resource: URI): string { @@ -128,9 +123,25 @@ export class BackupFileService implements IBackupFileService { return hash(str).toString(16); } - initialize(backupWorkspaceResource: URI): void { + private initialize(): void { + const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource; + if (backupWorkspaceResource) { + this.impl = new BackupFileServiceImpl(backupWorkspaceResource, this.hashPath, this.fileService); + } else { + this.impl = new InMemoryBackupFileService(this.hashPath); + } + } + + reinitialize(): void { + + // Re-init implementation (unless we are running in-memory) if (this.impl instanceof BackupFileServiceImpl) { - this.impl.initialize(backupWorkspaceResource); + const backupWorkspaceResource = this.environmentService.configuration.backupWorkspaceResource; + if (backupWorkspaceResource) { + this.impl.initialize(backupWorkspaceResource); + } else { + this.impl = new InMemoryBackupFileService(this.hashPath); + } } } @@ -177,15 +188,15 @@ class BackupFileServiceImpl implements IBackupFileService { private static readonly PREAMBLE_META_SEPARATOR = ' '; // using a character that is know to be escaped in a URI as separator private static readonly PREAMBLE_MAX_LENGTH = 10000; - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; - private backupWorkspacePath: URI; + private backupWorkspacePath!: URI; private isShuttingDown: boolean; private ioOperationQueues: ResourceQueue; // queue IO operations to ensure write order - private ready: Promise; - private model: IBackupFilesModel; + private ready!: Promise; + private model!: IBackupFilesModel; constructor( backupWorkspaceResource: URI, @@ -201,10 +212,10 @@ class BackupFileServiceImpl implements IBackupFileService { initialize(backupWorkspaceResource: URI): void { this.backupWorkspacePath = backupWorkspaceResource; - this.ready = this.init(); + this.ready = this.doInitialize(); } - private init(): Promise { + private doInitialize(): Promise { this.model = new BackupFilesModel(this.fileService); return this.model.resolve(this.backupWorkspacePath); @@ -323,7 +334,7 @@ class BackupFileServiceImpl implements IBackupFileService { return contents.substr(0, newLineIndex); } - throw new Error(`Could not find ${JSON.stringify(matchingString)} in first ${maximumBytesToRead} bytes of ${file}`); + throw new Error(`Backup: Could not find ${JSON.stringify(matchingString)} in first ${maximumBytesToRead} bytes of ${file}`); } async resolveBackupContent(backup: URI): Promise> { @@ -357,9 +368,7 @@ class BackupFileServiceImpl implements IBackupFileService { const content = await this.fileService.readFileStream(backup); const factory = await createTextBufferFactoryFromStream(content.value, metaPreambleFilter); - // Trigger read for meta data extraction from the filter above - factory.getFirstLineText(1); - + // Extract meta data (if any) let meta: T | undefined; const metaStartIndex = metaRaw.indexOf(BackupFileServiceImpl.PREAMBLE_META_SEPARATOR); if (metaStartIndex !== -1) { @@ -370,6 +379,15 @@ class BackupFileServiceImpl implements IBackupFileService { } } + // We have seen reports (e.g. https://github.com/microsoft/vscode/issues/78500) where + // if VSCode goes down while writing the backup file, the file can turn empty because + // it always first gets truncated and then written to. In this case, we will not find + // the meta-end marker ('\n') and as such the backup can only be invalid. We bail out + // here if that is the case. + if (!metaEndFound) { + throw new Error(`Backup: Could not find meta end marker in ${backup}. The file is probably corrupt.`); + } + return { value: factory, meta }; } @@ -380,7 +398,7 @@ class BackupFileServiceImpl implements IBackupFileService { export class InMemoryBackupFileService implements IBackupFileService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private backups: Map = new Map(); @@ -440,4 +458,4 @@ export class InMemoryBackupFileService implements IBackupFileService { toBackupResource(resource: URI): URI { return URI.file(join(resource.scheme, this.hashPath(resource))); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index 30b5c5acf51..74beddef46f 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -7,13 +7,14 @@ import { BackupFileService as CommonBackupFileService } from 'vs/workbench/servi import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import * as crypto from 'crypto'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; export class BackupFileService extends CommonBackupFileService { protected hashPath(resource: URI): string { return hashPath(resource); } - } /* @@ -21,5 +22,8 @@ export class BackupFileService extends CommonBackupFileService { */ export function hashPath(resource: URI): string { const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); + return crypto.createHash('md5').update(str).digest('hex'); -} \ No newline at end of file +} + +registerSingleton(IBackupFileService, BackupFileService); diff --git a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts index 990d688b3cc..b4f37b1b276 100644 --- a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts @@ -27,6 +27,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { hashPath, BackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; import { BACKUPS } from 'vs/platform/environment/common/environment'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; +import { VSBuffer } from 'vs/base/common/buffer'; const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice'); const appSettingsHome = path.join(userdataDir, 'User'); @@ -479,6 +480,28 @@ suite('BackupFileService', () => { await testResolveBackup(fooBarFile, contents, meta, null); }); + test('should throw an error when restoring invalid backup', async () => { + const contents = 'test\nand more stuff'; + + await service.backupResource(fooBarFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1); + + const backup = await service.loadBackupResource(fooBarFile); + if (!backup) { + throw new Error('Unexpected missing backup'); + } + + await service.fileService.writeFile(backup, VSBuffer.fromString('')); + + let err: Error; + try { + await service.resolveBackupContent(backup); + } catch (error) { + err = error; + } + + assert.ok(err!); + }); + async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) { if (typeof expectedMeta === 'undefined') { expectedMeta = meta; diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index f698782a600..fff264d6463 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -51,7 +51,7 @@ class ModelEditTask implements IDisposable { protected _edits: IIdentifiedSingleEditOperation[]; private _expectedModelVersionId: number | undefined; - protected _newEol: EndOfLineSequence; + protected _newEol: EndOfLineSequence | undefined; constructor(private readonly _modelReference: IReference) { this._model = this._modelReference.object.textEditorModel; @@ -142,7 +142,7 @@ class BulkEditModel implements IDisposable { private _textModelResolverService: ITextModelService; private _edits = new Map(); private _editor: ICodeEditor | undefined; - private _tasks: ModelEditTask[]; + private _tasks: ModelEditTask[] | undefined; private _progress: IProgress; constructor( @@ -159,7 +159,7 @@ class BulkEditModel implements IDisposable { } dispose(): void { - this._tasks = dispose(this._tasks); + this._tasks = dispose(this._tasks!); } addEdit(edit: ResourceTextEdit): void { @@ -196,7 +196,7 @@ class BulkEditModel implements IDisposable { } value.forEach(edit => task.addEdit(edit)); - this._tasks.push(task); + this._tasks!.push(task); this._progress.report(undefined); }); promises.push(promise); @@ -208,7 +208,7 @@ class BulkEditModel implements IDisposable { } validate(): ValidationResult { - for (const task of this._tasks) { + for (const task of this._tasks!) { const result = task.validate(); if (!result.canApply) { return result; @@ -218,7 +218,7 @@ class BulkEditModel implements IDisposable { } apply(): void { - for (const task of this._tasks) { + for (const task of this._tasks!) { task.apply(); this._progress.report(undefined); } diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 170e3f136aa..a5f15850dbf 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -17,9 +17,9 @@ export const folderSettingsSchemaId = 'vscode://schemas/settings/folder'; export const launchSchemaId = 'vscode://schemas/launch'; export const LOCAL_MACHINE_SCOPES = [ConfigurationScope.APPLICATION, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]; -export const REMOTE_MACHINE_SCOPES = [ConfigurationScope.MACHINE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]; -export const WORKSPACE_SCOPES = [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]; -export const FOLDER_SCOPES = [ConfigurationScope.RESOURCE]; +export const REMOTE_MACHINE_SCOPES = [ConfigurationScope.MACHINE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.MACHINE_OVERRIDABLE]; +export const WORKSPACE_SCOPES = [ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.MACHINE_OVERRIDABLE]; +export const FOLDER_SCOPES = [ConfigurationScope.RESOURCE, ConfigurationScope.MACHINE_OVERRIDABLE]; export const TASKS_CONFIGURATION_KEY = 'tasks'; export const LAUNCH_CONFIGURATION_KEY = 'launch'; @@ -36,4 +36,4 @@ export interface IConfigurationCache { write(key: ConfigurationKey, content: string): Promise; remove(key: ConfigurationKey): Promise; -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index ffef24c5573..90b8895fc96 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -133,7 +133,7 @@ export class ConfigurationEditingService { public _serviceBrand: any; private queue: Queue; - private remoteSettingsResource: URI | null; + private remoteSettingsResource: URI | null = null; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, 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 e59d6ad40d8..b6e9d5d09fa 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 @@ -106,6 +106,8 @@ suite('WorkspaceContextService - Folder', () => { workspaceResource = folderDir; const environmentService = new TestEnvironmentService(URI.file(parentDir)); const fileService = new FileService(new NullLogService()); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); fileService.registerProvider(Schemas.userData, new FileUserDataProvider(environmentService.appSettingsHome, environmentService.backupHome, new DiskFileSystemProvider(new NullLogService()), environmentService)); workspaceContextService = new WorkspaceService({ configurationCache: new ConfigurationCache(environmentService) }, environmentService, fileService, new RemoteAgentService({}, environmentService, new RemoteAuthorityResolverService(), new SignService(undefined))); return (workspaceContextService).initialize(convertToWorkspacePayload(URI.file(folderDir))); diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index f406a3e0d63..4709a15555b 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -250,7 +250,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR inputOptions.value = info.default; } return this.quickInputService.input(inputOptions).then(resolvedInput => { - return resolvedInput ? resolvedInput : undefined; + return resolvedInput; }); } diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts index fafa3c35e17..47281663ce9 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts @@ -60,4 +60,4 @@ export interface CommandInputInfo { args?: any; } -export type ConfiguredInput = PromptStringInputInfo | PickStringInputInfo | CommandInputInfo; \ No newline at end of file +export type ConfiguredInput = PromptStringInputInfo | PickStringInputInfo | CommandInputInfo; diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts index fa0e030188e..1fa8a4ca589 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts @@ -102,8 +102,20 @@ export const inputsSchema: IJSONSchema = { description: nls.localize('JsonSchema.input.command.command', "The command to execute for this input variable.") }, args: { - type: 'object', - description: nls.localize('JsonSchema.input.command.args', "Optional arguments passed to the command.") + oneOf: [ + { + type: 'object', + description: nls.localize('JsonSchema.input.command.args', "Optional arguments passed to the command.") + }, + { + type: 'array', + description: nls.localize('JsonSchema.input.command.args', "Optional arguments passed to the command.") + }, + { + type: 'string', + description: nls.localize('JsonSchema.input.command.args', "Optional arguments passed to the command.") + } + ] } } } 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 5b9f44514e9..dd668c74f6d 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 @@ -555,7 +555,7 @@ class MockQuickInputService implements IQuickInputService { return Promise.resolve(options ? 'resolved' + options.prompt : 'resolved'); } - backButton: IQuickInputButton; + backButton!: IQuickInputButton; createQuickPick(): IQuickPick { throw new Error('not implemented.'); diff --git a/src/vs/workbench/services/credentials/browser/credentialsService.ts b/src/vs/workbench/services/credentials/browser/credentialsService.ts new file mode 100644 index 00000000000..b7613b9c23c --- /dev/null +++ b/src/vs/workbench/services/credentials/browser/credentialsService.ts @@ -0,0 +1,132 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; + +export interface ICredentialsProvider { + getPassword(service: string, account: string): Promise; + setPassword(service: string, account: string, password: string): Promise; + deletePassword(service: string, account: string): Promise; + findPassword(service: string): Promise; +} + +export class BrowserCredentialsService implements ICredentialsService { + + _serviceBrand!: ServiceIdentifier; + + private credentialsProvider: ICredentialsProvider; + + constructor(@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService) { + if (environmentService.options && environmentService.options.credentialsProvider) { + this.credentialsProvider = environmentService.options.credentialsProvider; + } else { + this.credentialsProvider = new LocalStorageCredentialsProvider(); + } + } + + async getPassword(service: string, account: string): Promise { + return this.credentialsProvider.getPassword(service, account); + } + + async setPassword(service: string, account: string, password: string): Promise { + return this.credentialsProvider.setPassword(service, account, password); + } + + async deletePassword(service: string, account: string): Promise { + return this.credentialsProvider.deletePassword(service, account); + } + + async findPassword(service: string): Promise { + return this.credentialsProvider.findPassword(service); + } +} + +interface ICredential { + service: string; + account: string; + password: string; +} + +class LocalStorageCredentialsProvider implements ICredentialsProvider { + + static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider'; + + private _credentials: ICredential[]; + private get credentials(): ICredential[] { + if (!this._credentials) { + try { + const serializedCredentials = window.localStorage.getItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY); + if (serializedCredentials) { + this._credentials = JSON.parse(serializedCredentials); + } + } catch (error) { + // ignore + } + + if (!Array.isArray(this._credentials)) { + this._credentials = []; + } + } + + return this._credentials; + } + + private save(): void { + window.localStorage.setItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY, JSON.stringify(this.credentials)); + } + + async getPassword(service: string, account: string): Promise { + return this.doGetPassword(service, account); + } + + private async doGetPassword(service: string, account?: string): Promise { + for (const credential of this.credentials) { + if (credential.service === service) { + if (typeof account !== 'string' || account === credential.account) { + return credential.password; + } + } + } + + return null; + } + + async setPassword(service: string, account: string, password: string): Promise { + this.deletePassword(service, account); + + this.credentials.push({ service, account, password }); + + this.save(); + } + + async deletePassword(service: string, account: string): Promise { + let found = false; + + this._credentials = this.credentials.filter(credential => { + if (credential.service === service && credential.account === account) { + found = true; + + return false; + } + + return true; + }); + + if (found) { + this.save(); + } + + return found; + } + + async findPassword(service: string): Promise { + return this.doGetPassword(service); + } +} + +registerSingleton(ICredentialsService, BrowserCredentialsService, true); diff --git a/src/vs/platform/credentials/common/credentials.ts b/src/vs/workbench/services/credentials/common/credentials.ts similarity index 84% rename from src/vs/platform/credentials/common/credentials.ts rename to src/vs/workbench/services/credentials/common/credentials.ts index dc6618d89f7..8fa520c374e 100644 --- a/src/vs/platform/credentials/common/credentials.ts +++ b/src/vs/workbench/services/credentials/common/credentials.ts @@ -3,12 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export const ICredentialsService = createDecorator('ICredentialsService'); export interface ICredentialsService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; + getPassword(service: string, account: string): Promise; setPassword(service: string, account: string, password: string): Promise; deletePassword(service: string, account: string): Promise; diff --git a/src/vs/platform/credentials/node/credentialsService.ts b/src/vs/workbench/services/credentials/node/credentialsService.ts similarity index 80% rename from src/vs/platform/credentials/node/credentialsService.ts rename to src/vs/workbench/services/credentials/node/credentialsService.ts index 282b761fe2e..1361f169be3 100644 --- a/src/vs/platform/credentials/node/credentialsService.ts +++ b/src/vs/workbench/services/credentials/node/credentialsService.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; import { IdleValue } from 'vs/base/common/async'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; type KeytarModule = { getPassword(service: string, account: string): Promise; @@ -15,7 +17,7 @@ type KeytarModule = { export class KeytarCredentialsService implements ICredentialsService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; private readonly _keytar = new IdleValue>(() => import('keytar')); @@ -39,3 +41,5 @@ export class KeytarCredentialsService implements ICredentialsService { return keytar.findPassword(service); } } + +registerSingleton(ICredentialsService, KeytarCredentialsService, true); diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index f7d1e60691e..512d28485d8 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -19,6 +19,7 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; +import { isWeb } from 'vs/base/common/platform'; export class FileDialogService implements IFileDialogService { @@ -90,9 +91,14 @@ export class FileDialogService implements IFileDialogService { return (schema !== Schemas.file) || (setting === true); } - private ensureFileSchema(schema: string): string[] { + private addFileSchemaIfNeeded(schema: string): string[] { + // Include File schema unless the schema is web // Don't allow untitled schema through. - return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); + if (isWeb) { + return schema === Schemas.untitled ? [Schemas.file] : [schema]; + } else { + return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); + } } async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { @@ -104,7 +110,7 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); - const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + const availableFileSystems = this.addFileSchemaIfNeeded(schema); const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); @@ -130,7 +136,7 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFile.title', 'Open File'); - const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + const availableFileSystems = this.addFileSchemaIfNeeded(schema); const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); if (uri) { @@ -152,7 +158,7 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFolder.title', 'Open Folder'); - const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + const availableFileSystems = this.addFileSchemaIfNeeded(schema); const uri = await this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); if (uri) { @@ -175,7 +181,7 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openWorkspace.title', 'Open Workspace'); const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; - const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + const availableFileSystems = this.addFileSchemaIfNeeded(schema); const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); if (uri) { @@ -192,7 +198,7 @@ export class FileDialogService implements IFileDialogService { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { - options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); } options.title = nls.localize('saveFileAs.title', 'Save As'); @@ -221,7 +227,7 @@ export class FileDialogService implements IFileDialogService { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { - options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); } return this.saveRemoteResource(options); @@ -239,7 +245,7 @@ export class FileDialogService implements IFileDialogService { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { - options.availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); } const uri = await this.pickRemoteResource(options); @@ -301,4 +307,4 @@ function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmen return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome); } -registerSingleton(IFileDialogService, FileDialogService, true); \ No newline at end of file +registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts index 7c70bf3fecb..28fb4787d24 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts @@ -46,24 +46,24 @@ enum UpdateResult { } export class RemoteFileDialog { - private options: IOpenDialogOptions; - private currentFolder: URI; - private filePickBox: IQuickPick; - private hidden: boolean; - private allowFileSelection: boolean; - private allowFolderSelection: boolean; + private options!: IOpenDialogOptions; + private currentFolder!: URI; + private filePickBox!: IQuickPick; + private hidden: boolean = false; + private allowFileSelection: boolean = true; + private allowFolderSelection: boolean = false; private remoteAuthority: string | undefined; - private requiresTrailing: boolean; + private requiresTrailing: boolean = false; private trailing: string | undefined; private scheme: string = REMOTE_HOST_SCHEME; private contextKey: IContextKey; - private userEnteredPathSegment: string; - private autoCompletePathSegment: string; - private activeItem: FileQuickPickItem; - private userHome: URI; + private userEnteredPathSegment: string = ''; + private autoCompletePathSegment: string = ''; + private activeItem: FileQuickPickItem | undefined; + private userHome!: URI; private badPath: string | undefined; - private remoteAgentEnvironment: IRemoteAgentEnvironment | null; - private separator: string; + private remoteAgentEnvironment: IRemoteAgentEnvironment | null | undefined; + private separator: string = '/'; private onBusyChangeEmitter = new Emitter(); private updatingPromise: CancelablePromise | undefined; @@ -762,8 +762,8 @@ export class RemoteFileDialog { private pathAppend(uri: URI, additional: string): string { if ((additional === '..') || (additional === '.')) { - const basePath = this.pathFromUri(uri); - return basePath + this.separator + additional; + const basePath = this.pathFromUri(uri, true); + return basePath + additional; } else { return this.pathFromUri(resources.joinPath(uri, additional)); } @@ -865,4 +865,4 @@ export class RemoteFileDialog { return undefined; } } -} \ 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 444045e8825..bf36c64c500 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -33,7 +33,7 @@ type ICachedEditorInput = ResourceEditorInput | IFileEditorInput | DataUriEditor export class EditorService extends Disposable implements EditorServiceImpl { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static CACHE: ResourceMap = new ResourceMap(); @@ -56,8 +56,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { private fileInputFactory: IFileInputFactory; private openEditorHandlers: IOpenEditorOverrideHandler[] = []; - private lastActiveEditor: IEditorInput | null; - private lastActiveGroupId: GroupIdentifier; + private lastActiveEditor: IEditorInput | null = null; + private lastActiveGroupId: GroupIdentifier | null = null; constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -216,11 +216,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region openEditor() - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: GroupIdentifier): Promise { + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: GroupIdentifier): Promise { // Typed Editor Support if (editor instanceof EditorInput) { @@ -240,11 +240,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { return this.doOpenEditor(targetGroup, typedInput, editorOptions); } - return Promise.resolve(null); + return Promise.resolve(undefined); } - protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { - return group.openEditor(editor, options); + protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { + return group.openEditor(editor, options).then(withNullAsUndefined); } private findTargetGroup(input: IEditorInput, options?: IEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): IEditorGroup { @@ -315,7 +315,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } const textOptions: ITextEditorOptions = options; - if (!!textOptions.selection) { + if (textOptions.selection || textOptions.viewState) { return TextEditorOptions.create(options); } @@ -448,13 +448,14 @@ export class EditorService extends Disposable implements EditorServiceImpl { typedEditors.push(replaceEditorArg as IEditorReplacement); } else { const editor = replaceEditorArg.editor as IResourceEditor; + const replacement = replaceEditorArg.replacement as IResourceEditor; const typedEditor = this.createInput(editor); - const replacementEditor = this.createInput(replaceEditorArg.replacement as IResourceEditor); + const typedReplacement = this.createInput(replacement); typedEditors.push({ editor: typedEditor, - replacement: replacementEditor, - options: this.toOptions(editor.options) + replacement: typedReplacement, + options: this.toOptions(replacement.options) }); } }); @@ -626,7 +627,7 @@ export interface IEditorOpenHandler { * method by providing a IEditorOpenHandler. */ export class DelegatingEditorService extends EditorService { - private editorOpenHandler: IEditorOpenHandler; + private editorOpenHandler: IEditorOpenHandler | undefined; constructor( @IEditorGroupsService editorGroupService: IEditorGroupsService, @@ -650,7 +651,7 @@ export class DelegatingEditorService extends EditorService { this.editorOpenHandler = handler; } - protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { + protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { if (!this.editorOpenHandler) { return super.doOpenEditor(group, editor, options); } @@ -664,4 +665,4 @@ export class DelegatingEditorService extends EditorService { } } -registerSingleton(IEditorService, EditorService); \ No newline at end of file +registerSingleton(IEditorService, EditorService); diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index a9ab498cc98..a53e5ce507e 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -59,7 +59,13 @@ export const enum GroupsArrangement { /** * Size all groups evenly. */ - EVEN + EVEN, + + /** + * Will behave like MINIMIZE_OTHERS if the active + * group is not already maximized and EVEN otherwise + */ + TOGGLE } export interface GroupLayoutArgument { @@ -176,6 +182,11 @@ export interface IEditorGroupsService { */ readonly onDidLayout: Event; + /** + * An event for when the index of a group changes. + */ + readonly onDidGroupIndexChange: Event; + /** * The size of the editor groups area. */ @@ -343,7 +354,7 @@ export const enum GroupChangeKind { /* Group Changes */ GROUP_ACTIVE, - GROUP_LABEL, + GROUP_INDEX, /* Editor Changes */ EDITOR_OPEN, @@ -374,6 +385,14 @@ export interface IEditorGroup { */ readonly id: GroupIdentifier; + /** + * A number that indicates the position of this group in the visual + * order of groups from left to right and top to bottom. The lowest + * index will likely be top-left while the largest index in most + * cases should be bottom-right, but that depends on the grid. + */ + readonly index: number; + /** * A human readable label for the group. This label can change depending * on the layout of all editor groups. Clients should listen on the diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index ab79c65ff48..5664c9c8c45 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -36,7 +36,7 @@ export interface IOpenEditorOverride { * If defined, will prevent the opening of an editor and replace the resulting * promise with the provided promise for the openEditor() call. */ - override?: Promise; + override?: Promise; } export interface IVisibleEditor extends IEditor { @@ -117,13 +117,13 @@ export interface IEditorService { * active group. Use `SIDE_GROUP_TYPE` to open the editor in a new editor group to the side * of the currently active group. * - * @returns the editor that opened or NULL if the operation failed or the editor was not + * @returns the editor that opened or `undefined` if the operation failed or the editor was not * opened to be active. */ - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; /** * Open editors in an editor group. @@ -185,4 +185,4 @@ export interface IEditorService { * Converts a lightweight input to a workbench editor input. */ 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 477fdfc5bc7..d461e545ddc 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -196,8 +196,11 @@ suite('EditorGroupsService', () => { const gridOrder = part.getGroups(GroupsOrder.GRID_APPEARANCE); assert.equal(gridOrder.length, 3); assert.equal(gridOrder[0], rootGroup); + assert.equal(gridOrder[0].index, 0); assert.equal(gridOrder[1], rightGroup); + assert.equal(gridOrder[1].index, 1); assert.equal(gridOrder[2], downGroup); + assert.equal(gridOrder[2].index, 2); part.moveGroup(downGroup, rightGroup, GroupDirection.DOWN); assert.equal(groupMovedCounter, 1); @@ -254,30 +257,60 @@ suite('EditorGroupsService', () => { part.dispose(); }); - test('groups labels', function () { + test('groups index / labels', function () { const part = createPart(); const rootGroup = part.groups[0]; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); const downGroup = part.addGroup(rightGroup, GroupDirection.DOWN); - let labelChangeCounter = 0; + let groupIndexChangedCounter = 0; + const groupIndexChangedListener = part.onDidGroupIndexChange(() => { + groupIndexChangedCounter++; + }); + + let indexChangeCounter = 0; const labelChangeListener = downGroup.onDidGroupChange(e => { - if (e.kind === GroupChangeKind.GROUP_LABEL) { - labelChangeCounter++; + if (e.kind === GroupChangeKind.GROUP_INDEX) { + indexChangeCounter++; } }); + assert.equal(rootGroup.index, 0); + assert.equal(rightGroup.index, 1); + assert.equal(downGroup.index, 2); assert.equal(rootGroup.label, 'Group 1'); assert.equal(rightGroup.label, 'Group 2'); assert.equal(downGroup.label, 'Group 3'); part.removeGroup(rightGroup); + assert.equal(rootGroup.index, 0); + assert.equal(downGroup.index, 1); assert.equal(rootGroup.label, 'Group 1'); assert.equal(downGroup.label, 'Group 2'); - assert.equal(labelChangeCounter, 1); + assert.equal(indexChangeCounter, 1); + assert.equal(groupIndexChangedCounter, 1); + + part.moveGroup(downGroup, rootGroup, GroupDirection.UP); + assert.equal(downGroup.index, 0); + assert.equal(rootGroup.index, 1); + assert.equal(downGroup.label, 'Group 1'); + assert.equal(rootGroup.label, 'Group 2'); + assert.equal(indexChangeCounter, 2); + assert.equal(groupIndexChangedCounter, 3); + + const newFirstGroup = part.addGroup(downGroup, GroupDirection.UP); + assert.equal(newFirstGroup.index, 0); + assert.equal(downGroup.index, 1); + assert.equal(rootGroup.index, 2); + assert.equal(newFirstGroup.label, 'Group 1'); + assert.equal(downGroup.label, 'Group 2'); + assert.equal(rootGroup.label, 'Group 3'); + assert.equal(indexChangeCounter, 3); + assert.equal(groupIndexChangedCounter, 6); labelChangeListener.dispose(); + groupIndexChangedListener.dispose(); part.dispose(); }); @@ -355,7 +388,7 @@ suite('EditorGroupsService', () => { test('editor basics', async function () { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); await part.whenRestored; @@ -404,7 +437,7 @@ suite('EditorGroupsService', () => { assert.equal(group.isActive(inputInactive), false); assert.equal(group.isOpened(input), true); assert.equal(group.isOpened(inputInactive), true); - assert.equal(group.isEmpty(), false); + assert.equal(group.isEmpty, false); assert.equal(group.count, 2); assert.equal(editorWillOpenCounter, 2); assert.equal(editorDidOpenCounter, 2); @@ -453,7 +486,7 @@ suite('EditorGroupsService', () => { test('openEditors / closeEditors', async () => { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); const input = new TestEditorInput(URI.file('foo/bar')); const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); @@ -464,14 +497,14 @@ suite('EditorGroupsService', () => { assert.equal(group.getEditor(1), inputInactive); await group.closeEditors([input, inputInactive]); - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); part.dispose(); }); test('closeEditors (except one)', async () => { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); const input1 = new TestEditorInput(URI.file('foo/bar1')); const input2 = new TestEditorInput(URI.file('foo/bar2')); @@ -492,7 +525,7 @@ suite('EditorGroupsService', () => { test('closeEditors (saved only)', async () => { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); const input1 = new TestEditorInput(URI.file('foo/bar1')); const input2 = new TestEditorInput(URI.file('foo/bar2')); @@ -512,7 +545,7 @@ suite('EditorGroupsService', () => { test('closeEditors (direction: right)', async () => { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); const input1 = new TestEditorInput(URI.file('foo/bar1')); const input2 = new TestEditorInput(URI.file('foo/bar2')); @@ -534,7 +567,7 @@ suite('EditorGroupsService', () => { test('closeEditors (direction: left)', async () => { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); const input1 = new TestEditorInput(URI.file('foo/bar1')); const input2 = new TestEditorInput(URI.file('foo/bar2')); @@ -556,7 +589,7 @@ suite('EditorGroupsService', () => { test('closeAllEditors', async () => { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); const input = new TestEditorInput(URI.file('foo/bar')); const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); @@ -567,14 +600,14 @@ suite('EditorGroupsService', () => { assert.equal(group.getEditor(1), inputInactive); await group.closeAllEditors(); - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); part.dispose(); }); test('moveEditor (same group)', async () => { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); const input = new TestEditorInput(URI.file('foo/bar')); const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); @@ -602,7 +635,7 @@ suite('EditorGroupsService', () => { test('moveEditor (across groups)', async () => { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); const rightGroup = part.addGroup(group, GroupDirection.RIGHT); @@ -624,7 +657,7 @@ suite('EditorGroupsService', () => { test('copyEditor (across groups)', async () => { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); const rightGroup = part.addGroup(group, GroupDirection.RIGHT); @@ -647,7 +680,7 @@ suite('EditorGroupsService', () => { test('replaceEditors', async () => { const part = createPart(); const group = part.activeGroup; - assert.equal(group.isEmpty(), true); + assert.equal(group.isEmpty, true); const input = new TestEditorInput(URI.file('foo/bar')); const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); 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 913a95d4aa1..712b8f922b4 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -49,8 +49,8 @@ export class TestEditorControl extends BaseEditor { } export class TestEditorInput extends EditorInput implements IFileEditorInput { - public gotDisposed: boolean; - private fails: boolean; + public gotDisposed = false; + private fails = false; constructor(private resource: URI) { super(); } getTypeId() { return 'testEditorInputForEditorService'; } diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 2bd39c97554..6cbe59689db 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWindowConfiguration, IPath, IPathsToWaitFor } from 'vs/platform/windows/common/windows'; -import { IEnvironmentService, IExtensionHostDebugParams, IDebugParams, BACKUPS } from 'vs/platform/environment/common/environment'; +import { IExtensionHostDebugParams, IDebugParams, BACKUPS } from 'vs/platform/environment/common/environment'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { IProcessEnvironment } from 'vs/base/common/platform'; @@ -13,6 +13,9 @@ import { ExportData } from 'vs/base/common/performance'; import { LogLevel } from 'vs/platform/log/common/log'; import { joinPath } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; +import { generateUuid } from 'vs/base/common/uuid'; export class BrowserWindowConfiguration implements IWindowConfiguration { @@ -32,11 +35,13 @@ export class BrowserWindowConfiguration implements IWindowConfiguration { nodeCachedDataDir?: string; backupPath?: string; + backupWorkspaceResource?: URI; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; - remoteAuthority: string; + remoteAuthority?: string; + connectionToken?: string; zoomLevel?: number; fullscreen?: boolean; @@ -61,26 +66,30 @@ export interface IBrowserWindowConfiguration { workspaceId: string; remoteAuthority?: string; webviewEndpoint?: string; + connectionToken?: string; } -export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { - _serviceBrand: ServiceIdentifier; +export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironmentService { + + _serviceBrand!: ServiceIdentifier; readonly configuration: IWindowConfiguration = new BrowserWindowConfiguration(); - constructor(configuration: IBrowserWindowConfiguration) { + constructor(workspaceId: string, public readonly options: IWorkbenchConstructionOptions) { this.args = { _: [] }; this.appRoot = '/web/'; this.appNameLong = 'Visual Studio Code - Web'; - this.configuration.remoteAuthority = configuration.remoteAuthority; + this.configuration.remoteAuthority = options.remoteAuthority; + this.configuration.machineId = generateUuid(); this.userRoamingDataHome = URI.file('/User').with({ scheme: Schemas.userData }); this.settingsResource = joinPath(this.userRoamingDataHome, 'settings.json'); this.keybindingsResource = joinPath(this.userRoamingDataHome, 'keybindings.json'); this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); this.localeResource = joinPath(this.userRoamingDataHome, 'locale.json'); this.backupHome = joinPath(this.userRoamingDataHome, BACKUPS); - this.configuration.backupWorkspaceResource = joinPath(this.backupHome, configuration.workspaceId); + this.configuration.backupWorkspaceResource = joinPath(this.backupHome, workspaceId); + this.configuration.connectionToken = options.connectionToken || this.getConnectionTokenFromLocation(); this.logsPath = '/web/logs'; @@ -89,7 +98,7 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { break: false }; - this.webviewEndpoint = configuration.webviewEndpoint; + this.webviewEndpoint = options.webviewEndpoint; this.untitledWorkspacesHome = URI.from({ scheme: Schemas.untitled, path: 'Workspaces' }); if (document && document.location && document.location.search) { @@ -100,7 +109,7 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { for (let p of vars) { const pair = p.split('='); if (pair.length >= 2) { - map.set(decodeURIComponent(pair[0]), decodeURIComponent(pair[1])); + map.set(pair[0], decodeURIComponent(pair[1])); } } @@ -182,4 +191,21 @@ export class BrowserWorkbenchEnvironmentService implements IEnvironmentService { get webviewCspSource(): string { return this.webviewEndpoint ? this.webviewEndpoint : 'vscode-resource:'; } + + private getConnectionTokenFromLocation(): string | undefined { + // TODO: Check with @alexd where the token will be: search or hash? + let connectionToken: string | undefined = undefined; + if (document.location.search) { + connectionToken = this.getConnectionToken(document.location.search); + } + if (!connectionToken && document.location.hash) { + connectionToken = this.getConnectionToken(document.location.hash); + } + return connectionToken; + } + + private getConnectionToken(str: string): string | undefined { + const m = str.match(/[#&?]tkn=([^&]+)/); + return m ? m[1] : undefined; + } } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index fd4beaf134d..72c98796b9a 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -6,6 +6,7 @@ import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; export const IWorkbenchEnvironmentService = createDecorator('environmentService'); @@ -14,4 +15,6 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { _serviceBrand: ServiceIdentifier; readonly configuration: IWindowConfiguration; + + readonly options?: IWorkbenchConstructionOptions; } diff --git a/src/vs/workbench/services/environment/node/environmentService.ts b/src/vs/workbench/services/environment/node/environmentService.ts index 8b8b9354b63..2ebf10365a2 100644 --- a/src/vs/workbench/services/environment/node/environmentService.ts +++ b/src/vs/workbench/services/environment/node/environmentService.ts @@ -10,16 +10,18 @@ import { memoize } from 'vs/base/common/decorators'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/common/backup'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class WorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; constructor( private _configuration: IWindowConfiguration, execPath: string ) { super(_configuration, execPath); + this._configuration.backupWorkspaceResource = this._configuration.backupPath ? toBackupWorkspaceResource(this._configuration.backupPath, this) : undefined; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 337f52b278a..f1831bdbc19 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -8,7 +8,7 @@ import { IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionType, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, isLanguagePackExtension, IExtensionManifest } 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'; @@ -17,6 +17,8 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { localize } from 'vs/nls'; import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IProductService } from 'vs/platform/product/common/product'; +import { Schemas } from 'vs/base/common/network'; +import { IDownloadService } from 'vs/platform/download/common/download'; export class ExtensionManagementService extends Disposable implements IExtensionManagementService { @@ -34,6 +36,7 @@ export class ExtensionManagementService extends Disposable implements IExtension @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IConfigurationService protected readonly configurationService: IConfigurationService, @IProductService protected readonly productService: IProductService, + @IDownloadService protected readonly downloadService: IDownloadService, ) { super(); if (this.extensionManagementServerService.localExtensionManagementServer) { @@ -142,15 +145,43 @@ export class ExtensionManagementService extends Disposable implements IExtension } async install(vsix: URI): Promise { + if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { + const manifest = await this.getManifest(vsix); + if (isLanguagePackExtension(manifest)) { + // Install on both servers + const [local] = await Promise.all(this.servers.map(server => this.installVSIX(vsix, server))); + return local; + } + if (isUIExtension(manifest, this.productService, this.configurationService)) { + // Install only on local server + return this.installVSIX(vsix, this.extensionManagementServerService.localExtensionManagementServer); + } + // Install only on remote server + return this.installVSIX(vsix, this.extensionManagementServerService.remoteExtensionManagementServer); + } if (this.extensionManagementServerService.localExtensionManagementServer) { - return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix); + return this.installVSIX(vsix, this.extensionManagementServerService.localExtensionManagementServer); } if (this.extensionManagementServerService.remoteExtensionManagementServer) { - return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix); + return this.installVSIX(vsix, this.extensionManagementServerService.remoteExtensionManagementServer); } return Promise.reject('No Servers to Install'); } + protected installVSIX(vsix: URI, server: IExtensionManagementServer): Promise { + return server.extensionManagementService.install(vsix); + } + + getManifest(vsix: URI): Promise { + if (vsix.scheme === Schemas.file && this.extensionManagementServerService.localExtensionManagementServer) { + return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getManifest(vsix); + } + if (vsix.scheme === Schemas.vscodeRemote && this.extensionManagementServerService.remoteExtensionManagementServer) { + return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.getManifest(vsix); + } + return Promise.reject('No Servers'); + } + async installFromGallery(gallery: IGalleryExtension): Promise { if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None); @@ -191,4 +222,4 @@ export class ExtensionManagementService extends Disposable implements IExtension private getServer(extension: ILocalExtension): IExtensionManagementServer | null { return this.extensionManagementServerService.getExtensionManagementServer(extension.location); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/extensionManagement/node/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/node/extensionManagementService.ts index 46db06b9d1a..143026c2b91 100644 --- a/src/vs/workbench/services/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/node/extensionManagementService.ts @@ -3,34 +3,26 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { tmpdir } from 'os'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ILocalExtension, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; -import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; -import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { ExtensionManagementService as BaseExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { Schemas } from 'vs/base/common/network'; +import * as path from 'vs/base/common/path'; export class ExtensionManagementService extends BaseExtensionManagementService { - async install(vsix: URI): Promise { - if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - const manifest = await getManifest(vsix.fsPath); - if (isLanguagePackExtension(manifest)) { - // Install on both servers - const [local] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix))); - return local; - } - if (isUIExtension(manifest, this.productService, this.configurationService)) { - // Install only on local server - return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix); - } - // Install only on remote server - return this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.install(vsix); + protected async installVSIX(vsix: URI, server: IExtensionManagementServer): Promise { + if (vsix.scheme === Schemas.vscodeRemote && server === this.extensionManagementServerService.localExtensionManagementServer) { + const downloadedLocation = URI.file(path.join(tmpdir(), generateUuid())); + await this.downloadService.download(vsix, downloadedLocation); + vsix = downloadedLocation; } - if (this.extensionManagementServerService.localExtensionManagementServer) { - return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix); - } - return Promise.reject('No Servers to Install'); + return server.extensionManagementService.install(vsix); } - } + +registerSingleton(IExtensionManagementService, ExtensionManagementService); diff --git a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts index 5852ce2d53c..4536a5ad507 100644 --- a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts @@ -16,12 +16,12 @@ import { IExtensionContributions, ExtensionType, IExtension } from 'vs/platform/ import { isUndefinedOrNull } from 'vs/base/common/types'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ProductService } from 'vs/platform/product/node/productService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { assign } from 'vs/base/common/objects'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { productService } from 'vs/workbench/test/workbenchTestServices'; function storageService(instantiationService: TestInstantiationService): IStorageService { let service = instantiationService.get(IStorageService); @@ -46,7 +46,7 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService), instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService), - new ProductService() + productService ); } diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 077598d16d6..78076e31480 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -18,10 +18,21 @@ import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/co import { RemoteExtensionHostClient, IInitDataProvider } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { WebWorkerExtensionHostStarter } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter'; +import { URI } from 'vs/base/common/uri'; +import { isWebExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browser/webWorkerFileSystemProvider'; +import { Schemas } from 'vs/base/common/network'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; +import { DeltaExtensionsResult } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { - private _remoteExtensionsEnvironmentData: IRemoteAgentEnvironment | null; + private _disposables = new DisposableStore(); + private _remoteExtensionsEnvironmentData: IRemoteAgentEnvironment | null = null; constructor( @IInstantiationService instantiationService: IInstantiationService, @@ -32,6 +43,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IFileService fileService: IFileService, @IProductService productService: IProductService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + @IConfigurationService private readonly _configService: IConfigurationService, + @IStaticExtensionsService private readonly _staticExtensions: IStaticExtensionsService, ) { super( instantiationService, @@ -43,8 +56,19 @@ export class ExtensionService extends AbstractExtensionService implements IExten productService, ); - this._remoteExtensionsEnvironmentData = null; this._initialize(); + this._initFetchFileSystem(); + } + + dispose(): void { + this._disposables.dispose(); + super.dispose(); + } + + private _initFetchFileSystem(): void { + const provider = new FetchFileSystemProvider(); + this._disposables.add(this._fileService.registerProvider(Schemas.http, provider)); + this._disposables.add(this._fileService.registerProvider(Schemas.https, provider)); } private _createProvider(remoteAuthority: string): IInitDataProvider { @@ -58,36 +82,60 @@ export class ExtensionService extends AbstractExtensionService implements IExten }; } - protected _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] { + protected _createExtensionHosts(_isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] { const result: ExtensionHostProcessManager[] = []; - const remoteAgentConnection = this._remoteAgentService.getConnection()!; - const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); - const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); - result.push(remoteExtHostProcessManager); + const webExtensions = this.getExtensions().then(extensions => extensions.filter(ext => isWebExtension(ext, this._configService))); + const webHostProcessWorker = this._instantiationService.createInstance(WebWorkerExtensionHostStarter, true, webExtensions, URI.parse('empty:value')); //todo@joh + const webHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, webHostProcessWorker, null, initialActivationEvents); + result.push(webHostProcessManager); + + const remoteAgentConnection = this._remoteAgentService.getConnection(); + if (remoteAgentConnection) { + const remoteExtensions = this.getExtensions().then(extensions => extensions.filter(ext => !isWebExtension(ext, this._configService))); + const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, remoteExtensions, this._createProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); + const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); + result.push(remoteExtHostProcessManager); + } return result; } protected async _scanAndHandleExtensions(): Promise { // fetch the remote environment - const remoteEnv = (await this._remoteAgentService.getEnvironment())!; + let [remoteEnv, localExtensions] = await Promise.all([ + this._remoteAgentService.getEnvironment(), + this._staticExtensions.getExtensions() + ]); - // enable or disable proposed API per extension - this._checkEnableProposedApi(remoteEnv.extensions); + let result: DeltaExtensionsResult; - // remove disabled extensions - remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension)); + // local: only enabled and web'ish extension + localExtensions = localExtensions.filter(ext => this._isEnabled(ext) && isWebExtension(ext, this._configService)); + this._checkEnableProposedApi(localExtensions); - // save for remote extension's init data - this._remoteExtensionsEnvironmentData = remoteEnv; + if (!remoteEnv) { + result = this._registry.deltaExtensions(localExtensions, []); + + } else { + // remote: only enabled and none-web'ish extension + remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension) && !isWebExtension(extension, this._configService)); + this._checkEnableProposedApi(remoteEnv.extensions); + + // in case of overlap, the remote wins + const isRemoteExtension = new Set(); + remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier))); + localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier))); + + // save for remote extension's init data + this._remoteExtensionsEnvironmentData = remoteEnv; + + result = this._registry.deltaExtensions(remoteEnv.extensions.concat(localExtensions), []); + } - // this._handleExtensionPoints(([]).concat(remoteEnv.extensions).concat(localExtensions)); - const result = this._registry.deltaExtensions(remoteEnv.extensions, []); if (result.removedDueToLooping.length > 0) { this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', '))); } - this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions()); } diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts new file mode 100644 index 00000000000..ef8569f5252 --- /dev/null +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts @@ -0,0 +1,155 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getWorkerBootstrapUrl } from 'vs/base/worker/defaultWorkerFactory'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { createMessageOfType, MessageType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import * as platform from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; +import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; +import { IProductService } from 'vs/platform/product/common/product'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; + +export class WebWorkerExtensionHostStarter implements IExtensionHostStarter { + + private _toDispose = new DisposableStore(); + private _isTerminating: boolean = false; + private _protocol?: IMessagePassingProtocol; + + private readonly _onDidExit = new Emitter<[number, string | null]>(); + readonly onExit: Event<[number, string | null]> = this._onDidExit.event; + + constructor( + private readonly _autoStart: boolean, + private readonly _extensions: Promise, + private readonly _extensionHostLogsLocation: URI, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, + @ILabelService private readonly _labelService: ILabelService, + @ILogService private readonly _logService: ILogService, + @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, + @IProductService private readonly _productService: IProductService, + ) { + + } + + async start(): Promise { + + if (!this._protocol) { + + const emitter = new Emitter(); + + const url = getWorkerBootstrapUrl(require.toUrl('../worker/extensionHostWorkerMain.js'), 'WorkerExtensionHost'); + const worker = new Worker(url); + + worker.onmessage = (event) => { + const { data } = event; + if (!(data instanceof ArrayBuffer)) { + console.warn('UNKNOWN data received', data); + this._onDidExit.fire([77, 'UNKNOWN data received']); + return; + } + + emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength))); + }; + + worker.onerror = (event) => { + console.error(event.message, event.error); + this._onDidExit.fire([81, event.message || event.error]); + }; + + // keep for cleanup + this._toDispose.add(emitter); + this._toDispose.add(toDisposable(() => worker.terminate())); + + const protocol: IMessagePassingProtocol = { + onMessage: emitter.event, + send: vsbuf => { + const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength); + worker.postMessage(data, [data]); + } + }; + + // extension host handshake happens below + // (1) <== wait for: Ready + // (2) ==> send: init data + // (3) <== wait for: Initialized + + await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Ready))); + protocol.send(VSBuffer.fromString(JSON.stringify(await this._createExtHostInitData()))); + await Event.toPromise(Event.filter(protocol.onMessage, msg => isMessageOfType(msg, MessageType.Initialized))); + + this._protocol = protocol; + } + return this._protocol; + + } + + dispose(): void { + if (!this._protocol) { + this._toDispose.dispose(); + return; + } + if (this._isTerminating) { + return; + } + this._isTerminating = true; + this._protocol.send(createMessageOfType(MessageType.Terminate)); + setTimeout(() => this._toDispose.dispose(), 10 * 1000); + } + + getInspectPort(): number | undefined { + return undefined; + } + + private async _createExtHostInitData(): Promise { + const [telemetryInfo, extensionDescriptions] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._extensions]); + const workspace = this._contextService.getWorkspace(); + return { + commit: this._productService.commit, + version: this._productService.version, + parentPid: -1, + environment: { + isExtensionDevelopmentDebug: false, + appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined, + appSettingsHome: this._environmentService.appSettingsHome ? this._environmentService.appSettingsHome : undefined, + appName: this._productService.nameLong, + appUriScheme: this._productService.urlProtocol, + appLanguage: platform.language, + extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, + extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, + globalStorageHome: URI.parse('fake:globalStorageHome'), //todo@joh URI.file(this._environmentService.globalStorageHome), + userHome: URI.parse('fake:userHome'), //todo@joh URI.file(this._environmentService.userHome), + webviewResourceRoot: this._environmentService.webviewResourceRoot, + webviewCspSource: this._environmentService.webviewCspSource, + }, + workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { + configuration: workspace.configuration || undefined, + id: workspace.id, + name: this._labelService.getWorkspaceLabel(workspace) + }, + resolvedExtensions: [], + hostExtensions: [], + extensions: extensionDescriptions, + telemetryInfo, + logLevel: this._logService.getLevel(), + logsLocation: this._extensionHostLogsLocation, + autoStart: this._autoStart, + remote: { + authority: this._environmentService.configuration.remoteAuthority, + isRemote: false + }, + }; + } +} diff --git a/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts b/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts new file mode 100644 index 00000000000..13afbbcce1f --- /dev/null +++ b/src/vs/workbench/services/extensions/browser/webWorkerFileSystemProvider.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IFileSystemProvider, FileSystemProviderCapabilities, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileSystemProviderError, FileSystemProviderErrorCode } from 'vs/platform/files/common/files'; + +import { Event } from 'vs/base/common/event'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { NotImplementedError } from 'vs/base/common/errors'; + +export class FetchFileSystemProvider implements IFileSystemProvider { + + readonly capabilities = FileSystemProviderCapabilities.Readonly + FileSystemProviderCapabilities.FileReadWrite + FileSystemProviderCapabilities.PathCaseSensitive; + readonly onDidChangeCapabilities = Event.None; + readonly onDidChangeFile = Event.None; + + // working implementations + async readFile(resource: URI): Promise { + try { + const res = await fetch(resource.toString(true)); + if (res.status === 200) { + return new Uint8Array(await res.arrayBuffer()); + } + throw new FileSystemProviderError(res.statusText, FileSystemProviderErrorCode.Unknown); + } catch (err) { + throw new FileSystemProviderError(err, FileSystemProviderErrorCode.Unknown); + } + } + + // fake implementations + async stat(_resource: URI): Promise { + return { + type: FileType.File, + size: 0, + mtime: 0, + ctime: 0 + }; + } + + watch(): IDisposable { + return Disposable.None; + } + + // error implementations + writeFile(_resource: URI, _content: Uint8Array, _opts: FileWriteOptions): Promise { + throw new NotImplementedError(); + } + readdir(_resource: URI): Promise<[string, FileType][]> { + throw new NotImplementedError(); + } + mkdir(_resource: URI): Promise { + throw new NotImplementedError(); + } + delete(_resource: URI, _opts: FileDeleteOptions): Promise { + throw new NotImplementedError(); + } + rename(_from: URI, _to: URI, _opts: FileOverwriteOptions): Promise { + throw new NotImplementedError(); + } +} diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index eed7a616357..6445a03a6e6 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -170,14 +170,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._startExtensionHostProcess(false, Array.from(this._allRequestedActivateEvents.keys())); } - public startExtensionHost(): void { + protected startExtensionHost(): void { this._startExtensionHostProcess(false, Array.from(this._allRequestedActivateEvents.keys())); } - public stopExtensionHost(): void { - this._stopExtensionHostProcess(); - } - public activateByEvent(activationEvent: string): Promise { if (this._installedExtensionsReady.isOpen()) { // Extensions have been scanned and interpreted @@ -263,6 +259,11 @@ export abstract class AbstractExtensionService extends Disposable implements IEx return 0; } + public async setRemoteEnvironment(env: { [key: string]: string | null }): Promise { + await this._extensionHostProcessManagers + .map(manager => manager.setRemoteEnvironment(env)); + } + //#endregion // --- impl diff --git a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts index 0c3523f555e..38c4b63eda7 100644 --- a/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionDescriptionRegistry.ts @@ -17,9 +17,9 @@ export class ExtensionDescriptionRegistry { public readonly onDidChange = this._onDidChange.event; private _extensionDescriptions: IExtensionDescription[]; - private _extensionsMap: Map; - private _extensionsArr: IExtensionDescription[]; - private _activationMap: Map; + private _extensionsMap!: Map; + private _extensionsArr!: IExtensionDescription[]; + private _activationMap!: Map; constructor(extensionDescriptions: IExtensionDescription[]) { this._extensionDescriptions = extensionDescriptions; diff --git a/src/vs/workbench/services/extensions/node/extensionHostMain.ts b/src/vs/workbench/services/extensions/common/extensionHostMain.ts similarity index 68% rename from src/vs/workbench/services/extensions/node/extensionHostMain.ts rename to src/vs/workbench/services/extensions/common/extensionHostMain.ts index 9e58237b634..16356a4a96c 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostMain.ts @@ -5,20 +5,22 @@ import { timeout } from 'vs/base/common/async'; import * as errors from 'vs/base/common/errors'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Counter } from 'vs/base/common/numbers'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { IInitData, MainContext, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; -import { ExtHostExtensionService, IHostUtils } from 'vs/workbench/api/node/extHostExtensionService'; -import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; -import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; +import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IExtHostRpcService, ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IURITransformerService, URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { IExtHostExtensionService, IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; // we don't (yet) throw when extensions parse // uris that have no scheme @@ -32,26 +34,17 @@ export interface IConsolePatchFn { (mainThreadConsole: MainThreadConsoleShape): any; } -export interface ILogServiceFn { - (initData: IInitData): ILogService; -} - export class ExtensionHostMain { private _isTerminating: boolean; private readonly _hostUtils: IHostUtils; - private readonly _extensionService: ExtHostExtensionService; - private readonly _extHostLogService: ExtHostLogService; - private disposables: IDisposable[] = []; - - private _searchRequestIdProvider: Counter; + private readonly _extensionService: IExtHostExtensionService; + private readonly _disposables = new DisposableStore(); constructor( protocol: IMessagePassingProtocol, initData: IInitData, hostUtils: IHostUtils, - consolePatchFn: IConsolePatchFn, - logServiceFn: ILogServiceFn, uriTransformer: IURITransformer | null ) { this._isTerminating = false; @@ -59,32 +52,31 @@ export class ExtensionHostMain { const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer); // ensure URIs are transformed and revived - initData = this.transform(initData, rpcProtocol); + initData = ExtensionHostMain._transform(initData, rpcProtocol); - // allow to patch console - consolePatchFn(rpcProtocol.getProxy(MainContext.MainThreadConsole)); + // bootstrap services + const services = new ServiceCollection(...getSingletonServiceDescriptors()); + services.set(IExtHostInitDataService, { _serviceBrand: undefined, ...initData }); + services.set(IExtHostRpcService, new ExtHostRpcService(rpcProtocol)); + services.set(IURITransformerService, new URITransformerService(uriTransformer)); + services.set(IHostUtils, hostUtils); - // services - this._extHostLogService = new ExtHostLogService(logServiceFn(initData), initData.logsLocation.fsPath); - this.disposables.push(this._extHostLogService); + const instaService: IInstantiationService = new InstantiationService(services, true); - this._searchRequestIdProvider = new Counter(); - const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, this._extHostLogService, this._searchRequestIdProvider, withNullAsUndefined(initData.workspace)); + // todo@joh + // ugly self - inject + const logService = instaService.invokeFunction(accessor => accessor.get(ILogService)); + this._disposables.add(logService); - this._extHostLogService.info('extension host started'); - this._extHostLogService.trace('initData', initData); + logService.info('extension host started'); + logService.trace('initData', initData); - const extHostConfiguraiton = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace); - this._extensionService = new ExtHostExtensionService( - hostUtils, - initData, - rpcProtocol, - extHostWorkspace, - extHostConfiguraiton, - initData.environment, - this._extHostLogService, - uriTransformer - ); + // todo@joh + // ugly self - inject + // must call initialize *after* creating the extension service + // because `initialize` itself creates instances that depend on it + this._extensionService = instaService.invokeFunction(accessor => accessor.get(IExtHostExtensionService)); + this._extensionService.initialize(); // 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) @@ -127,7 +119,7 @@ export class ExtensionHostMain { } this._isTerminating = true; - this.disposables = dispose(this.disposables); + this._disposables.dispose(); errors.setUnexpectedErrorHandler((err) => { // TODO: write to log once we have one @@ -141,7 +133,7 @@ export class ExtensionHostMain { }, 1000); } - private transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData { + private static _transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData { initData.extensions.forEach((ext) => (ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation))); initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot)); initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome)); diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index 21cddb13749..f8067e33e0d 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -154,9 +154,13 @@ export class ExtensionHostProcessManager extends Disposable { private async _measureUp(proxy: ExtHostExtensionServiceShape): Promise { const SIZE = 10 * 1024 * 1024; // 10MB - let b = Buffer.alloc(SIZE, Math.random() % 256); + let buff = VSBuffer.alloc(SIZE); + let value = Math.ceil(Math.random() * 256); + for (let i = 0; i < buff.byteLength; i++) { + buff.writeUInt8(i, value); + } const sw = StopWatch.create(true); - await proxy.$test_up(VSBuffer.wrap(b)); + await proxy.$test_up(buff); sw.stop(); return ExtensionHostProcessManager._convert(SIZE, sw.elapsed()); } @@ -288,6 +292,15 @@ export class ExtensionHostProcessManager extends Disposable { } return proxy.$deltaExtensions(toAdd, toRemove); } + + public async setRemoteEnvironment(env: { [key: string]: string | null }): Promise { + const proxy = await this._getExtensionHostProcessProxy(); + if (!proxy) { + return; + } + + return proxy.$setRemoteEnvironment(env); + } } const colorTables = [ diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 1bd329231b6..7c26e61e181 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -220,14 +220,10 @@ export interface IExtensionService { restartExtensionHost(): void; /** - * Starts the extension host. + * Modify the environment of the remote extension host + * @param env New properties for the remote extension host */ - startExtensionHost(): void; - - /** - * Stops the extension host. - */ - stopExtensionHost(): void; + setRemoteEnvironment(env: { [key: string]: string | null }): Promise; _logOrShowMessage(severity: Severity, msg: string): void; _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise; @@ -276,8 +272,7 @@ export class NullExtensionService implements IExtensionService { getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { return Object.create(null); } getInspectPort(): number { return 0; } restartExtensionHost(): void { } - startExtensionHost(): void { } - stopExtensionHost(): void { } + async setRemoteEnvironment(_env: { [key: string]: string | null }): Promise { } canAddExtension(): boolean { return false; } canRemoveExtension(): boolean { return false; } _logOrShowMessage(_severity: Severity, _msg: string): void { } diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 9776a42da6a..f5fd3137e47 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -61,9 +61,7 @@ export interface IExtensionPointUser { collector: ExtensionMessageCollector; } -export interface IExtensionPointHandler { - (extensions: IExtensionPointUser[], delta: ExtensionPointUserDelta): void; -} +export type IExtensionPointHandler = (extensions: readonly IExtensionPointUser[], delta: ExtensionPointUserDelta) => void; export interface IExtensionPoint { name: string; @@ -73,7 +71,7 @@ export interface IExtensionPoint { export class ExtensionPointUserDelta { - private static _toSet(arr: IExtensionPointUser[]): Set { + private static _toSet(arr: readonly IExtensionPointUser[]): Set { const result = new Set(); for (let i = 0, len = arr.length; i < len; i++) { result.add(ExtensionIdentifier.toKey(arr[i].description.identifier)); @@ -81,7 +79,7 @@ export class ExtensionPointUserDelta { return result; } - public static compute(previous: IExtensionPointUser[] | null, current: IExtensionPointUser[]): ExtensionPointUserDelta { + public static compute(previous: readonly IExtensionPointUser[] | null, current: readonly IExtensionPointUser[]): ExtensionPointUserDelta { if (!previous || !previous.length) { return new ExtensionPointUserDelta(current, []); } @@ -99,8 +97,8 @@ export class ExtensionPointUserDelta { } constructor( - public readonly added: IExtensionPointUser[], - public readonly removed: IExtensionPointUser[], + public readonly added: readonly IExtensionPointUser[], + public readonly removed: readonly IExtensionPointUser[], ) { } } diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index e03ddb10239..a1496708db6 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -10,6 +10,11 @@ import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionM import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IProductService } from 'vs/platform/product/common/product'; +export function isWebExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, configurationService); + return extensionKind === 'web'; +} + export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { const uiContributions = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name); const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); diff --git a/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts index 7330ceedefb..22f8c02a13c 100644 --- a/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts @@ -20,6 +20,9 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -48,7 +51,7 @@ export interface IExtensionUrlHandler { * * It also makes sure the user confirms opening URLs directed towards extensions. */ -export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { +class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { readonly _serviceBrand: any; @@ -67,7 +70,6 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService - ) { const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS); const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE); @@ -80,6 +82,9 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { urlService.registerHandler(this), toDisposable(() => clearInterval(interval)) ); + + const cache = ExtensionUrlBootstrapHandler.cache; + setTimeout(() => cache.forEach(uri => this.handleURL(uri))); } async handleURL(uri: URI, confirmed?: boolean): Promise { @@ -333,3 +338,33 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { } registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler); + +/** + * This class handles URLs before `ExtensionUrlHandler` is instantiated. + * More info: https://github.com/microsoft/vscode/issues/73101 + */ +class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandler { + + private static _cache: URI[] = []; + private static disposable: IDisposable; + + static get cache(): URI[] { + ExtensionUrlBootstrapHandler.disposable.dispose(); + + const result = ExtensionUrlBootstrapHandler._cache; + ExtensionUrlBootstrapHandler._cache = []; + return result; + } + + constructor(@IURLService urlService: IURLService) { + ExtensionUrlBootstrapHandler.disposable = urlService.registerHandler(this); + } + + handleURL(uri: URI): Promise { + ExtensionUrlBootstrapHandler._cache.push(uri); + return Promise.resolve(true); + } +} + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(ExtensionUrlBootstrapHandler, LifecyclePhase.Ready); diff --git a/src/vs/workbench/services/extensions/common/proxyIdentifier.ts b/src/vs/workbench/services/extensions/common/proxyIdentifier.ts index fdf7d366c94..311d9e7cd07 100644 --- a/src/vs/workbench/services/extensions/common/proxyIdentifier.ts +++ b/src/vs/workbench/services/extensions/common/proxyIdentifier.ts @@ -20,10 +20,10 @@ export interface IRPCProtocol { assertRegistered(identifiers: ProxyIdentifier[]): void; } +// @ts-ignore export class ProxyIdentifier { public static count = 0; _proxyIdentifierBrand: void; - _suppressCompilerUnusedWarning: T; public readonly isMain: boolean; public readonly sid: string; diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 037a2534d92..47aaf4a22bb 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -71,7 +71,6 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH public start(): Promise { const options: IConnectionOptions = { - isBuilt: this._environmentService.isBuilt, commit: this._productService.commit, socketFactory: this._socketFactory, addressProvider: { diff --git a/src/vs/workbench/services/extensions/common/staticExtensions.ts b/src/vs/workbench/services/extensions/common/staticExtensions.ts new file mode 100644 index 00000000000..06f102534d1 --- /dev/null +++ b/src/vs/workbench/services/extensions/common/staticExtensions.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IExtensionDescription, IExtensionManifest, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { UriComponents, URI } from 'vs/base/common/uri'; + +export const IStaticExtensionsService = createDecorator('IStaticExtensionsService'); + +export interface IStaticExtensionsService { + _serviceBrand: any; + getExtensions(): Promise; +} + +export class StaticExtensionsService implements IStaticExtensionsService { + + _serviceBrand: any; + + private readonly _descriptions: IExtensionDescription[] = []; + + constructor(staticExtensions: { packageJSON: IExtensionManifest, extensionLocation: UriComponents }[]) { + this._descriptions = staticExtensions.map(data => { + identifier: new ExtensionIdentifier(`${data.packageJSON.publisher}.${data.packageJSON.name}`), + extensionLocation: URI.revive(data.extensionLocation), + ...data.packageJSON, + }); + } + + async getExtensions(): Promise { + return this._descriptions; + } +} diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 10fd66cbdc9..5558123d19a 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -48,8 +48,8 @@ function getExtraDevSystemExtensionsRoot(): string { export class CachedExtensionScanner { public readonly scannedExtensions: Promise; - private _scannedExtensionsResolve: (result: IExtensionDescription[]) => void; - private _scannedExtensionsReject: (err: any) => void; + private _scannedExtensionsResolve!: (result: IExtensionDescription[]) => void; + private _scannedExtensionsReject!: (err: any) => void; public readonly translationConfig: Promise; constructor( diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 8e923aea1cb..0ed2188509d 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -87,6 +87,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._terminating = false; this._namedPipeServer = null; + this._inspectPort = null; this._extensionHostProcess = null; this._extensionHostConnection = null; this._messageProtocol = null; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 6fb7d2d1be1..b8c267ab590 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -34,6 +34,8 @@ import { IFileService } from 'vs/platform/files/common/files'; import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; import { IProductService } from 'vs/platform/product/common/product'; import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints'; +import { flatten } from 'vs/base/common/arrays'; +import { IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; class DeltaExtensionsQueueItem { constructor( @@ -64,6 +66,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IConfigurationService private readonly _configurationService: IConfigurationService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWindowService protected readonly _windowService: IWindowService, + @IStaticExtensionsService private readonly _staticExtensions: IStaticExtensionsService, ) { super( instantiationService, @@ -72,7 +75,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten telemetryService, extensionEnablementService, fileService, - productService, + productService ); if (this._extensionEnablementService.allUserExtensionsDisabled) { @@ -349,6 +352,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten } const result: ExtensionHostProcessManager[] = []; + const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation); const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, true, extHostProcessWorker, null, initialActivationEvents); result.push(extHostProcessManager); @@ -431,7 +435,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten const remoteAuthority = this._environmentService.configuration.remoteAuthority; const extensionHost = this._extensionHostProcessManagers[0]; - let localExtensions = await this._extensionScanner.scannedExtensions; + let localExtensions = flatten(await Promise.all([this._extensionScanner.scannedExtensions, this._staticExtensions.getExtensions()])); // enable or disable proposed API per extension this._checkEnableProposedApi(localExtensions); @@ -457,7 +461,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._remoteAuthorityResolverService.setResolvedAuthorityError(remoteAuthority, err); // Proceed with the local extension host - await this._startLocalExtensionHost(extensionHost, localExtensions); + await this._startLocalExtensionHost(extensionHost, localExtensions, localExtensions.map(extension => extension.identifier)); return; } @@ -502,20 +506,18 @@ export class ExtensionService extends AbstractExtensionService implements IExten // save for remote extension's init data this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv); - this._handleExtensionPoints(([]).concat(remoteEnv.extensions).concat(localExtensions)); - extensionHost.start(localExtensions.map(extension => extension.identifier)); - + await this._startLocalExtensionHost(extensionHost, remoteEnv.extensions.concat(localExtensions), localExtensions.map(extension => extension.identifier)); } else { - await this._startLocalExtensionHost(extensionHost, localExtensions); + await this._startLocalExtensionHost(extensionHost, localExtensions, localExtensions.map(extension => extension.identifier)); } } - private async _startLocalExtensionHost(extensionHost: ExtensionHostProcessManager, localExtensions: IExtensionDescription[]): Promise { - this._handleExtensionPoints(localExtensions); - extensionHost.start(localExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id))); + private async _startLocalExtensionHost(extensionHost: ExtensionHostProcessManager, allExtensions: IExtensionDescription[], localExtensions: ExtensionIdentifier[]): Promise { + this._registerAndHandleExtensions(allExtensions); + extensionHost.start(localExtensions.filter(id => this._registry.containsExtension(id))); } - private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void { + private _registerAndHandleExtensions(allExtensions: IExtensionDescription[]): void { const result = this._registry.deltaExtensions(allExtensions, []); if (result.removedDueToLooping.length > 0) { this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', '))); diff --git a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts index 84ec145c4c8..f94d89adb16 100644 --- a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts +++ b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts @@ -140,4 +140,4 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC } return this.getDependenciesAndPackedExtensionsRecursively(toGet, result, uiExtension, token); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 1a5822e645f..8b5028acac6 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -5,23 +5,22 @@ import * as nativeWatchdog from 'native-watchdog'; import * as net from 'net'; -import * as minimist from 'minimist'; +import * as minimist from 'vscode-minimist'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event, Emitter } from 'vs/base/common/event'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { PersistentProtocol, ProtocolConstants, createBufferedEvent } from 'vs/base/parts/ipc/common/ipc.net'; import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import product from 'vs/platform/product/node/product'; -import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; -import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain'; +import { ExtensionHostMain, IExitFn } from 'vs/workbench/services/extensions/common/extensionHostMain'; import { VSBuffer } from 'vs/base/common/buffer'; -import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc'; import { exists } from 'vs/base/node/pfs'; import { realpath } from 'vs/base/node/extpath'; -import { IHostUtils } from 'vs/workbench/api/node/extHostExtensionService'; -import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; +import 'vs/workbench/api/node/extHost.services'; interface ParsedExtHostArgs { uriTransformerPath?: string; @@ -63,27 +62,13 @@ function patchProcess(allowExit: boolean) { } } as (code?: number) => never; + // override Electron's process.crash() method process.crash = function () { const err = new Error('An extension called process.crash() and this was prevented.'); console.warn(err.stack); }; } -// use IPC messages to forward console-calls -function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void { - // The console is already patched to use `process.send()` - const nativeProcessSend = process.send!; - process.send = (...args: any[]) => { - if (args.length === 0 || !args[0] || args[0].type !== '__$console') { - return nativeProcessSend.apply(process, args); - } - - mainThreadConsole.$logExtensionHostMessage(args[0]); - }; -} - -const createLogService: ILogServiceFn = initData => new SpdLogService(ExtensionHostLogFileName, initData.logsLocation.fsPath, initData.logLevel); - interface IRendererConnection { protocol: IMessagePassingProtocol; initData: IInitData; @@ -205,7 +190,7 @@ async function createExtHostProtocol(): Promise { } function connectToRenderer(protocol: IMessagePassingProtocol): Promise { - return new Promise((c, e) => { + return new Promise((c) => { // Listen init data message const first = protocol.onMessage(raw => { @@ -312,6 +297,7 @@ export async function startExtensionHostProcess(): Promise { // host abstraction const hostUtils = new class NodeHost implements IHostUtils { + _serviceBrand: undefined; exit(code: number) { nativeExit(code); } exists(path: string) { return exists(path); } realpath(path: string) { return realpath(path); } @@ -333,8 +319,6 @@ export async function startExtensionHostProcess(): Promise { renderer.protocol, initData, hostUtils, - patchPatchedConsole, - createLogService, uriTransformer ); diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 5a7efd1462b..8abe6aa7f52 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -309,11 +309,6 @@ class ExtensionManifestValidator extends ExtensionManifestHandler { extensionDescription.id = `${extensionDescription.publisher}.${extensionDescription.name}`; extensionDescription.identifier = new ExtensionIdentifier(extensionDescription.id); - // main := absolutePath(`main`) - if (extensionDescription.main) { - extensionDescription.main = path.join(this._absoluteFolderPath, extensionDescription.main); - } - extensionDescription.extensionLocation = URI.file(this._absoluteFolderPath); return extensionDescription; @@ -409,7 +404,7 @@ class ExtensionManifestValidator extends ExtensionManifestHandler { export class ExtensionScannerInput { - public mtime: number; + public mtime: number | undefined; constructor( public readonly ourVersion: string, @@ -609,4 +604,4 @@ export class ExtensionScanner { return resultArr; }); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 6aa6ba1e617..12a6e3ea66a 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -17,11 +17,11 @@ import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorksp import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { ProxyAgent } from 'vscode-proxy-agent'; import { MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { URI } from 'vs/base/common/uri'; import { promisify } from 'util'; +import { ILogService } from 'vs/platform/log/common/log'; interface ConnectionResult { proxy: string; @@ -34,7 +34,7 @@ export function connectProxyResolver( extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extensionService: ExtHostExtensionService, - extHostLogService: ExtHostLogService, + extHostLogService: ILogService, mainThreadTelemetry: MainThreadTelemetryShape ) { const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry); @@ -47,7 +47,7 @@ const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache function setupProxyResolution( extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, - extHostLogService: ExtHostLogService, + extHostLogService: ILogService, mainThreadTelemetry: MainThreadTelemetryShape ) { const env = process.env; @@ -421,7 +421,7 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku }); } -function useSystemCertificates(extHostLogService: ExtHostLogService, useSystemCertificates: boolean, opts: http.RequestOptions, callback: () => void) { +function useSystemCertificates(extHostLogService: ILogService, useSystemCertificates: boolean, opts: http.RequestOptions, callback: () => void) { if (useSystemCertificates) { getCaCertificates(extHostLogService) .then(caCertificates => { @@ -443,7 +443,7 @@ function useSystemCertificates(extHostLogService: ExtHostLogService, useSystemCe } let _caCertificates: ReturnType | Promise; -async function getCaCertificates(extHostLogService: ExtHostLogService) { +async function getCaCertificates(extHostLogService: ILogService) { if (!_caCertificates) { _caCertificates = readCaCertificates() .then(res => res && res.certs.length ? res : undefined) @@ -469,24 +469,26 @@ async function readCaCertificates() { } async function readWindowsCaCertificates() { - const winCA = await import('vscode-windows-ca-certs'); + // Not using await to work around minifier bug (https://github.com/microsoft/vscode/issues/79044). + return import('vscode-windows-ca-certs') + .then(winCA => { + let ders: any[] = []; + const store = winCA(); + try { + let der: any; + while (der = store.next()) { + ders.push(der); + } + } finally { + store.done(); + } - let ders: any[] = []; - const store = winCA(); - try { - let der: any; - while (der = store.next()) { - ders.push(der); - } - } finally { - store.done(); - } - - const certs = new Set(ders.map(derToPem)); - return { - certs: Array.from(certs), - append: true - }; + const certs = new Set(ders.map(derToPem)); + return { + certs: Array.from(certs), + append: true + }; + }); } async function readMacCaCertificates() { diff --git a/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts b/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts index cf1658b05c5..6e0669eb0d2 100644 --- a/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts +++ b/src/vs/workbench/services/extensions/test/node/rpcProtocol.test.ts @@ -14,7 +14,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; suite('RPCProtocol', () => { class MessagePassingProtocol implements IMessagePassingProtocol { - private _pair: MessagePassingProtocol; + private _pair?: MessagePassingProtocol; private readonly _onMessage = new Emitter(); public readonly onMessage: Event = this._onMessage.event; @@ -25,7 +25,7 @@ suite('RPCProtocol', () => { public send(buffer: VSBuffer): void { process.nextTick(() => { - this._pair._onMessage.fire(buffer); + this._pair!._onMessage.fire(buffer); }); } } diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts new file mode 100644 index 00000000000..bf4a7791554 --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtHostOutputService, ExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; +import { IExtHostWorkspace, ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostDecorations, ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; +import { IExtHostConfiguration, ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; +import { IExtHostCommands, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; +import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; +import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService'; + +// register singleton services +registerSingleton(ILogService, ExtHostLogService); +registerSingleton(IExtHostOutputService, ExtHostOutputService); +registerSingleton(IExtHostWorkspace, ExtHostWorkspace); +registerSingleton(IExtHostDecorations, ExtHostDecorations); +registerSingleton(IExtHostConfiguration, ExtHostConfiguration); +registerSingleton(IExtHostCommands, ExtHostCommands); +registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors); +registerSingleton(IExtHostStorage, ExtHostStorage); +registerSingleton(IExtHostExtensionService, ExtHostExtensionService); + +// register services that only throw errors +function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { + return class { + constructor() { + return new Proxy({}, { + get(target: any, prop: string | number) { + if (target[prop]) { + return target[prop]; + } + throw new Error(`Not Implemented: ${name}->${String(prop)}`); + } + }); + } + }; +} +registerSingleton(IExtHostTerminalService, class extends NotImplementedProxy(IExtHostTerminalService) { }); +registerSingleton(IExtHostTask, class extends NotImplementedProxy(IExtHostTask) { }); +registerSingleton(IExtHostDebugService, class extends NotImplementedProxy(IExtHostDebugService) { }); +registerSingleton(IExtHostSearch, class extends NotImplementedProxy(IExtHostSearch) { }); +registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { + whenReady = Promise.resolve(); +}); diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts new file mode 100644 index 00000000000..34e3f35a624 --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { Emitter } from 'vs/base/common/event'; +import { isMessageOfType, MessageType, createMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtensionHostMain } from 'vs/workbench/services/extensions/common/extensionHostMain'; +import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; +import 'vs/workbench/services/extensions/worker/extHost.services'; + +// worker-self +declare namespace self { + function close(): void; +} + +// do not allow extensions to call terminate +const nativeClose = self.close.bind(self); +self.close = () => console.trace('An extension called terminate and this was prevented'); +let onTerminate = nativeClose; + +const hostUtil = new class implements IHostUtils { + _serviceBrand: any; + exit(_code?: number | undefined): void { + nativeClose(); + } + async exists(_path: string): Promise { + return true; + } + async realpath(path: string): Promise { + return path; + } +}; + +//todo@joh do not allow extensions to call postMessage and other globals... + +class ExtensionWorker { + + // protocol + readonly protocol: IMessagePassingProtocol; + + constructor() { + + let emitter = new Emitter(); + let terminating = false; + + onmessage = event => { + const { data } = event; + if (!(data instanceof ArrayBuffer)) { + console.warn('UNKNOWN data received', data); + return; + } + + const msg = VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)); + if (isMessageOfType(msg, MessageType.Terminate)) { + // handle terminate-message right here + terminating = true; + onTerminate(); + return; + } + + // emit non-terminate messages to the outside + emitter.fire(msg); + }; + + this.protocol = { + onMessage: emitter.event, + send: vsbuf => { + if (!terminating) { + const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength); + postMessage(data, [data]); + } + } + }; + } +} + +interface IRendererConnection { + protocol: IMessagePassingProtocol; + initData: IInitData; +} +function connectToRenderer(protocol: IMessagePassingProtocol): Promise { + return new Promise(resolve => { + const once = protocol.onMessage(raw => { + once.dispose(); + const initData = JSON.parse(raw.toString()); + protocol.send(createMessageOfType(MessageType.Initialized)); + resolve({ protocol, initData }); + }); + protocol.send(createMessageOfType(MessageType.Ready)); + }); +} + +(function create(): void { + const res = new ExtensionWorker(); + + connectToRenderer(res.protocol).then(data => { + + const extHostMain = new ExtensionHostMain( + data.protocol, + data.initData, + hostUtil, + null, + ); + + onTerminate = () => extHostMain.terminate(); + }); +})(); diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts new file mode 100644 index 00000000000..79455414c06 --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +(function () { + + let MonacoEnvironment = (self).MonacoEnvironment; + let monacoBaseUrl = MonacoEnvironment && MonacoEnvironment.baseUrl ? MonacoEnvironment.baseUrl : '../../../../../'; + + if (typeof (self).define !== 'function' || !(self).define.amd) { + importScripts(monacoBaseUrl + 'vs/loader.js'); + } + + require.config({ + baseUrl: monacoBaseUrl, + catchError: true + }); + + require(['vs/workbench/services/extensions/worker/extensionHostWorker'], () => { }, err => console.error(err)); +})(); diff --git a/src/vs/workbench/services/files/common/workspaceWatcher.ts b/src/vs/workbench/services/files/common/workspaceWatcher.ts index 7aa7ef157f6..da8c120643a 100644 --- a/src/vs/workbench/services/files/common/workspaceWatcher.ts +++ b/src/vs/workbench/services/files/common/workspaceWatcher.ts @@ -13,10 +13,10 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { ResourceMap } from 'vs/base/common/map'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity, NeverShowAgainScope } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import { FileService } from 'vs/platform/files/common/fileService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; export class WorkspaceWatcher extends Disposable { @@ -27,7 +27,7 @@ export class WorkspaceWatcher extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @INotificationService private readonly notificationService: INotificationService, - @IStorageService private readonly storageService: IStorageService + @IOpenerService private readonly openerService: IOpenerService ) { super(); @@ -73,38 +73,34 @@ export class WorkspaceWatcher extends Disposable { onUnexpectedError(msg); // Detect if we run < .NET Framework 4.5 - if (msg.indexOf('System.MissingMethodException') >= 0 && !this.storageService.getBoolean('ignoreNetVersionError', StorageScope.WORKSPACE)) { + if (msg.indexOf('System.MissingMethodException') >= 0) { this.notificationService.prompt( Severity.Warning, localize('netVersionError', "The Microsoft .NET Framework 4.5 is required. Please follow the link to install it."), [{ label: localize('installNet', "Download .NET Framework 4.5"), - run: () => window.open('https://go.microsoft.com/fwlink/?LinkId=786533') - }, - { - label: localize('neverShowAgain', "Don't Show Again"), - isSecondary: true, - run: () => this.storageService.store('ignoreNetVersionError', true, StorageScope.WORKSPACE) + run: () => this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?LinkId=786533')) }], - { sticky: true } + { + sticky: true, + neverShowAgain: { id: 'ignoreNetVersionError', isSecondary: true, scope: NeverShowAgainScope.WORKSPACE } + } ); } // Detect if we run into ENOSPC issues - if (msg.indexOf('ENOSPC') >= 0 && !this.storageService.getBoolean('ignoreEnospcError', StorageScope.WORKSPACE)) { + if (msg.indexOf('ENOSPC') >= 0) { this.notificationService.prompt( Severity.Warning, localize('enospcError', "Unable to watch for file changes in this large workspace. Please follow the instructions link to resolve this issue."), [{ label: localize('learnMore', "Instructions"), - run: () => window.open('https://go.microsoft.com/fwlink/?linkid=867693') - }, - { - label: localize('neverShowAgain', "Don't Show Again"), - isSecondary: true, - run: () => this.storageService.store('ignoreEnospcError', true, StorageScope.WORKSPACE) + run: () => this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=867693')) }], - { sticky: true } + { + sticky: true, + neverShowAgain: { id: 'ignoreEnospcError', isSecondary: true, scope: NeverShowAgainScope.WORKSPACE } + } ); } } @@ -157,4 +153,4 @@ export class WorkspaceWatcher extends Disposable { } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceWatcher, LifecyclePhase.Restored); \ No newline at end of file +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceWatcher, LifecyclePhase.Restored); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 91e3136b50e..23ad652c71c 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -99,7 +99,7 @@ interface IRecentlyClosedFile { export class HistoryService extends Disposable implements IHistoryService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private static readonly STORAGE_KEY = 'history.entries'; private static readonly MAX_HISTORY_ITEMS = 200; @@ -115,10 +115,10 @@ export class HistoryService extends Disposable implements IHistoryService { private stack: IStackEntry[]; private index: number; private lastIndex: number; - private navigatingInStack: boolean; - private currentTextEditorState: TextEditorState | null; + private navigatingInStack = false; + private currentTextEditorState: TextEditorState | null = null; - private lastEditLocation: IStackEntry; + private lastEditLocation: IStackEntry | undefined; private history: Array; private recentlyClosedFiles: IRecentlyClosedFile[]; @@ -453,7 +453,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.doNavigate(this.stack[this.index], !acrossEditors).finally(() => this.navigatingInStack = false); } - private doNavigate(location: IStackEntry, withSelection: boolean): Promise { + private doNavigate(location: IStackEntry, withSelection: boolean): Promise { const options: ITextEditorOptions = { revealIfOpened: true // support to navigate across editor groups }; diff --git a/src/vs/workbench/services/integrity/browser/integrityService.ts b/src/vs/workbench/services/integrity/browser/integrityService.ts new file mode 100644 index 00000000000..5ddf9d5b3f9 --- /dev/null +++ b/src/vs/workbench/services/integrity/browser/integrityService.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IIntegrityService, IntegrityTestResult } from 'vs/workbench/services/integrity/common/integrity'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +export class BrowserIntegrityServiceImpl implements IIntegrityService { + + _serviceBrand!: ServiceIdentifier; + + async isPure(): Promise { + return { isPure: true, proof: [] }; + } +} + +registerSingleton(IIntegrityService, BrowserIntegrityServiceImpl, true); diff --git a/src/vs/workbench/services/integrity/node/integrityService.ts b/src/vs/workbench/services/integrity/node/integrityService.ts index 00e083a9b99..36c80d8c148 100644 --- a/src/vs/workbench/services/integrity/node/integrityService.ts +++ b/src/vs/workbench/services/integrity/node/integrityService.ts @@ -14,6 +14,8 @@ import product from 'vs/platform/product/node/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; interface IStorageData { dontShowPrompt: boolean; @@ -55,7 +57,7 @@ class IntegrityStorage { export class IntegrityServiceImpl implements IIntegrityService { - _serviceBrand: any; + _serviceBrand!: ServiceIdentifier; private _storage: IntegrityStorage; private _isPurePromise: Promise; @@ -63,7 +65,8 @@ export class IntegrityServiceImpl implements IIntegrityService { constructor( @INotificationService private readonly notificationService: INotificationService, @IStorageService storageService: IStorageService, - @ILifecycleService private readonly lifecycleService: ILifecycleService + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IOpenerService private readonly openerService: IOpenerService ) { this._storage = new IntegrityStorage(storageService); @@ -90,7 +93,7 @@ export class IntegrityServiceImpl implements IIntegrityService { [ { label: nls.localize('integrity.moreInformation', "More Information"), - run: () => window.open(URI.parse(product.checksumFailMoreInfoUrl).toString(true)) + run: () => this.openerService.open(URI.parse(product.checksumFailMoreInfoUrl)) }, { label: nls.localize('integrity.dontShowAgain', "Don't Show Again"), @@ -159,4 +162,4 @@ export class IntegrityServiceImpl implements IIntegrityService { } } -registerSingleton(IIntegrityService, IntegrityServiceImpl, true); \ No newline at end of file +registerSingleton(IIntegrityService, IntegrityServiceImpl, true); diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index dec59cfca2c..b90651c4f33 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -45,7 +45,7 @@ import * as objects from 'vs/base/common/objects'; import { IKeymapService } from 'vs/workbench/services/keybinding/common/keymapInfo'; import { getDispatchConfig } from 'vs/workbench/services/keybinding/common/dispatchConfig'; import { isArray } from 'vs/base/common/types'; -import { INavigatorWithKeyboard } from 'vs/workbench/services/keybinding/common/navigatorKeyboard'; +import { INavigatorWithKeyboard } from 'vs/workbench/services/keybinding/browser/navigatorKeyboard'; import { ScanCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE } from 'vs/base/common/scanCode'; interface ContributedKeyBinding { @@ -725,7 +725,6 @@ const keyboardConfiguration: IConfigurationNode = { 'markdownDescription': nls.localize('dispatch', "Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`."), 'included': OS === OperatingSystem.Macintosh || OS === OperatingSystem.Linux } - // no touch bar support } }; diff --git a/src/vs/workbench/services/keybinding/browser/keymapService.ts b/src/vs/workbench/services/keybinding/browser/keymapService.ts index faacc11fbf5..e3744643efd 100644 --- a/src/vs/workbench/services/keybinding/browser/keymapService.ts +++ b/src/vs/workbench/services/keybinding/browser/keymapService.ts @@ -25,7 +25,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ConfigExtensions, IConfigurationRegistry, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { INavigatorWithKeyboard } from 'vs/workbench/services/keybinding/common/navigatorKeyboard'; +import { INavigatorWithKeyboard } from 'vs/workbench/services/keybinding/browser/navigatorKeyboard'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -176,6 +176,7 @@ export class BrowserKeyboardMapperFactoryBase { } setActiveKeyMapping(keymap: IKeyboardMapping | null) { + let keymapUpdated = false; let matchedKeyboardLayout = this.getMatchedKeymapInfo(keymap); if (matchedKeyboardLayout) { // let score = matchedKeyboardLayout.score; @@ -209,18 +210,21 @@ export class BrowserKeyboardMapperFactoryBase { if (!this._activeKeymapInfo) { this._activeKeymapInfo = matchedKeyboardLayout.result; + keymapUpdated = true; } else if (keymap) { if (matchedKeyboardLayout.result.getScore(keymap) > this._activeKeymapInfo.getScore(keymap)) { this._activeKeymapInfo = matchedKeyboardLayout.result; + keymapUpdated = true; } } } if (!this._activeKeymapInfo) { this._activeKeymapInfo = this.getUSStandardLayout(); + keymapUpdated = true; } - if (!this._activeKeymapInfo) { + if (!this._activeKeymapInfo || !keymapUpdated) { return; } diff --git a/src/vs/workbench/services/keybinding/common/navigatorKeyboard.ts b/src/vs/workbench/services/keybinding/browser/navigatorKeyboard.ts similarity index 100% rename from src/vs/workbench/services/keybinding/common/navigatorKeyboard.ts rename to src/vs/workbench/services/keybinding/browser/navigatorKeyboard.ts diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts b/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts index eec3ac01744..ae95d7b85f4 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybinding.contribution.ts @@ -22,8 +22,17 @@ const keyboardConfiguration: IConfigurationNode = { 'default': true, 'description': nls.localize('touchbar.enabled', "Enables the macOS touchbar buttons on the keyboard if available."), 'included': OS === OperatingSystem.Macintosh && parseFloat(release()) >= 16 // Minimum: macOS Sierra (10.12.x = darwin 16.x) + }, + 'keyboard.touchbar.ignored': { + 'type': 'array', + 'items': { + 'type': 'string' + }, + 'default': [], + 'description': nls.localize('touchbar.ignored', 'A set of identifiers for entries in the touchbar that should not show up (for example `workbench.action.navigateBack`.'), + 'included': OS === OperatingSystem.Macintosh && parseFloat(release()) >= 16 // Minimum: macOS Sierra (10.12.x = darwin 16.x) } } }; -configurationRegistry.registerConfiguration(keyboardConfiguration); \ No newline at end of file +configurationRegistry.registerConfiguration(keyboardConfiguration); diff --git a/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts index cbc20c36579..e6f8b9b4d97 100644 --- a/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/browserKeyboardMapper.test.ts @@ -40,12 +40,12 @@ suite('keyboard layout loader', () => { let commandService = instantiationService.stub(ICommandService, {}); let instance = new TestKeyboardMapperFactory(notitifcationService, storageService, commandService); - test('load default US keyboard layout', () => { + test.skip('load default US keyboard layout', () => { assert.notEqual(instance.activeKeyboardLayout, null); assert.equal(instance.activeKeyboardLayout!.isUSStandard, true); }); - test('isKeyMappingActive', () => { + test.skip('isKeyMappingActive', () => { assert.equal(instance.isKeyMappingActive({ KeyA: { value: 'a', diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 7042c156bfc..a8e2290be22 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -68,7 +68,7 @@ const resourceLabelFormattersExtPoint = ExtensionsRegistry.registerExtensionPoin }); const sepRegexp = /\//g; -const labelMatchingRegexp = /\$\{(scheme|authority|path|(qs)\.(.+?))\}/g; +const labelMatchingRegexp = /\$\{(scheme|authority|path|(query)\.(.+?))\}/g; function hasDriveLetter(path: string): boolean { return !!(isWindows && path && path[2] === ':'); @@ -228,7 +228,7 @@ export class LabelService implements ILabelService { case 'authority': return resource.authority; case 'path': return resource.path; default: { - if (qsToken === 'qs') { + if (qsToken === 'query') { const { query } = resource; if (query && query[0] === '{' && query[query.length - 1] === '}') { try { @@ -276,4 +276,4 @@ export class LabelService implements ILabelService { } } -registerSingleton(ILabelService, LabelService, true); \ No newline at end of file +registerSingleton(ILabelService, LabelService, true); diff --git a/src/vs/workbench/services/label/test/label.test.ts b/src/vs/workbench/services/label/test/label.test.ts index 891c928fc34..ee4650f4d2b 100644 --- a/src/vs/workbench/services/label/test/label.test.ts +++ b/src/vs/workbench/services/label/test/label.test.ts @@ -98,11 +98,11 @@ suite('URI Label', () => { assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'second'); }); - test('custom qs', function () { + test('custom query', function () { labelService.registerFormatter({ scheme: 'vscode', formatting: { - label: 'LABEL${qs.prefix}: ${qs.path}/END', + label: 'LABEL${query.prefix}: ${query.path}/END', separator: '/', tildify: true, normalizeDriveLetter: true @@ -113,11 +113,11 @@ suite('URI Label', () => { assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABELprefix: path/END'); }); - test('custom qs without value', function () { + test('custom query without value', function () { labelService.registerFormatter({ scheme: 'vscode', formatting: { - label: 'LABEL${qs.prefix}: ${qs.path}/END', + label: 'LABEL${query.prefix}: ${query.path}/END', separator: '/', tildify: true, normalizeDriveLetter: true @@ -128,11 +128,11 @@ suite('URI Label', () => { assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: path/END'); }); - test('custom qs without query json', function () { + test('custom query without query json', function () { labelService.registerFormatter({ scheme: 'vscode', formatting: { - label: 'LABEL${qs.prefix}: ${qs.path}/END', + label: 'LABEL${query.prefix}: ${query.path}/END', separator: '/', tildify: true, normalizeDriveLetter: true @@ -143,11 +143,11 @@ suite('URI Label', () => { assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: /END'); }); - test('custom qs without query', function () { + test('custom query without query', function () { labelService.registerFormatter({ scheme: 'vscode', formatting: { - label: 'LABEL${qs.prefix}: ${qs.path}/END', + label: 'LABEL${query.prefix}: ${query.path}/END', separator: '/', tildify: true, normalizeDriveLetter: true diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index 61d28dced34..0813403571a 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -94,7 +94,7 @@ export const languagesExtPoint: IExtensionPoint = export class WorkbenchModeServiceImpl extends ModeServiceImpl { private _configurationService: IConfigurationService; private _extensionService: IExtensionService; - private _onReadyPromise: Promise; + private _onReadyPromise: Promise | undefined; constructor( @IExtensionService extensionService: IExtensionService, @@ -232,4 +232,4 @@ function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collec return true; } -registerSingleton(IModeService, WorkbenchModeServiceImpl); \ No newline at end of file +registerSingleton(IModeService, WorkbenchModeServiceImpl); diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 78280e20a22..e701a079c29 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -3,17 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; +import * as nls from 'vs/nls'; +import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions, NoOpNotification, NeverShowAgainScope } from 'vs/platform/notification/common/notification'; import { INotificationsModel, NotificationsModel, ChoiceAction } from 'vs/workbench/common/notifications'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Action } from 'vs/base/common/actions'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; export class NotificationService extends Disposable implements INotificationService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private _model: INotificationsModel = this._register(new NotificationsModel()); @@ -21,6 +23,10 @@ export class NotificationService extends Disposable implements INotificationServ return this._model; } + constructor(@IStorageService private readonly storageService: IStorageService) { + super(); + } + info(message: NotificationMessage | NotificationMessage[]): void { if (Array.isArray(message)) { message.forEach(m => this.info(m)); @@ -52,12 +58,82 @@ export class NotificationService extends Disposable implements INotificationServ } notify(notification: INotification): INotificationHandle { - return this.model.addNotification(notification); + const toDispose = new DisposableStore(); + + // Handle neverShowAgain option accordingly + let handle: INotificationHandle; + if (notification.neverShowAgain) { + const scope = notification.neverShowAgain.scope === NeverShowAgainScope.WORKSPACE ? StorageScope.WORKSPACE : StorageScope.GLOBAL; + + // If the user already picked to not show the notification + // again, we return with a no-op notification here + const id = notification.neverShowAgain.id; + if (this.storageService.getBoolean(id, scope)) { + return new NoOpNotification(); + } + + const neverShowAgainAction = toDispose.add(new Action( + 'workbench.notification.neverShowAgain', + nls.localize('neverShowAgain', "Don't Show Again"), + undefined, true, () => { + + // Close notification + handle.close(); + + // Remember choice + this.storageService.store(id, true, scope); + + return Promise.resolve(); + })); + + // Insert as primary or secondary action + const actions = notification.actions || { primary: [], secondary: [] }; + if (!notification.neverShowAgain.isSecondary) { + actions.primary = [neverShowAgainAction, ...(actions.primary || [])]; // action comes first + } else { + actions.secondary = [...(actions.secondary || []), neverShowAgainAction]; // actions comes last + } + + notification.actions = actions; + } + + // Show notification + handle = this.model.addNotification(notification); + + // Cleanup when notification gets disposed + Event.once(handle.onDidClose)(() => toDispose.dispose()); + + return handle; } prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { const toDispose = new DisposableStore(); + // Handle neverShowAgain option accordingly + if (options && options.neverShowAgain) { + const scope = options.neverShowAgain.scope === NeverShowAgainScope.WORKSPACE ? StorageScope.WORKSPACE : StorageScope.GLOBAL; + + // If the user already picked to not show the notification + // again, we return with a no-op notification here + const id = options.neverShowAgain.id; + if (this.storageService.getBoolean(id, scope)) { + return new NoOpNotification(); + } + + const neverShowAgainChoice = { + label: nls.localize('neverShowAgain', "Don't Show Again"), + run: () => this.storageService.store(id, true, scope), + isSecondary: options.neverShowAgain.isSecondary + }; + + // Insert as primary or secondary action + if (!options.neverShowAgain.isSecondary) { + choices = [neverShowAgainChoice, ...choices]; // action comes first + } else { + choices = [...choices, neverShowAgainChoice]; // actions comes last + } + } + let choiceClicked = false; let handle: INotificationHandle; @@ -108,4 +184,4 @@ export class NotificationService extends Disposable implements INotificationServ } } -registerSingleton(INotificationService, NotificationService, true); \ No newline at end of file +registerSingleton(INotificationService, NotificationService, true); diff --git a/src/vs/workbench/services/output/common/outputChannelModel.ts b/src/vs/workbench/services/output/common/outputChannelModel.ts index be87bf1aecc..f92624f44e8 100644 --- a/src/vs/workbench/services/output/common/outputChannelModel.ts +++ b/src/vs/workbench/services/output/common/outputChannelModel.ts @@ -58,7 +58,7 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen readonly onDispose: Event = this._onDispose.event; protected modelUpdater: RunOnceScheduler; - protected model: ITextModel | null; + protected model: ITextModel | null = null; protected startOffset: number = 0; protected endOffset: number = 0; @@ -288,7 +288,7 @@ export class BufferredOutputChannel extends Disposable implements IOutputChannel readonly onDispose: Event = this._onDispose.event; private modelUpdater: RunOnceScheduler; - private model: ITextModel | null; + private model: ITextModel | null = null; private readonly bufferredContent: BufferedContent; private lastReadId: number | undefined = undefined; @@ -414,4 +414,4 @@ class BufferedContent { return { value, id }; } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/output/node/outputChannelModelService.ts b/src/vs/workbench/services/output/node/outputChannelModelService.ts index da44834ef94..e841597dcac 100644 --- a/src/vs/workbench/services/output/node/outputChannelModelService.ts +++ b/src/vs/workbench/services/output/node/outputChannelModelService.ts @@ -213,7 +213,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService this.instantiationService.createInstance(DelegatedOutputChannelModel, id, modelUri, mimeType, this.outputDir); } - private _outputDir: Promise | null; + private _outputDir: Promise | null = null; private get outputDir(): Promise { if (!this._outputDir) { const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.environmentService.configuration.windowId}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`)); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 8788a416741..1e579f2968b 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -38,6 +38,7 @@ import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultSetti import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { withNullAsUndefined } from 'vs/base/common/types'; const emptyEditableSettingsContent = '{\n}'; @@ -188,15 +189,15 @@ export class PreferencesService extends Disposable implements IPreferencesServic return null; } - openRawDefaultSettings(): Promise { + openRawDefaultSettings(): Promise { return this.editorService.openEditor({ resource: this.defaultSettingsRawResource }); } - openRawUserSettings(): Promise { + openRawUserSettings(): Promise { return this.editorService.openEditor({ resource: this.userSettingsResource }); } - openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise { + openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -217,7 +218,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic .then(() => this.editorGroupService.activeGroup.activeControl!); } - openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -227,16 +228,16 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.openOrSwitchSettings2(ConfigurationTarget.USER_LOCAL, undefined, options, group); } - async openRemoteSettings(): Promise { + async openRemoteSettings(): Promise { const environment = await this.remoteAgentService.getEnvironment(); if (environment) { await this.createIfNotExists(environment.settingsPath, emptyEditableSettingsContent); return this.editorService.openEditor({ resource: environment.settingsPath, options: { pinned: true, revealIfOpened: true } }); } - return null; + return undefined; } - openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -251,7 +252,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE, undefined, options, group); } - async openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + async openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -306,7 +307,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { pinned: true, revealIfOpened: true }).then(() => undefined); } - openDefaultKeybindingsFile(): Promise { + openDefaultKeybindingsFile(): Promise { return this.editorService.openEditor({ resource: this.defaultKeybindingsResource, label: nls.localize('defaultKeybindings', "Default Keybindings") }); } @@ -328,7 +329,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic })); } - private openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + private openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { const editorInput = this.getActiveSettingsEditorInput(group); if (editorInput) { const editorInputResource = editorInput.master.getResource(); @@ -339,11 +340,11 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.doOpenSettings(configurationTarget, resource, options, group); } - private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { return this.doOpenSettings2(configurationTarget, folderUri, options, group); } - private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { const openSplitJSON = !!this.configurationService.getValue(USE_SPLIT_JSON_SETTING); if (openSplitJSON) { return this.doOpenSplitJSON(configurationTarget, resource, options, group); @@ -365,14 +366,14 @@ export class PreferencesService extends Disposable implements IPreferencesServic return Promise.all([ this.editorService.openEditor({ resource: this.defaultSettingsRawResource, options: { pinned: true, preserveFocus: true, revealIfOpened: true }, label: nls.localize('defaultSettings', "Default Settings"), description: '' }), this.editorService.openEditor(editableSettingsEditorInput, { pinned: true, revealIfOpened: true }, sideEditorGroup.id) - ]).then(([defaultEditor, editor]) => editor); + ]).then(([defaultEditor, editor]) => withNullAsUndefined(editor)); } else { return this.editorService.openEditor(editableSettingsEditorInput, SettingsEditorOptions.create(options), group); } }); } - private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource) .then(editableSettingsEditorInput => { if (!options) { @@ -392,7 +393,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.instantiationService.createInstance(Settings2EditorModel, this.getDefaultSettings(ConfigurationTarget.USER_LOCAL)); } - private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { const input = this.settingsEditor2Input; const settingsOptions: ISettingsEditorOptions = { ...options, @@ -633,4 +634,4 @@ export class PreferencesService extends Disposable implements IPreferencesServic } } -registerSingleton(IPreferencesService, PreferencesService); \ No newline at end of file +registerSingleton(IPreferencesService, PreferencesService); diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index abf288178cc..195631aa108 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -201,15 +201,15 @@ export interface IPreferencesService { createPreferencesEditorModel(uri: URI): Promise | null>; createSettings2EditorModel(): Settings2EditorModel; // TODO - openRawDefaultSettings(): Promise; - openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise; - openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - openRemoteSettings(): Promise; - openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openRawDefaultSettings(): Promise; + openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise; + openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openRemoteSettings(): Promise; + openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise; openGlobalKeybindingSettings(textual: boolean): Promise; - openDefaultKeybindingsFile(): Promise; + openDefaultKeybindingsFile(): Promise; configureSettingsForLanguage(language: string | null): void; } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index d24d2a53f68..3e252282711 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -23,6 +23,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorModel } from 'vs/workbench/common/editor'; import { IFilterMetadata, IFilterResult, IGroupFilter, IKeybindingsEditorModel, ISearchResultGroup, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; import { withNullAsUndefined, isArray } from 'vs/base/common/types'; +import { FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; export const nullRange: IRange = { startLineNumber: -1, startColumn: -1, endLineNumber: -1, endColumn: -1 }; export function isNullRange(range: IRange): boolean { return range.startLineNumber === -1 && range.startColumn === -1 && range.endLineNumber === -1 && range.endColumn === -1; } @@ -659,11 +660,14 @@ export class DefaultSettings extends Disposable { } private matchesScope(property: IConfigurationNode): boolean { + if (!property.scope) { + return true; + } if (this.target === ConfigurationTarget.WORKSPACE_FOLDER) { - return property.scope === ConfigurationScope.RESOURCE; + return FOLDER_SCOPES.indexOf(property.scope) !== -1; } if (this.target === ConfigurationTarget.WORKSPACE) { - return property.scope === ConfigurationScope.WINDOW || property.scope === ConfigurationScope.RESOURCE; + return WORKSPACE_SCOPES.indexOf(property.scope) !== -1; } return true; } diff --git a/src/vs/workbench/services/progress/browser/editorProgressService.ts b/src/vs/workbench/services/progress/browser/editorProgressService.ts index 7ea3daae18e..73ecabab26a 100644 --- a/src/vs/workbench/services/progress/browser/editorProgressService.ts +++ b/src/vs/workbench/services/progress/browser/editorProgressService.ts @@ -9,5 +9,5 @@ import { ProgressBarIndicator } from 'vs/workbench/services/progress/browser/pro export class EditorProgressService extends ProgressBarIndicator { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; } diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index d7bb38745a1..465920cd05d 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -28,7 +28,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; export class ProgressService extends Disposable implements IProgressService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly stack: [IProgressOptions, Progress][] = []; private readonly globalStatusEntry = this._register(new MutableDisposable()); diff --git a/src/vs/workbench/services/progress/test/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/progressIndicator.test.ts index a8d2b740d0b..b548da65af9 100644 --- a/src/vs/workbench/services/progress/test/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/progressIndicator.test.ts @@ -28,7 +28,7 @@ class TestViewlet implements IViewlet { } class TestCompositeScope extends CompositeScope { - isActive: boolean; + isActive: boolean = false; constructor(viewletService: IViewletService, panelService: IPanelService, scopeId: string) { super(viewletService, panelService, scopeId); @@ -39,10 +39,10 @@ class TestCompositeScope extends CompositeScope { } class TestProgressBar { - fTotal: number; - fWorked: number; - fInfinite: boolean; - fDone: boolean; + fTotal: number = 0; + fWorked: number = 0; + fInfinite: boolean = false; + fDone: boolean = false; constructor() { } diff --git a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts index 4219ca53901..fd979ca094c 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts @@ -28,7 +28,7 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR super(environmentService); this.socketFactory = new BrowserSocketFactory(webSocketFactory); - this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority!, productService.commit, this.socketFactory, environmentService, remoteAuthorityResolverService, signService)); + this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority!, productService.commit, this.socketFactory, remoteAuthorityResolverService, signService)); } getConnection(): IRemoteAgentConnection | null { diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index e48834a6a6b..167e7a5bf14 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -17,7 +17,7 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } f import { Registry } from 'vs/platform/registry/common/platform'; import { RemoteExtensionEnvironmentChannelClient } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { Emitter } from 'vs/base/common/event'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -31,6 +31,7 @@ export abstract class AbstractRemoteAgentService extends Disposable { @IEnvironmentService protected readonly _environmentService: IEnvironmentService ) { super(); + this._environment = null; } abstract getConnection(): IRemoteAgentConnection | null; @@ -84,7 +85,6 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon remoteAuthority: string, private readonly _commit: string | undefined, private readonly _socketFactory: ISocketFactory, - private readonly _environmentService: IEnvironmentService, private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, private readonly _signService: ISignService ) { @@ -111,7 +111,6 @@ export class RemoteAgentConnection extends Disposable implements IRemoteAgentCon private async _createConnection(): Promise> { let firstCall = true; const options: IConnectionOptions = { - isBuilt: this._environmentService.isBuilt, commit: this._commit, socketFactory: this._socketFactory, addressProvider: { diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index ae87e384bb4..fe15f2b644e 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -8,7 +8,8 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; -import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; +import { RemoteAuthorities } from 'vs/base/common/network'; export interface IGetEnvironmentDataArguments { language: string; @@ -18,6 +19,7 @@ export interface IGetEnvironmentDataArguments { export interface IRemoteAgentEnvironmentDTO { pid: number; + connectionToken: string; appRoot: UriComponents; appSettingsHome: UriComponents; settingsPath: UriComponents; @@ -43,8 +45,11 @@ export class RemoteExtensionEnvironmentChannelClient { const data = await this.channel.call('getEnvironmentData', args); + RemoteAuthorities.setConnectionToken(remoteAuthority, data.connectionToken); + return { pid: data.pid, + connectionToken: data.connectionToken, appRoot: URI.revive(data.appRoot), appSettingsHome: URI.revive(data.appSettingsHome), settingsPath: URI.revive(data.settingsPath), diff --git a/src/vs/workbench/services/remote/common/remoteAgentService.ts b/src/vs/workbench/services/remote/common/remoteAgentService.ts index bc4d49641e1..fdd61483fd5 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentService.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { Event } from 'vs/base/common/event'; import { PersistenConnectionEvent as PersistentConnectionEvent, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; diff --git a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts index 538fb1be860..82d039a6c54 100644 --- a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts @@ -27,7 +27,7 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR super(environmentService); this.socketFactory = nodeSocketFactory; if (remoteAuthority) { - this._connection = this._register(new RemoteAgentConnection(remoteAuthority, product.commit, nodeSocketFactory, environmentService, remoteAuthorityResolverService, signService)); + this._connection = this._register(new RemoteAgentConnection(remoteAuthority, product.commit, nodeSocketFactory, remoteAuthorityResolverService, signService)); } } diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 9a359e44c28..457411cd670 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -14,6 +14,7 @@ import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remot import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { ISignService } from 'vs/platform/sign/common/sign'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise { const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort); @@ -100,7 +101,6 @@ export class TunnelService implements ITunnelService { } const options: IConnectionOptions = { - isBuilt: this.environmentService.isBuilt, commit: product.commit, socketFactory: nodeSocketFactory, addressProvider: { @@ -114,3 +114,5 @@ export class TunnelService implements ITunnelService { return createRemoteTunnel(options, remotePort); } } + +registerSingleton(ITunnelService, TunnelService, true); diff --git a/src/vs/workbench/services/request/browser/requestService.ts b/src/vs/workbench/services/request/browser/requestService.ts index dca97347c35..cebf1654eca 100644 --- a/src/vs/workbench/services/request/browser/requestService.ts +++ b/src/vs/workbench/services/request/browser/requestService.ts @@ -16,7 +16,6 @@ export class RequestService extends BrowserRequestService { private readonly remoteRequestChannel: RequestChannelClient | null; constructor( - private readonly requestHandler: ((options: IRequestOptions) => Promise) | undefined, @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IConfigurationService configurationService: IConfigurationService, @ILogService logService: ILogService @@ -27,9 +26,6 @@ export class RequestService extends BrowserRequestService { } async request(options: IRequestOptions, token: CancellationToken): Promise { - if (this.requestHandler) { - return this.requestHandler(options); - } try { const context = await super.request(options, token); if (this.remoteRequestChannel && context.res.statusCode === 405) { @@ -45,4 +41,4 @@ export class RequestService extends BrowserRequestService { } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/search/common/replace.ts b/src/vs/workbench/services/search/common/replace.ts index a6caab8223e..99f610bbcf5 100644 --- a/src/vs/workbench/services/search/common/replace.ts +++ b/src/vs/workbench/services/search/common/replace.ts @@ -6,6 +6,7 @@ import * as strings from 'vs/base/common/strings'; import { IPatternInfo } from 'vs/workbench/services/search/common/search'; import { CharCode } from 'vs/base/common/charCode'; +import { buildReplaceStringWithCasePreserved } from 'vs/base/common/search'; export class ReplacePattern { @@ -54,7 +55,7 @@ export class ReplacePattern { * Returns the replace string for the first match in the given text. * If text has no matches then returns null. */ - getReplaceString(text: string): string | null { + getReplaceString(text: string, preserveCase?: boolean): string | null { this._regExp.lastIndex = 0; let match = this._regExp.exec(text); if (match) { @@ -65,12 +66,20 @@ export class ReplacePattern { let replaceString = text.replace(this._regExp, this.pattern); return replaceString.substr(match.index, match[0].length - (text.length - replaceString.length)); } - return this.pattern; + return this.buildReplaceString(match, preserveCase); } return null; } + public buildReplaceString(matches: string[] | null, preserveCase?: boolean): string { + if (preserveCase) { + return buildReplaceStringWithCasePreserved(matches, this._replacePattern); + } else { + return this._replacePattern; + } + } + /** * \n => LF * \t => TAB diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index f4e4e1ae243..da4e3edb333 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -251,20 +251,23 @@ export class TextSearchMatch implements ITextSearchMatch { constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions) { this.ranges = range; - if (previewOptions && previewOptions.matchLines === 1 && !Array.isArray(range)) { + if (previewOptions && previewOptions.matchLines === 1 && (!Array.isArray(range) || range.length === 1)) { + const oneRange = Array.isArray(range) ? range[0] : range; + // 1 line preview requested text = getNLines(text, previewOptions.matchLines); const leadingChars = Math.floor(previewOptions.charsPerLine / 5); - const previewStart = Math.max(range.startColumn - leadingChars, 0); + const previewStart = Math.max(oneRange.startColumn - leadingChars, 0); const previewText = text.substring(previewStart, previewOptions.charsPerLine + previewStart); - const endColInPreview = (range.endLineNumber - range.startLineNumber + 1) <= previewOptions.matchLines ? - Math.min(previewText.length, range.endColumn - previewStart) : // if number of match lines will not be trimmed by previewOptions + const endColInPreview = (oneRange.endLineNumber - oneRange.startLineNumber + 1) <= previewOptions.matchLines ? + Math.min(previewText.length, oneRange.endColumn - previewStart) : // if number of match lines will not be trimmed by previewOptions previewText.length; // if number of lines is trimmed + const oneLineRange = new OneLineRange(0, oneRange.startColumn - previewStart, endColInPreview); this.preview = { text: previewText, - matches: new OneLineRange(0, range.startColumn - previewStart, endColInPreview) + matches: Array.isArray(range) ? [oneLineRange] : oneLineRange }; } else { const firstMatchLine = Array.isArray(range) ? range[0].startLineNumber : range.startLineNumber; diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 53a81175157..8325ceb8317 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -21,9 +21,11 @@ import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFil import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class SearchService extends Disposable implements ISearchService { - _serviceBrand: any; + + _serviceBrand!: ServiceIdentifier; protected diskSearch: ISearchResultProvider; private readonly fileSearchProviders = new Map(); diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index 652aaf3a2de..eac9dcfb1d0 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -15,67 +15,10 @@ import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/pla import { IStorageService } from 'vs/platform/storage/common/storage'; import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/browser/workbenchCommonProperties'; import { IProductService } from 'vs/platform/product/common/product'; - -interface IConfig { - instrumentationKey?: string; - endpointUrl?: string; - emitLineDelimitedJson?: boolean; - accountId?: string; - sessionRenewalMs?: number; - sessionExpirationMs?: number; - maxBatchSizeInBytes?: number; - maxBatchInterval?: number; - enableDebug?: boolean; - disableExceptionTracking?: boolean; - disableTelemetry?: boolean; - verboseLogging?: boolean; - diagnosticLogInterval?: number; - samplingPercentage?: number; - autoTrackPageVisitTime?: boolean; - disableAjaxTracking?: boolean; - overridePageViewDuration?: boolean; - maxAjaxCallsPerView?: number; - disableDataLossAnalysis?: boolean; - disableCorrelationHeaders?: boolean; - correlationHeaderExcludedDomains?: string[]; - disableFlushOnBeforeUnload?: boolean; - enableSessionStorageBuffer?: boolean; - isCookieUseDisabled?: boolean; - cookieDomain?: string; - isRetryDisabled?: boolean; - url?: string; - isStorageUseDisabled?: boolean; - isBeaconApiDisabled?: boolean; - sdkExtension?: string; - isBrowserLinkTrackingEnabled?: boolean; - appId?: string; - enableCorsCorrelation?: boolean; -} - -declare class Microsoft { - public static ApplicationInsights: { - Initialization: { - new(init: { config: IConfig }): AppInsights; - } - }; -} - -declare interface IAppInsightsClient { - config: IConfig; - - /** Log a user action or other occurrence. */ - trackEvent: (name: string, properties?: { [key: string]: string }, measurements?: { [key: string]: number }) => void; - - /** Immediately send all queued telemetry. Synchronous. */ - flush(): void; -} - -interface AppInsights { - loadAppInsights: () => IAppInsightsClient; -} +import { ApplicationInsights } from '@microsoft/applicationinsights-web'; export class WebTelemetryAppender implements ITelemetryAppender { - private _aiClient?: IAppInsightsClient; + private _aiClient?: ApplicationInsights; constructor(aiKey: string, private _logService: ILogService) { const initConfig = { @@ -89,8 +32,8 @@ export class WebTelemetryAppender implements ITelemetryAppender { } }; - const appInsights = new Microsoft.ApplicationInsights.Initialization(initConfig); - this._aiClient = appInsights.loadAppInsights(); + this._aiClient = new ApplicationInsights(initConfig); + this._aiClient.loadAppInsights(); } log(eventName: string, data: any): void { @@ -101,7 +44,11 @@ export class WebTelemetryAppender implements ITelemetryAppender { data = validateTelemetryData(data); this._logService.trace(`telemetry/${eventName}`, data); - this._aiClient.trackEvent('monacoworkbench/' + eventName, data.properties, data.measurements); + this._aiClient.trackEvent({ + name: 'monacoworkbench/' + eventName, + properties: data.properties, + measurements: data.measurements + }); } flush(): Promise { @@ -167,4 +114,4 @@ export class TelemetryService extends Disposable implements ITelemetryService { } } -registerSingleton(ITelemetryService, TelemetryService); \ No newline at end of file +registerSingleton(ITelemetryService, TelemetryService); diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 6eaaf220ce5..c5561e44295 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -69,4 +69,4 @@ export class TelemetryService extends Disposable implements ITelemetryService { } } -registerSingleton(ITelemetryService, TelemetryService); \ No newline at end of file +registerSingleton(ITelemetryService, TelemetryService); diff --git a/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts b/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts index 4c352da67a4..4c6d3c5fdb7 100644 --- a/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts +++ b/src/vs/workbench/services/textMate/common/TMScopeRegistry.ts @@ -31,7 +31,7 @@ export class TMScopeRegistry extends Disposable { constructor() { super(); - this.reset(); + this._scopeNameToLanguageRegistration = Object.create(null); } public reset(): void { diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts b/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts index a5c24f1d7f7..6f78679f1fd 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateWorker.ts @@ -80,7 +80,7 @@ class TextMateWorkerModel extends MirrorTextModel { const languageId = this._languageId; this._worker.getOrCreateGrammar(languageId).then((r) => { - if (this._isDisposed || languageId !== this._languageId) { + if (this._isDisposed || languageId !== this._languageId || !r) { return; } @@ -118,7 +118,7 @@ export class TextMateWorker { private readonly _host: TextMateWorkerHost; private readonly _models: { [uri: string]: TextMateWorkerModel; }; private readonly _grammarCache: Promise[]; - private readonly _grammarFactory: TMGrammarFactory; + private readonly _grammarFactory: TMGrammarFactory | null; constructor(ctx: IWorkerContext, createData: ICreateData) { this._host = ctx.host; @@ -135,23 +135,23 @@ export class TextMateWorker { }; }); - let vscodeTextmate: typeof import('vscode-textmate'); const globalDefine = (self).define; try { (self).define.amd = undefined; - vscodeTextmate = require.__$__nodeRequire('vscode-textmate'); + const vscodeTextmate = require.__$__nodeRequire('vscode-textmate'); + + this._grammarFactory = new TMGrammarFactory({ + logTrace: (msg: string) => {/* console.log(msg) */ }, + logError: (msg: string, err: any) => console.error(msg, err), + readFile: (resource: URI) => this._host.readFile(resource) + }, grammarDefinitions, vscodeTextmate, undefined); } catch (err) { console.error(err); + this._grammarFactory = null; return; } finally { (self).define = globalDefine; } - - this._grammarFactory = new TMGrammarFactory({ - logTrace: (msg: string) => {/* console.log(msg) */ }, - logError: (msg: string, err: any) => console.error(msg, err), - readFile: (resource: URI) => this._host.readFile(resource) - }, grammarDefinitions, vscodeTextmate, undefined); } public acceptNewModel(data: IRawModelData): void { @@ -175,7 +175,10 @@ export class TextMateWorker { } } - public getOrCreateGrammar(languageId: LanguageId): Promise { + public getOrCreateGrammar(languageId: LanguageId): Promise { + if (!this._grammarFactory) { + return Promise.resolve(null); + } if (!this._grammarCache[languageId]) { this._grammarCache[languageId] = this._grammarFactory.createGrammar(languageId); } @@ -183,7 +186,9 @@ export class TextMateWorker { } public acceptTheme(theme: IRawTheme): void { - this._grammarFactory.setTheme(theme); + if (this._grammarFactory) { + this._grammarFactory.setTheme(theme); + } } public _setTokens(resource: URI, versionId: number, tokens: Uint8Array): void { diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 9f0660e400c..3d2d91f5996 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -17,15 +17,15 @@ export class BrowserTextFileService extends TextFileService { } }; - protected beforeShutdown(reason: ShutdownReason): boolean { + protected onBeforeShutdown(reason: ShutdownReason): boolean { // Web: we cannot perform long running in the shutdown phase // As such we need to check sync if there are any dirty files // that have not been backed up yet and then prevent the shutdown // if that is the case. - return this.doBeforeShutdownSync(reason); + return this.doBeforeShutdownSync(); } - private doBeforeShutdownSync(reason: ShutdownReason): boolean { + private doBeforeShutdownSync(): boolean { const dirtyResources = this.getDirty(); if (!dirtyResources.length) { return false; // no dirty: no veto @@ -46,6 +46,7 @@ export class BrowserTextFileService extends TextFileService { } if (!hasBackup) { + console.warn('Unload prevented: pending backups'); return true; // dirty without backup: veto } } @@ -54,4 +55,4 @@ export class BrowserTextFileService extends TextFileService { } } -registerSingleton(ITextFileService, BrowserTextFileService); \ No newline at end of file +registerSingleton(ITextFileService, BrowserTextFileService); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 819e2a1fc08..4fa61e19c83 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -308,7 +308,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil try { return await this.loadFromBackup(backup, options); } catch (error) { - // ignore error and continue to load as file below + this.logService.error(error); // ignore error and continue to load as file below } } } @@ -470,7 +470,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // We also want to trigger auto save if it is enabled to simulate the exact same behaviour // you would get if manually making the model dirty (fixes https://github.com/Microsoft/vscode/issues/16977) if (fromBackup) { - this.makeDirty(); + this.doMakeDirty(); if (this.autoSaveAfterMilliesEnabled) { this.doAutoSave(this.versionId); } @@ -549,7 +549,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.logService.trace('onModelContentChanged() - model content changed and marked as dirty', this.resource); // Mark as dirty - this.makeDirty(); + this.doMakeDirty(); // Start auto save process unless we are in conflict resolution mode and unless it is disabled if (this.autoSaveAfterMilliesEnabled) { @@ -564,7 +564,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.contentChangeEventScheduler.schedule(); } - private makeDirty(): void { + makeDirty(): void { + if (!this.isResolved()) { + return; // only resolved models can be marked dirty + } + + this.doMakeDirty(); + } + + private doMakeDirty(): void { // Track dirty state and version id const wasDirty = this.dirty; @@ -913,7 +921,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil TextFileEditorModel.saveErrorHandler.onSaveError(error, this); } - isDirty(): boolean { + isDirty(): this is IResolvedTextFileEditorModel { return this.dirty; } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 8afe2d46768..4de574861a0 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -38,46 +38,16 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private readonly _onModelOrphanedChanged: Emitter = this._register(new Emitter()); readonly onModelOrphanedChanged: Event = this._onModelOrphanedChanged.event; - private _onModelsDirtyEvent: Event; - private _onModelsSaveError: Event; - private _onModelsSaved: Event; - private _onModelsReverted: Event; - - private mapResourceToDisposeListener: ResourceMap; - private mapResourceToStateChangeListener: ResourceMap; - private mapResourceToModelContentChangeListener: ResourceMap; - private mapResourceToModel: ResourceMap; - private mapResourceToPendingModelLoaders: ResourceMap>; - - constructor( - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(); - - this.mapResourceToModel = new ResourceMap(); - this.mapResourceToDisposeListener = new ResourceMap(); - this.mapResourceToStateChangeListener = new ResourceMap(); - this.mapResourceToModelContentChangeListener = new ResourceMap(); - this.mapResourceToPendingModelLoaders = new ResourceMap>(); - - this.registerListeners(); - } - - private registerListeners(): void { - - // Lifecycle - this.lifecycleService.onShutdown(this.dispose, this); - } - + private _onModelsDirty!: Event; get onModelsDirty(): Event { - if (!this._onModelsDirtyEvent) { - this._onModelsDirtyEvent = this.debounce(this.onModelDirty); + if (!this._onModelsDirty) { + this._onModelsDirty = this.debounce(this.onModelDirty); } - return this._onModelsDirtyEvent; + return this._onModelsDirty; } + private _onModelsSaveError!: Event; get onModelsSaveError(): Event { if (!this._onModelsSaveError) { this._onModelsSaveError = this.debounce(this.onModelSaveError); @@ -86,6 +56,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaveError; } + private _onModelsSaved!: Event; get onModelsSaved(): Event { if (!this._onModelsSaved) { this._onModelsSaved = this.debounce(this.onModelSaved); @@ -94,6 +65,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaved; } + private _onModelsReverted!: Event; get onModelsReverted(): Event { if (!this._onModelsReverted) { this._onModelsReverted = this.debounce(this.onModelReverted); @@ -102,6 +74,27 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsReverted; } + private mapResourceToDisposeListener = new ResourceMap(); + private mapResourceToStateChangeListener = new ResourceMap(); + private mapResourceToModelContentChangeListener = new ResourceMap(); + private mapResourceToModel = new ResourceMap(); + private mapResourceToPendingModelLoaders = new ResourceMap>(); + + constructor( + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Lifecycle + this.lifecycleService.onShutdown(this.dispose, this); + } + private debounce(event: Event): Event { return Event.debounce(event, (prev: TextFileModelChangeEvent[], cur: TextFileModelChangeEvent) => { if (!prev) { @@ -327,4 +320,4 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this.clear(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 33870009b83..f9147e602e8 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -46,7 +46,7 @@ import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; */ export abstract class TextFileService extends Disposable implements ITextFileService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private readonly _onAutoSaveConfigurationChange: Emitter = this._register(new Emitter()); readonly onAutoSaveConfigurationChange: Event = this._onAutoSaveConfigurationChange.event; @@ -64,9 +64,9 @@ export abstract class TextFileService extends Disposable implements ITextFileSer private currentFilesAssociationConfig: { [key: string]: string; }; private configuredAutoSaveDelay?: number; - private configuredAutoSaveOnFocusChange: boolean; - private configuredAutoSaveOnWindowChange: boolean; - private configuredHotExit: string; + private configuredAutoSaveOnFocusChange: boolean | undefined; + private configuredAutoSaveOnWindowChange: boolean | undefined; + private configuredHotExit: string | undefined; private autoSaveContext: IContextKey; constructor( @@ -107,7 +107,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer private registerListeners(): void { // Lifecycle - this.lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(event.reason))); + this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); this.lifecycleService.onShutdown(this.dispose, this); // Files configuration changes @@ -118,7 +118,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer })); } - protected beforeShutdown(reason: ShutdownReason): boolean | Promise { + protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { // Dirty files need treatment on shutdown const dirty = this.getDirty(); @@ -443,9 +443,94 @@ export abstract class TextFileService extends Disposable implements ITextFileSer } async move(source: URI, target: URI, overwrite?: boolean): Promise { + + // await onWillMove event joiners + await this.notifyOnWillMove(source, target); + + // find all models that related to either source or target (can be many if resource is a folder) + const sourceModels: ITextFileEditorModel[] = []; + const conflictingModels: ITextFileEditorModel[] = []; + for (const model of this.getFileModels()) { + const resource = model.getResource(); + + if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { + conflictingModels.push(model); + } + + if (isEqualOrParent(resource, source)) { + sourceModels.push(model); + } + } + + // remember each source model to load again after move is done + // with optional content to restore if it was dirty + type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot }; + const modelsToRestore: ModelToRestore[] = []; + for (const sourceModel of sourceModels) { + const sourceModelResource = sourceModel.getResource(); + + // If the source is the actual model, just use target as new resource + let modelToRestoreResource: URI; + if (isEqual(sourceModelResource, source)) { + modelToRestoreResource = target; + } + + // Otherwise a parent folder of the source is being moved, so we need + // to compute the target resource based on that + else { + modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); + } + + const modelToRestore: ModelToRestore = { resource: modelToRestoreResource }; + if (sourceModel.isDirty()) { + modelToRestore.snapshot = sourceModel.createSnapshot(); + } + + modelsToRestore.push(modelToRestore); + } + + // in order to move, we need to soft revert all dirty models, + // both from the source as well as the target if any + const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); + await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.getResource()), { soft: true }); + + // now we can rename the source to target via file operation + let stat: IFileStatWithMetadata; + try { + stat = await this.fileService.move(source, target, overwrite); + } catch (error) { + + // in case of any error, ensure to set dirty flag back + dirtyModels.forEach(dirtyModel => dirtyModel.makeDirty()); + + throw error; + } + + // finally, restore models that we had loaded previously + await Promise.all(modelsToRestore.map(async modelToRestore => { + + // restore the model, forcing a reload. this is important because + // we know the file has changed on disk after the move and the + // model might have still existed with the previous state. this + // ensures we are not tracking a stale state. + const restoredModel = await this.models.loadOrCreate(modelToRestore.resource, { reload: { async: false } }); + + // restore previous dirty content if any and ensure to mark + // the model as dirty + if (modelToRestore.snapshot && restoredModel.isResolved()) { + this.modelService.updateModel(restoredModel.textEditorModel, createTextBufferFactoryFromSnapshot(modelToRestore.snapshot)); + + restoredModel.makeDirty(); + } + })); + + return stat; + } + + private async notifyOnWillMove(source: URI, target: URI): Promise { const waitForPromises: Promise[] = []; - // Event + // fire event this._onWillMove.fire({ oldResource: source, newResource: target, @@ -458,58 +543,6 @@ export abstract class TextFileService extends Disposable implements ITextFileSer Object.freeze(waitForPromises); await Promise.all(waitForPromises); - - // Handle target models if existing (if target URI is a folder, this can be multiple) - const dirtyTargetModels = this.getDirtyFileModels().filter(model => isEqualOrParent(model.getResource(), target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)); - if (dirtyTargetModels.length) { - await this.revertAll(dirtyTargetModels.map(targetModel => targetModel.getResource()), { soft: true }); - } - - // Handle dirty source models if existing (if source URI is a folder, this can be multiple) - const dirtySourceModels = this.getDirtyFileModels().filter(model => isEqualOrParent(model.getResource(), source)); - const dirtyTargetModelUris: URI[] = []; - if (dirtySourceModels.length) { - await Promise.all(dirtySourceModels.map(async sourceModel => { - const sourceModelResource = sourceModel.getResource(); - let targetModelResource: URI; - - // If the source is the actual model, just use target as new resource - if (isEqual(sourceModelResource, source)) { - targetModelResource = target; - } - - // 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: joinPath(target, sourceModelResource.path.substr(source.path.length + 1)).path }); - } - - // Remember as dirty target model to load after the operation - dirtyTargetModelUris.push(targetModelResource); - - // Backup dirty source model to the target resource it will become later - await sourceModel.backup(targetModelResource); - })); - } - - // Soft revert the dirty source files if any - await this.revertAll(dirtySourceModels.map(dirtySourceModel => dirtySourceModel.getResource()), { soft: true }); - - // Rename to target - try { - const stat = await this.fileService.move(source, target, overwrite); - - // Load models that were dirty before - await Promise.all(dirtyTargetModelUris.map(dirtyTargetModel => this.models.loadOrCreate(dirtyTargetModel))); - - return stat; - } catch (error) { - - // In case of an error, discard any dirty target backups that were made - await Promise.all(dirtyTargetModelUris.map(dirtyTargetModel => this.backupFileService.discardResourceBackup(dirtyTargetModel))); - - throw error; - } } //#endregion @@ -537,7 +570,9 @@ export abstract class TextFileService extends Disposable implements ITextFileSer async confirmSave(resources?: URI[]): Promise { if (this.environmentService.isExtensionDevelopment) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + if (!this.environmentService.args['extension-development-confirm-save']) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } } const resourcesToConfirm = this.getDirty(resources); @@ -855,7 +890,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return false; } - // take over encoding, mode (only if more specific) and model value from source model + // take over model value, encoding and mode (only if more specific) from source model targetModel.updatePreferredEncoding(sourceModel.getEncoding()); if (sourceModel.isResolved() && targetModel.isResolved()) { this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); @@ -1009,8 +1044,8 @@ export abstract class TextFileService extends Disposable implements ITextFileSer getAutoSaveConfiguration(): IAutoSaveConfiguration { return { autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, - autoSaveFocusChange: this.configuredAutoSaveOnFocusChange, - autoSaveApplicationChange: this.configuredAutoSaveOnWindowChange + autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, + autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange }; } @@ -1027,4 +1062,4 @@ export abstract class TextFileService extends Disposable implements ITextFileSer super.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts index ce097d6fdf5..653da4a83c4 100644 --- a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts +++ b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts @@ -17,7 +17,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class TextResourcePropertiesService implements ITextResourcePropertiesService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private remoteEnvironment: IRemoteAgentEnvironment | null = null; @@ -55,4 +55,4 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer } } -registerSingleton(ITextResourcePropertiesService, TextResourcePropertiesService, true); \ No newline at end of file +registerSingleton(ITextResourcePropertiesService, TextResourcePropertiesService, true); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 1cbf25a56ef..02956fd1cdd 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -470,7 +470,9 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport hasBackup(): boolean; - isDirty(): boolean; + isDirty(): this is IResolvedTextFileEditorModel; + + makeDirty(): void; isResolved(): this is IResolvedTextFileEditorModel; @@ -522,7 +524,7 @@ export function stringToSnapshot(value: string): ITextSnapshot { } export class TextSnapshotReadable implements VSBufferReadable { - private preambleHandled: boolean; + private preambleHandled = false; constructor(private snapshot: ITextSnapshot, private preamble?: string) { } @@ -805,4 +807,4 @@ export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; lab labelShort: 'CP 850', order: 47 } -}; \ No newline at end of file +}; diff --git a/src/vs/workbench/services/textfile/node/textFileService.ts b/src/vs/workbench/services/textfile/node/textFileService.ts index 3db51b4e10f..f940da7889e 100644 --- a/src/vs/workbench/services/textfile/node/textFileService.ts +++ b/src/vs/workbench/services/textfile/node/textFileService.ts @@ -17,20 +17,20 @@ import { isMacintosh } from 'vs/base/common/platform'; import product from 'vs/platform/product/node/product'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, encodeStream, UTF8_BOM, UTF16be_BOM, UTF16le_BOM, toDecodeStream, IDecodeStreamResult, detectEncodingByBOMFromBuffer } from 'vs/base/node/encoding'; +import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, encodeStream, UTF8_BOM, toDecodeStream, IDecodeStreamResult, detectEncodingByBOMFromBuffer, isUTFEncoding } from 'vs/base/node/encoding'; import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { joinPath, extname, isEqualOrParent } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { VSBufferReadable, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { VSBufferReadable } from 'vs/base/common/buffer'; import { Readable } from 'stream'; -import { isUndefinedOrNull } from 'vs/base/common/types'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { ITextSnapshot } from 'vs/editor/common/model'; +import { nodeReadableToString, streamToNodeReadable, nodeStreamToVSBufferReadable } from 'vs/base/node/stream'; export class NodeTextFileService extends TextFileService { - private _encoding: EncodingOracle; + private _encoding!: EncodingOracle; get encoding(): EncodingOracle { if (!this._encoding) { this._encoding = this._register(this.instantiationService.createInstance(EncodingOracle)); @@ -45,7 +45,7 @@ export class NodeTextFileService extends TextFileService { return { ...bufferStream, encoding: decoder.detected.encoding || UTF8, - value: await this.nodeReadableToString(decoder.stream) + value: await nodeReadableToString(decoder.stream) }; } @@ -68,7 +68,7 @@ export class NodeTextFileService extends TextFileService { const bufferStream = await this.fileService.readFileStream(resource, options); // read through encoding library - const decoder = await toDecodeStream(this.streamToNodeReadable(bufferStream.value), { + const decoder = await toDecodeStream(streamToNodeReadable(bufferStream.value), { guessEncoding: (options && options.autoGuessEncoding) || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'), overwriteEncoding: detectedEncoding => this.encoding.getReadEncoding(resource, options, detectedEncoding) }); @@ -108,60 +108,6 @@ export class NodeTextFileService extends TextFileService { return ensuredOptions; } - private streamToNodeReadable(stream: VSBufferReadableStream): Readable { - return new class extends Readable { - private listening = false; - - _read(size?: number): void { - if (!this.listening) { - this.listening = true; - - // Data - stream.on('data', data => { - try { - if (!this.push(data.buffer)) { - stream.pause(); // pause the stream if we should not push anymore - } - } catch (error) { - this.emit(error); - } - }); - - // End - stream.on('end', () => { - try { - this.push(null); // signal EOS - } catch (error) { - this.emit(error); - } - }); - - // Error - stream.on('error', error => this.emit(error)); - } - - // ensure the stream is flowing - stream.resume(); - } - - _destroy(error: Error | null, callback: (error: Error | null) => void): void { - stream.destroy(); - - callback(null); - } - }; - } - - private nodeReadableToString(stream: NodeJS.ReadableStream): Promise { - return new Promise((resolve, reject) => { - let result = ''; - - stream.on('data', chunk => result += chunk); - stream.on('error', reject); - stream.on('end', () => resolve(result)); - }); - } - protected async doCreate(resource: URI, value?: string, options?: ICreateFileOptions): Promise { // check for encoding @@ -238,7 +184,7 @@ export class NodeTextFileService extends TextFileService { const encodedReadable = readable.pipe(encoder); - return this.nodeStreamToReadable(encodedReadable, encoding, addBOM); + return nodeStreamToVSBufferReadable(encodedReadable, addBOM && isUTFEncoding(encoding) ? { encoding } : undefined); } private snapshotToNodeReadable(snapshot: ITextSnapshot): Readable { @@ -266,53 +212,6 @@ export class NodeTextFileService extends TextFileService { }); } - private nodeStreamToReadable(stream: NodeJS.ReadWriteStream, encoding: string, addBOM: boolean): VSBufferReadable { - let bytesRead = 0; - let done = false; - - return { - read(): VSBuffer | null { - if (done) { - return null; - } - - const res = stream.read(); - if (isUndefinedOrNull(res)) { - done = true; - - // If we are instructed to add a BOM but we detect that no - // bytes have been read, we must ensure to return the BOM - // ourselves so that we comply with the contract. - if (bytesRead === 0 && addBOM) { - switch (encoding) { - case UTF8: - case UTF8_with_bom: - return VSBuffer.wrap(Buffer.from(UTF8_BOM)); - case UTF16be: - return VSBuffer.wrap(Buffer.from(UTF16be_BOM)); - case UTF16le: - return VSBuffer.wrap(Buffer.from(UTF16le_BOM)); - } - } - - return null; - } - - // Handle String - if (typeof res === 'string') { - bytesRead += res.length; - return VSBuffer.fromString(res); - } - - // Handle Buffer - else { - bytesRead += res.byteLength; - return VSBuffer.wrap(res); - } - } - }; - } - private async writeElevated(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { // write into a tmp file first @@ -505,4 +404,4 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { } } -registerSingleton(ITextFileService, NodeTextFileService); \ No newline at end of file +registerSingleton(ITextFileService, NodeTextFileService); diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index e22cbe034db..b5609d42d32 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -219,6 +219,33 @@ suite('Files - TextFileEditorModel', () => { assert.ok(model.isDirty()); }); + test('Make Dirty', async function () { + let eventCounter = 0; + + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + model.makeDirty(); + assert.ok(!model.isDirty()); // needs to be resolved + + await model.load(); + model.textEditorModel!.setValue('foo'); + assert.ok(model.isDirty()); + + await model.revert(true /* soft revert */); + assert.ok(!model.isDirty()); + + model.onDidStateChange(e => { + if (e === StateChange.DIRTY) { + eventCounter++; + } + }); + + model.makeDirty(); + assert.ok(model.isDirty()); + assert.equal(eventCounter, 1); + model.dispose(); + }); + test('File not modified error is handled gracefully', async function () { let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index 586639b4e8e..eab223e0d4a 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -4,16 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices'; import { ITextFileService, snapshotToString, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { Schemas } from 'vs/base/common/network'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { rimraf, RimRafMode, copy, readFile, exists } from 'vs/base/node/pfs'; @@ -36,20 +31,15 @@ import { detectEncodingByBOM } from 'vs/base/test/node/encoding/encoding.test'; class ServiceAccessor { constructor( - @ILifecycleService public lifecycleService: TestLifecycleService, @ITextFileService public textFileService: TestTextFileService, - @IUntitledEditorService public untitledEditorService: IUntitledEditorService, - @IWindowsService public windowsService: TestWindowsService, - @IWorkspaceContextService public contextService: TestContextService, - @IModelService public modelService: ModelServiceImpl, - @IFileService public fileService: TestFileService + @IUntitledEditorService public untitledEditorService: IUntitledEditorService ) { } } class TestNodeTextFileService extends NodeTextFileService { - private _testEncoding: TestEncodingOracle; + private _testEncoding: TestEncodingOracle | undefined; get encoding(): TestEncodingOracle { if (!this._testEncoding) { this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index ce5dd5dd772..07676420066 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -37,7 +37,7 @@ class ServiceAccessor { class BeforeShutdownEventImpl implements BeforeShutdownEvent { - public value: boolean | Promise; + public value: boolean | Promise | undefined; public reason = ShutdownReason.CLOSE; veto(value: boolean | Promise): void { @@ -272,8 +272,16 @@ suite('Files - TextFileService', () => { }); test('move - dirty file', async function () { - let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target.txt'), 'utf8', undefined); + await testMove(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt')); + }); + + test('move - dirty file (target exists and is dirty)', async function () { + await testMove(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true); + }); + + async function testMove(source: URI, target: URI, targetDirty?: boolean): Promise { + let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, source, 'utf8', undefined); + let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, target, 'utf8', undefined); (accessor.textFileService.models).add(sourceModel.getResource(), sourceModel); (accessor.textFileService.models).add(targetModel.getResource(), targetModel); @@ -283,11 +291,22 @@ suite('Files - TextFileService', () => { sourceModel.textEditorModel!.setValue('foo'); assert.ok(service.isDirty(sourceModel.getResource())); + if (targetDirty) { + await targetModel.load(); + targetModel.textEditorModel!.setValue('bar'); + assert.ok(service.isDirty(targetModel.getResource())); + } + await service.move(sourceModel.getResource(), targetModel.getResource(), true); + + assert.equal(targetModel.textEditorModel!.getValue(), 'foo'); + assert.ok(!service.isDirty(sourceModel.getResource())); + assert.ok(service.isDirty(targetModel.getResource())); + sourceModel.dispose(); targetModel.dispose(); - }); + } suite('Hot Exit', () => { suite('"onExit" setting', () => { diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index 3059579a649..2512af088a0 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -11,7 +11,7 @@ import * as Json from 'vs/base/common/json'; import { ExtensionData, IThemeExtensionPoint, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IFileService } from 'vs/platform/files/common/files'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; -import { asDomUri } from 'vs/base/browser/dom'; +import { asCSSUrl } from 'vs/base/browser/dom'; export class FileIconThemeData implements IFileIconTheme { id: string; @@ -332,7 +332,7 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i let fonts = iconThemeDocument.fonts; if (Array.isArray(fonts)) { fonts.forEach(font => { - let src = font.src.map(l => `url('${asDomUri(resolvePath(l.path))}') format('${l.format}')`).join(', '); + let src = font.src.map(l => `${asCSSUrl(resolvePath(l.path))} format('${l.format}')`).join(', '); cssRules.push(`@font-face { src: ${src}; font-family: '${font.id}'; font-weight: ${font.weight}; font-style: ${font.style}; }`); }); cssRules.push(`.show-file-icons .file-icon::before, .show-file-icons .folder-icon::before, .show-file-icons .rootfolder-icon::before { font-family: '${fonts[0].id}'; font-size: ${fonts[0].size || '150%'}}`); @@ -343,7 +343,7 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i let definition = iconThemeDocument.iconDefinitions[defId]; if (definition) { if (definition.iconPath) { - cssRules.push(`${selectors.join(', ')} { content: ' '; background-image: url("${asDomUri(resolvePath(definition.iconPath))}"); }`); + cssRules.push(`${selectors.join(', ')} { content: ' '; background-image: ${asCSSUrl(resolvePath(definition.iconPath))}; }`); } if (definition.fontCharacter || definition.fontColor) { let body = ''; diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index ffc0854905f..139822af63b 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -180,7 +180,7 @@ export interface IStartupMetrics { /** * The time it took to create a new renderer (browser window) and to initialize that to the point - * of load the main-bundle (`workbench.main.js`). + * of load the main-bundle (`workbench.desktop.main.js`). * * * Happens in the main-process *and* the renderer-process * * Measured with the `main:loadWindow` and `willLoadWorkbenchMain` performance marks. @@ -208,7 +208,7 @@ export interface IStartupMetrics { readonly ellapsedWorkspaceServiceInit: number; /** - * The time it took to load the main-bundle of the workbench, e.g. `workbench.main.js`. + * The time it took to load the main-bundle of the workbench, e.g. `workbench.desktop.main.js`. * * * Happens in the renderer-process * * Measured with the `willLoadWorkbenchMain` and `didLoadWorkbenchMain` performance marks. @@ -305,7 +305,7 @@ class TimerService implements ITimerService { _serviceBrand: any; - private _startupMetrics: Promise; + private _startupMetrics?: Promise; constructor( @IWindowsService private readonly _windowsService: IWindowsService, diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index ad0498407ff..3571e4f6c0a 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -112,7 +112,7 @@ export interface IUntitledEditorService { export class UntitledEditorService extends Disposable implements IUntitledEditorService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; private mapResourceToInput = new ResourceMap(); private mapResourceToAssociatedFilePath = new ResourceMap(); @@ -284,4 +284,4 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor } } -registerSingleton(IUntitledEditorService, UntitledEditorService, true); \ No newline at end of file +registerSingleton(IUntitledEditorService, UntitledEditorService, true); diff --git a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts index ffed9c53d53..622da2547a3 100644 --- a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts @@ -209,8 +209,7 @@ export class InMemoryUserDataProvider extends Disposable implements IFileSystemP readonly onDidChangeFile: Event = this._onDidChangeFile.event; private _bufferedChanges: IFileChange[] = []; - private _fireSoonHandle?: NodeJS.Timer; - + private _fireSoonHandle?: any; watch(resource: URI, opts: IWatchOptions): IDisposable { // ignore, fires for all changes... @@ -229,4 +228,4 @@ export class InMemoryUserDataProvider extends Disposable implements IFileSystemP this._bufferedChanges.length = 0; }, 5); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts index b8ba819bba8..854e9582f5a 100644 --- a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts +++ b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts @@ -47,7 +47,7 @@ suite('FileUserDataProvider', () => { userDataResource = URI.file(userDataPath).with({ scheme: Schemas.userData }); await Promise.all([pfs.mkdirp(userDataPath), pfs.mkdirp(backupsPath)]); - const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: 'workspaceId' }); + const environmentService = new BrowserWorkbenchEnvironmentService('workspaceId', { remoteAuthority: 'remote' }); environmentService.userRoamingDataHome = userDataResource; const userDataFileSystemProvider = new FileUserDataProvider(URI.file(userDataPath), URI.file(backupsPath), diskFileSystemProvider, environmentService); @@ -321,7 +321,7 @@ suite('FileUserDataProvider - Watching', () => { localUserDataResource = URI.file(userDataPath); userDataResource = localUserDataResource.with({ scheme: Schemas.userData }); - const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: 'workspaceId' }); + const environmentService = new BrowserWorkbenchEnvironmentService('workspaceId', { remoteAuthority: 'remote' }); environmentService.userRoamingDataHome = userDataResource; const userDataFileSystemProvider = new FileUserDataProvider(localUserDataResource, localBackupsResource, new TestFileSystemProvider(fileEventEmitter.event), environmentService); @@ -475,4 +475,4 @@ suite('FileUserDataProvider - Watching', () => { type: FileChangeType.DELETED }]); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts index 500f4761c45..abf9f135d3d 100644 --- a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts @@ -36,7 +36,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class WorkspaceEditingService implements IWorkspaceEditingService { - _serviceBrand: ServiceIdentifier; + _serviceBrand!: ServiceIdentifier; constructor( @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, @@ -220,6 +220,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { private async doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): Promise { const state = this.contextService.getWorkbenchState(); if (this.environmentService.configuration.remoteAuthority) { + // Do not allow workspace folders with scheme different than the current remote scheme const schemas = this.contextService.getWorkspace().folders.map(f => f.uri.scheme); if (schemas.length && foldersToAdd.some(f => schemas.indexOf(f.uri.scheme) === -1)) { @@ -286,6 +287,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { if (path && !await this.isValidTargetWorkspacePath(path)) { return; } + const remoteAuthority = this.environmentService.configuration.remoteAuthority; const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority); if (path) { @@ -293,6 +295,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } else { path = untitledWorkspace.configPath; } + return this.enterWorkspace(path); } @@ -300,17 +303,18 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { if (!await this.isValidTargetWorkspacePath(path)) { return; } + const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); if (!workspaceIdentifier) { return; } + await this.saveWorkspaceAs(workspaceIdentifier, path); return this.enterWorkspace(path); } async isValidTargetWorkspacePath(path: URI): Promise { - const windows = await this.windowsService.getWindows(); // Prevent overwriting a workspace that is currently opened in another window @@ -382,30 +386,38 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } const workspace = await this.workspaceService.getWorkspaceIdentifier(path); + // Settings migration (only if we come from a folder workspace) if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { await this.migrateWorkspaceSettings(workspace); } + const workspaceImpl = this.contextService as WorkspaceService; await workspaceImpl.initialize(workspace); - // Restart extension host if first root folder changed (impact on deprecated workspace.rootPath API) - // Stop the extension host first to give extensions most time to shutdown - this.extensionService.stopExtensionHost(); - const result = await this.windowService.enterWorkspace(path); if (result) { + + // Migrate storage to new workspace await this.migrateStorage(result.workspace); + // Reinitialize backup service + this.environmentService.configuration.backupPath = result.backupPath; + this.environmentService.configuration.backupWorkspaceResource = result.backupPath ? toBackupWorkspaceResource(result.backupPath, this.environmentService) : undefined; if (this.backupFileService instanceof BackupFileService) { - this.backupFileService.initialize(toBackupWorkspaceResource(result.backupPath!, this.environmentService)); + this.backupFileService.reinitialize(); } } + // TODO@aeschli: workaround until restarting works if (this.environmentService.configuration.remoteAuthority) { - this.windowService.reloadWindow(); // TODO aeschli: workaround until restarting works - } else { - this.extensionService.startExtensionHost(); + this.windowService.reloadWindow(); + } + + // Restart the extension host: entering a workspace means a new location for + // storage and potentially a change in the workspace.rootPath property. + else { + this.extensionService.restartExtensionHost(); } } @@ -448,4 +460,4 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } } -registerSingleton(IWorkspaceEditingService, WorkspaceEditingService, true); \ No newline at end of file +registerSingleton(IWorkspaceEditingService, WorkspaceEditingService, true); diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index ed151b3aced..e0cefdd51c8 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -13,10 +13,10 @@ import { StorageScope } from 'vs/platform/storage/common/storage'; class SimplePart extends Part { - minimumWidth: number; - maximumWidth: number; - minimumHeight: number; - maximumHeight: number; + minimumWidth: number = 50; + maximumWidth: number = 50; + minimumHeight: number = 50; + maximumHeight: number = 50; layout(width: number, height: number): void { throw new Error('Method not implemented.'); diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index 30312d509e3..4c81c67b61a 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -103,7 +103,7 @@ suite('Notifications', () => { // Error with Action let item6 = NotificationViewItem.create({ severity: Severity.Error, message: createErrorWithActions('Hello Error', { actions: [new Action('id', 'label')] }) })!; - assert.equal(item6.actions.primary!.length, 1); + assert.equal(item6.actions!.primary!.length, 1); // Links let item7 = NotificationViewItem.create({ severity: Severity.Info, message: 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and [Link 3](command:without.title) and [Invalid Link4](ftp://link4.com)' })!; @@ -219,4 +219,4 @@ suite('Notifications', () => { disposable3.dispose(); assert.ok(!model.statusMessage); }); -}); \ No newline at end of file +}); 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 779c40e9656..f2c5b0043fe 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -181,7 +181,7 @@ suite('ExtHostLanguageFeatureCommands', function () { test('executeWorkspaceSymbolProvider should accept empty string, #39522', async function () { disposables.push(extHost.registerWorkspaceSymbolProvider(nullExtensionDescription, { - provideWorkspaceSymbols(query): vscode.SymbolInformation[] { + provideWorkspaceSymbols(): vscode.SymbolInformation[] { return [new types.SymbolInformation('hello', types.SymbolKind.Array, new types.Range(0, 0, 0, 0), URI.parse('foo:bar')) as vscode.SymbolInformation]; } })); @@ -312,7 +312,7 @@ suite('ExtHostLanguageFeatureCommands', function () { test('reference search, back and forth', function () { disposables.push(extHost.registerReferenceProvider(nullExtensionDescription, defaultSelector, { - provideReferences(doc: any) { + provideReferences() { return [ new types.Location(URI.parse('some:uri/path'), new types.Range(0, 1, 0, 5)) ]; @@ -388,7 +388,7 @@ suite('ExtHostLanguageFeatureCommands', function () { test('Suggest, back and forth', function () { disposables.push(extHost.registerCompletionItemProvider(nullExtensionDescription, defaultSelector, { - provideCompletionItems(doc, pos): any { + provideCompletionItems(): any { let a = new types.CompletionItem('item1'); let b = new types.CompletionItem('item2'); b.textEdit = types.TextEdit.replace(new types.Range(0, 4, 0, 8), 'foo'); // overwite after @@ -631,6 +631,61 @@ suite('ExtHostLanguageFeatureCommands', function () { }); }); + test('vscode.executeCodeActionProvider passes Range to provider although Selection is passed in #77997', function () { + disposables.push(extHost.registerCodeActionProvider(nullExtensionDescription, defaultSelector, { + provideCodeActions(document, rangeOrSelection): vscode.CodeAction[] { + return [{ + command: { + arguments: [document, rangeOrSelection], + command: 'command', + title: 'command_title', + }, + kind: types.CodeActionKind.Empty.append('foo'), + title: 'title', + }]; + } + })); + + const selection = new types.Selection(0, 0, 1, 1); + + return rpcProtocol.sync().then(() => { + return commands.executeCommand('vscode.executeCodeActionProvider', model.uri, selection).then(value => { + assert.equal(value.length, 1); + const [first] = value; + assert.ok(first.command); + assert.ok(first.command!.arguments![1] instanceof types.Selection); + assert.ok(first.command!.arguments![1].isEqual(selection)); + }); + }); + }); + + test('vscode.executeCodeActionProvider results seem to be missing their `isPreferred` property #78098', function () { + disposables.push(extHost.registerCodeActionProvider(nullExtensionDescription, defaultSelector, { + provideCodeActions(document, rangeOrSelection): vscode.CodeAction[] { + return [{ + command: { + arguments: [document, rangeOrSelection], + command: 'command', + title: 'command_title', + }, + kind: types.CodeActionKind.Empty.append('foo'), + title: 'title', + isPreferred: true + }]; + } + })); + + const selection = new types.Selection(0, 0, 1, 1); + + return rpcProtocol.sync().then(() => { + return commands.executeCommand('vscode.executeCodeActionProvider', model.uri, selection).then(value => { + assert.equal(value.length, 1); + const [first] = value; + assert.equal(first.isPreferred, true); + }); + }); + }); + // --- code lens test('CodeLens, back and forth', function () { @@ -724,7 +779,7 @@ suite('ExtHostLanguageFeatureCommands', function () { provideDocumentColors(): vscode.ColorInformation[] { return [new types.ColorInformation(new types.Range(0, 0, 0, 20), new types.Color(0.1, 0.2, 0.3, 0.4))]; }, - provideColorPresentations(color: vscode.Color, context: { range: vscode.Range, document: vscode.TextDocument }): vscode.ColorPresentation[] { + provideColorPresentations(): vscode.ColorPresentation[] { const cp = new types.ColorPresentation('#ABC'); cp.textEdit = types.TextEdit.replace(new types.Range(1, 0, 1, 20), '#ABC'); cp.additionalTextEdits = [types.TextEdit.insert(new types.Position(2, 20), '*')]; diff --git a/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts index d0cf59f320e..cbf97988142 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts @@ -26,7 +26,10 @@ suite('ExtHostCommands', function () { } }; - const commands = new ExtHostCommands(SingleProxyRPCProtocol(shape), new NullLogService()); + const commands = new ExtHostCommands( + SingleProxyRPCProtocol(shape), + new NullLogService() + ); commands.registerCommand(true, 'foo', (): any => { }).dispose(); assert.equal(lastUnregister!, 'foo'); assert.equal(CommandsRegistry.getCommand('foo'), undefined); @@ -46,7 +49,10 @@ suite('ExtHostCommands', function () { } }; - const commands = new ExtHostCommands(SingleProxyRPCProtocol(shape), new NullLogService()); + const commands = new ExtHostCommands( + SingleProxyRPCProtocol(shape), + new NullLogService() + ); const reg = commands.registerCommand(true, 'foo', (): any => { }); reg.dispose(); reg.dispose(); 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 db499917207..c2bfcdd6605 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { MainThreadConfigurationShape, IConfigurationInitData } from 'vs/workbench/api/common/extHost.protocol'; @@ -12,10 +12,10 @@ import { ConfigurationModel } from 'vs/platform/configuration/common/configurati import { TestRPCProtocol } from './testRPCProtocol'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { NullLogService } from 'vs/platform/log/common/log'; import { assign } from 'vs/base/common/objects'; -import { Counter } from 'vs/base/common/numbers'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; suite('ExtHostConfiguration', function () { @@ -27,11 +27,15 @@ suite('ExtHostConfiguration', function () { } } + function createExtHostWorkspace(): ExtHostWorkspace { + return new ExtHostWorkspace(new TestRPCProtocol(), new class extends mock() { }, new NullLogService()); + } + function createExtHostConfiguration(contents: any = Object.create(null), shape?: MainThreadConfigurationShape) { if (!shape) { shape = new class extends mock() { }; } - return new ExtHostConfigProvider(shape, new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()), createConfigurationData(contents)); + return new ExtHostConfigProvider(shape, createExtHostWorkspace(), createConfigurationData(contents)); } function createConfigurationData(contents: any): IConfigurationInitData { @@ -39,7 +43,7 @@ suite('ExtHostConfiguration', function () { defaults: new ConfigurationModel(contents), user: new ConfigurationModel(contents), workspace: new ConfigurationModel(), - folders: Object.create(null), + folders: [], configurationScopes: [] }; } @@ -264,7 +268,7 @@ suite('ExtHostConfiguration', function () { test('inspect in no workspace context', function () { const testObject = new ExtHostConfigProvider( new class extends mock() { }, - new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()), + createExtHostWorkspace(), { defaults: new ConfigurationModel({ 'editor': { @@ -277,7 +281,7 @@ suite('ExtHostConfiguration', function () { } }, ['editor.wordWrap']), workspace: new ConfigurationModel({}, []), - folders: Object.create(null), + folders: [], configurationScopes: [] } ); @@ -297,14 +301,14 @@ suite('ExtHostConfiguration', function () { test('inspect in single root context', function () { const workspaceUri = URI.file('foo'); - const folders = Object.create(null); + const folders: [UriComponents, IConfigurationModel][] = []; const workspace = new ConfigurationModel({ 'editor': { 'wordWrap': 'bounded' } }, ['editor.wordWrap']); - folders[workspaceUri.toString()] = workspace; - const extHostWorkspace = new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()); + folders.push([workspaceUri, workspace]); + const extHostWorkspace = createExtHostWorkspace(); extHostWorkspace.$initializeWorkspace({ 'id': 'foo', 'folders': [aWorkspaceFolder(URI.file('foo'), 0)], @@ -365,21 +369,21 @@ suite('ExtHostConfiguration', function () { const firstRoot = URI.file('foo1'); const secondRoot = URI.file('foo2'); const thirdRoot = URI.file('foo3'); - const folders = Object.create(null); - folders[firstRoot.toString()] = new ConfigurationModel({ + const folders: [UriComponents, IConfigurationModel][] = []; + folders.push([firstRoot, new ConfigurationModel({ 'editor': { 'wordWrap': 'off', 'lineNumbers': 'relative' } - }, ['editor.wordWrap']); - folders[secondRoot.toString()] = new ConfigurationModel({ + }, ['editor.wordWrap'])]); + folders.push([secondRoot, new ConfigurationModel({ 'editor': { 'wordWrap': 'on' } - }, ['editor.wordWrap']); - folders[thirdRoot.toString()] = new ConfigurationModel({}, []); + }, ['editor.wordWrap'])]); + folders.push([thirdRoot, new ConfigurationModel({}, [])]); - const extHostWorkspace = new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()); + const extHostWorkspace = createExtHostWorkspace(); extHostWorkspace.$initializeWorkspace({ 'id': 'foo', 'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)], @@ -589,7 +593,7 @@ suite('ExtHostConfiguration', function () { test('configuration change event', (done) => { const workspaceFolder = aWorkspaceFolder(URI.file('folder1'), 0); - const extHostWorkspace = new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()); + const extHostWorkspace = createExtHostWorkspace(); extHostWorkspace.$initializeWorkspace({ 'id': 'foo', 'folders': [workspaceFolder], diff --git a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts index 4eca9307e09..60181c415f2 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts @@ -424,4 +424,40 @@ suite('ExtHostDiagnostics', () => { collection.set(URI.parse('test:me'), array); assert.equal(callCount, 3); // same but un-equal array }); + + test('Diagnostics created by tasks aren\'t accessible to extensions #47292', async function () { + const diags = new ExtHostDiagnostics(new class implements IMainContext { + getProxy(id: any): any { + return {}; + } + set(): any { + return null; + } + assertRegistered(): void { + + } + }); + + + // + const uri = URI.parse('foo:bar'); + const data: IMarkerData[] = [{ + message: 'message', + startLineNumber: 1, + startColumn: 1, + endLineNumber: 1, + endColumn: 1, + severity: 3 + }]; + + const p1 = Event.toPromise(diags.onDidChangeDiagnostics); + diags.$acceptMarkersChange([[uri, data]]); + await p1; + assert.equal(diags.getDiagnostics(uri).length, 1); + + const p2 = Event.toPromise(diags.onDidChangeDiagnostics); + diags.$acceptMarkersChange([[uri, []]]); + await p2; + assert.equal(diags.getDiagnostics(uri).length, 0); + }); }); 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 99724c53a21..d85eae55ebf 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { TextDocumentSaveReason, TextEdit, Position, EndOfLine } from 'vs/workbench/api/common/extHostTypes'; -import { MainThreadTextEditorsShape, WorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; @@ -262,9 +262,9 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, pushEdits sync', () => { - let dto: WorkspaceEditDto; + let dto: IWorkspaceEditDto; const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock() { - $tryApplyWorkspaceEdit(_edits: WorkspaceEditDto) { + $tryApplyWorkspaceEdit(_edits: IWorkspaceEditDto) { dto = _edits; return Promise.resolve(true); } @@ -286,9 +286,9 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, concurrent change', () => { - let edits: WorkspaceEditDto; + let edits: IWorkspaceEditDto; const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock() { - $tryApplyWorkspaceEdit(_edits: WorkspaceEditDto) { + $tryApplyWorkspaceEdit(_edits: IWorkspaceEditDto) { edits = _edits; return Promise.resolve(true); } @@ -323,7 +323,7 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, two listeners -> two document states', () => { const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock() { - $tryApplyWorkspaceEdit(dto: WorkspaceEditDto) { + $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto) { for (const edit of dto.edits) { if (!isResourceTextEdit(edit)) { diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts index 67916da33da..ce9209f9344 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts @@ -6,17 +6,14 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; suite('ExtHostDocumentsAndEditors', () => { let editors: ExtHostDocumentsAndEditors; setup(function () { - editors = new ExtHostDocumentsAndEditors({ - getProxy: () => { return undefined!; }, - set: undefined!, - assertRegistered: undefined! - }); + editors = new ExtHostDocumentsAndEditors(new TestRPCProtocol()); }); test('The value of TextDocument.isClosed is incorrect when a text document is closed, #27949', () => { 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 0d15201d893..8dff457b408 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -18,6 +18,9 @@ import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteSt import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; import * as vscode from 'vscode'; import { NullLogService } from 'vs/platform/log/common/log'; +import { URITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; +import { mock } from 'vs/workbench/test/electron-browser/api/mock'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; let rpcProtocol: TestRPCProtocol; let extHostSearch: ExtHostSearch; @@ -135,7 +138,17 @@ suite('ExtHostSearch', () => { rpcProtocol.set(MainContext.MainThreadSearch, mockMainThreadSearch); mockPFS = {}; - extHostSearch = new ExtHostSearch(rpcProtocol, null!, logService, mockPFS as any); + extHostSearch = new class extends ExtHostSearch { + constructor() { + super( + rpcProtocol, + new class extends mock() { remote = { isRemote: false, authority: undefined }; }, + new URITransformerService(null), + logService + ); + this._pfs = mockPFS as any; + } + }; }); teardown(() => { diff --git a/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts index b168561d77a..a66b5b43b9f 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { MainContext, MainThreadTextEditorsShape, WorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; +import { MainContext, MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -16,14 +16,14 @@ suite('ExtHostTextEditors.applyWorkspaceEdit', () => { const resource = URI.parse('foo:bar'); let editors: ExtHostEditors; - let workspaceResourceEdits: WorkspaceEditDto; + let workspaceResourceEdits: IWorkspaceEditDto; setup(() => { workspaceResourceEdits = null!; let rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(MainContext.MainThreadTextEditors, new class extends mock() { - $tryApplyWorkspaceEdit(_workspaceResourceEdits: WorkspaceEditDto): Promise { + $tryApplyWorkspaceEdit(_workspaceResourceEdits: IWorkspaceEditDto): Promise { workspaceResourceEdits = _workspaceResourceEdits; return Promise.resolve(true); } diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index 5f05a9bc453..bc50a3dd00a 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -71,7 +71,10 @@ suite('ExtHostTreeView', function () { rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); target = new RecordingShape(); - testObject = new ExtHostTreeViews(target, new ExtHostCommands(rpcProtocol, new NullLogService()), new NullLogService()); + testObject = new ExtHostTreeViews(target, new ExtHostCommands( + rpcProtocol, + new NullLogService() + ), new NullLogService()); onDidChangeTreeNode = new Emitter<{ key: string }>(); onDidChangeTreeNodeWithId = new Emitter<{ key: string }>(); testObject.createTreeView('testNodeTreeProvider', { treeDataProvider: aNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription); diff --git a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts index 8a121279041..fb75ee14a20 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts @@ -48,7 +48,7 @@ suite('ExtHostWebview', () => { assert.strictEqual(lastInvokedDeserializer, serializerB); }); - test('toWebviewResource for desktop vscode-resource scheme', () => { + test('asWebviewUri for desktop vscode-resource scheme', () => { const shape = createNoopMainThreadWebviews(); const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { webviewCspSource: '', @@ -57,37 +57,37 @@ suite('ExtHostWebview', () => { const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); assert.strictEqual( - webview.webview.toWebviewResource(URI.parse('file:///Users/codey/file.html')).toString(), + webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html')).toString(), 'vscode-resource:/Users/codey/file.html', 'Unix basic' ); assert.strictEqual( - webview.webview.toWebviewResource(URI.parse('file:///Users/codey/file.html#frag')).toString(), + webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html#frag')).toString(), 'vscode-resource:/Users/codey/file.html#frag', 'Unix should preserve fragment' ); assert.strictEqual( - webview.webview.toWebviewResource(URI.parse('file:///Users/codey/f%20ile.html')).toString(), + webview.webview.asWebviewUri(URI.parse('file:///Users/codey/f%20ile.html')).toString(), 'vscode-resource:/Users/codey/f%20ile.html', 'Unix with encoding' ); assert.strictEqual( - webview.webview.toWebviewResource(URI.parse('file://localhost/Users/codey/file.html')).toString(), + webview.webview.asWebviewUri(URI.parse('file://localhost/Users/codey/file.html')).toString(), 'vscode-resource://localhost/Users/codey/file.html', 'Unix should preserve authority' ); assert.strictEqual( - webview.webview.toWebviewResource(URI.parse('file:///c:/codey/file.txt')).toString(), + webview.webview.asWebviewUri(URI.parse('file:///c:/codey/file.txt')).toString(), 'vscode-resource:/c%3A/codey/file.txt', 'Windows C drive' ); }); - test('toWebviewResource for web endpoint', () => { + test('asWebviewUri for web endpoint', () => { const shape = createNoopMainThreadWebviews(); const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { @@ -101,31 +101,31 @@ suite('ExtHostWebview', () => { } assert.strictEqual( - stripEndpointUuid(webview.webview.toWebviewResource(URI.parse('file:///Users/codey/file.html')).toString()), + stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html')).toString()), 'webview.contoso.com/commit///Users/codey/file.html', 'Unix basic' ); assert.strictEqual( - stripEndpointUuid(webview.webview.toWebviewResource(URI.parse('file:///Users/codey/file.html#frag')).toString()), + stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html#frag')).toString()), 'webview.contoso.com/commit///Users/codey/file.html#frag', 'Unix should preserve fragment' ); assert.strictEqual( - stripEndpointUuid(webview.webview.toWebviewResource(URI.parse('file:///Users/codey/f%20ile.html')).toString()), + stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/f%20ile.html')).toString()), 'webview.contoso.com/commit///Users/codey/f%20ile.html', 'Unix with encoding' ); assert.strictEqual( - stripEndpointUuid(webview.webview.toWebviewResource(URI.parse('file://localhost/Users/codey/file.html')).toString()), + stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file://localhost/Users/codey/file.html')).toString()), 'webview.contoso.com/commit//localhost/Users/codey/file.html', 'Unix should preserve authority' ); assert.strictEqual( - stripEndpointUuid(webview.webview.toWebviewResource(URI.parse('file:///c:/codey/file.txt')).toString()), + stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///c:/codey/file.txt')).toString()), 'webview.contoso.com/commit///c%3A/codey/file.txt', 'Windows C drive' ); 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 76040fc8eae..0a605c63bd0 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -5,7 +5,6 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Counter } from 'vs/base/common/numbers'; import { basename } from 'vs/base/common/path'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -17,9 +16,15 @@ import { RelativePattern } from 'vs/workbench/api/common/extHostTypes'; import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { TestRPCProtocol } from './testRPCProtocol'; +import { ExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService, requestIdProvider: Counter): ExtHostWorkspace { - const result = new ExtHostWorkspace(mainContext, logService, requestIdProvider); +function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService): ExtHostWorkspace { + const result = new ExtHostWorkspace( + new ExtHostRpcService(mainContext), + new class extends mock() { workspace = data; }, + logService + ); result.$initializeWorkspace(data); return result; } @@ -45,7 +50,7 @@ suite('ExtHostWorkspace', function () { test('asRelativePath', () => { - const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/Applications/NewsWoWBot'), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/Applications/NewsWoWBot'), 0)], name: 'Test' }, new NullLogService()); 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', @@ -59,7 +64,7 @@ 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 = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); assertAsRelativePath(ws, input, input); @@ -68,20 +73,20 @@ suite('ExtHostWorkspace', function () { }); test('asRelativePath, no workspace', function () { - const ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService()); assertAsRelativePath(ws, '', ''); assertAsRelativePath(ws, '/foo/bar', '/foo/bar'); }); test('asRelativePath, multiple folders', function () { - const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService()); 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 = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); + const mrws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService()); assertAsRelativePath(mrws, '/Coding/One/file.txt', 'One/file.txt'); assertAsRelativePath(mrws, '/Coding/One/file.txt', 'One/file.txt', true); @@ -93,7 +98,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 = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const srws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0)], name: 'Test' }, new NullLogService()); 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); @@ -103,24 +108,24 @@ suite('ExtHostWorkspace', function () { }); test('getPath, legacy', function () { - let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); assert.equal(ws.getPath(), undefined); - ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService()); assert.equal(ws.getPath(), undefined); - ws = createExtHostWorkspace(new TestRPCProtocol(), undefined!, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), undefined!, new NullLogService()); assert.equal(ws.getPath(), undefined); - ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }, new NullLogService()); assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder'); - ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }, new NullLogService()); assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder'); }); test('WorkspaceFolder has name and index', function () { - const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService()); const [one, two] = ws.getWorkspaceFolders()!; @@ -139,7 +144,7 @@ suite('ExtHostWorkspace', function () { aWorkspaceFolderData(URI.file('/Coding/Two'), 1), aWorkspaceFolderData(URI.file('/Coding/Two/Nested'), 2) ] - }, new NullLogService(), new Counter()); + }, new NullLogService()); let folder = ws.getWorkspaceFolder(URI.file('/foo/bar')); assert.equal(folder, undefined); @@ -179,7 +184,7 @@ suite('ExtHostWorkspace', function () { }); test('Multiroot change event should have a delta, #29641', function (done) { - let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); let finished = false; const finish = (error?: any) => { @@ -242,7 +247,7 @@ suite('ExtHostWorkspace', function () { }); test('Multiroot change keeps existing workspaces live', function () { - let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService()); 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')] }); @@ -262,7 +267,7 @@ suite('ExtHostWorkspace', function () { }); test('updateWorkspaceFolders - invalid arguments', function () { - let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, null!, null!)); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 0)); @@ -271,7 +276,7 @@ suite('ExtHostWorkspace', function () { assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, -1, 0)); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, -1, -1)); - ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService()); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 1, 1)); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2)); @@ -289,11 +294,11 @@ suite('ExtHostWorkspace', function () { const protocol: IMainContext = { getProxy: () => { return undefined!; }, - set: undefined!, - assertRegistered: undefined! + set: () => { return undefined!; }, + assertRegistered: () => { } }; - const ws = createExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); // // Add one folder @@ -526,7 +531,7 @@ suite('ExtHostWorkspace', function () { } }; - let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService()); let sub = ws.onDidChangeWorkspace(e => { try { assert.throws(() => { @@ -549,7 +554,7 @@ suite('ExtHostWorkspace', function () { id: 'foo', name: 'Test', folders: [ aWorkspaceFolderData(URI.file('c:/Users/marek/Desktop/vsc_test/'), 0) ] - }, new NullLogService(), new Counter()); + }, new NullLogService()); assert.ok(ws.getWorkspaceFolder(URI.file('c:/Users/marek/Desktop/vsc_test/a.txt'))); assert.ok(ws.getWorkspaceFolder(URI.file('C:/Users/marek/Desktop/vsc_test/b.txt'))); @@ -583,7 +588,7 @@ suite('ExtHostWorkspace', function () { } }); - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); return ws.findFiles('foo', undefined, 10, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -604,7 +609,7 @@ suite('ExtHostWorkspace', function () { } }); - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), undefined, 10, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -625,7 +630,7 @@ suite('ExtHostWorkspace', function () { } }); - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null!, 10, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); @@ -643,7 +648,7 @@ suite('ExtHostWorkspace', function () { } }); - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); const token = CancellationToken.Cancelled; return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null!, 10, new ExtensionIdentifier('test'), token).then(() => { @@ -664,7 +669,7 @@ suite('ExtHostWorkspace', function () { } }); - const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService()); return ws.findFiles('', new RelativePattern(root, 'glob/**'), 10, new ExtensionIdentifier('test')).then(() => { assert(mainThreadCalled, 'mainThreadCalled'); }); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDiagnostics.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDiagnostics.test.ts index e01923222da..72f53943e04 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDiagnostics.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDiagnostics.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { MarkerService } from 'vs/platform/markers/common/markerService'; import { MainThreadDiagnostics } from 'vs/workbench/api/browser/mainThreadDiagnostics'; import { URI } from 'vs/base/common/uri'; +import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; suite('MainThreadDiagnostics', function () { @@ -19,7 +20,16 @@ suite('MainThreadDiagnostics', function () { test('clear markers on dispose', function () { - let diag = new MainThreadDiagnostics(null!, markerService); + let diag = new MainThreadDiagnostics(new class implements IExtHostContext { + remoteAuthority = ''; + assertRegistered() { } + set(v: any): any { return null; } + getProxy(): any { + return { + $acceptMarkersChange() { } + }; + } + }, markerService); diag.$changeMany('foo', [[URI.file('a'), [{ code: '666', diff --git a/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts b/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts index 9391351cdae..4eef6eb559c 100644 --- a/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts +++ b/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts @@ -7,9 +7,11 @@ import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyId import { CharCode } from 'vs/base/common/charCode'; import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { isThenable } from 'vs/base/common/async'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -export function SingleProxyRPCProtocol(thing: any): IExtHostContext { +export function SingleProxyRPCProtocol(thing: any): IExtHostContext & IExtHostRpcService { return { + _serviceBrand: undefined, remoteAuthority: null!, getProxy(): T { return thing; @@ -21,13 +23,14 @@ export function SingleProxyRPCProtocol(thing: any): IExtHostContext { }; } -export class TestRPCProtocol implements IExtHostContext { +export class TestRPCProtocol implements IExtHostContext, IExtHostRpcService { + public _serviceBrand = undefined; public remoteAuthority = null!; private _callCountValue: number = 0; private _idle?: Promise; - private _completeIdle: Function; + private _completeIdle?: Function; private readonly _locals: { [id: string]: any; }; private readonly _proxies: { [id: string]: any; }; 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 4b246719249..b23a042280b 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as minimist from 'minimist'; +import * as minimist from 'vscode-minimist'; import * as path from 'vs/base/common/path'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; 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 e08748c6364..ee857b6b8b3 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -13,7 +13,7 @@ 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 minimist from 'vscode-minimist'; import * as path from 'vs/base/common/path'; import { LocalSearchService } from 'vs/workbench/services/search/node/searchService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 48ebaf8e296..d182e4ca73c 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -84,6 +84,8 @@ import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/n import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; import { NodeTextFileService } from 'vs/workbench/services/textfile/node/textFileService'; import { Schemas } from 'vs/base/common/network'; +import { IProductService } from 'vs/platform/product/common/product'; +import product from 'vs/platform/product/node/product'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -665,6 +667,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { onDidAddGroup: Event = Event.None; onDidRemoveGroup: Event = Event.None; onDidMoveGroup: Event = Event.None; + onDidGroupIndexChange: Event = Event.None; onDidLayout: Event = Event.None; orientation: any; @@ -761,6 +764,7 @@ export class TestEditorGroup implements IEditorGroupView { disposed: boolean; editors: ReadonlyArray = []; label: string; + index: number; whenRestored: Promise = Promise.resolve(undefined); element: HTMLElement; minimumWidth: number; @@ -768,6 +772,9 @@ export class TestEditorGroup implements IEditorGroupView { minimumHeight: number; maximumHeight: number; + isEmpty = true; + isMinimized = false; + onWillDispose: Event = Event.None; onDidGroupChange: Event = Event.None; onWillCloseEditor: Event = Event.None; @@ -837,9 +844,8 @@ export class TestEditorGroup implements IEditorGroupView { throw new Error('not implemented'); } - isEmpty(): boolean { return true; } setActive(_isActive: boolean): void { } - setLabel(_label: string): void { } + notifyIndexChanged(_index: number): void { } dispose(): void { } toJSON(): object { return Object.create(null); } layout(_width: number, _height: number): void { } @@ -1334,12 +1340,12 @@ export class TestWindowsService implements IWindowsService { public windowCount = 1; - onWindowOpen: Event; - onWindowFocus: Event; - onWindowBlur: Event; - onWindowMaximize: Event; - onWindowUnmaximize: Event; - onRecentlyOpenedChange: Event; + readonly onWindowOpen: Event = Event.None; + readonly onWindowFocus: Event = Event.None; + readonly onWindowBlur: Event = Event.None; + readonly onWindowMaximize: Event = Event.None; + readonly onWindowUnmaximize: Event = Event.None; + readonly onRecentlyOpenedChange: Event = Event.None; isFocused(_windowId: number): Promise { return Promise.resolve(false); @@ -1627,3 +1633,5 @@ export class RemoteFileSystemProvider implements IFileSystemProvider { private toFileResource(resource: URI): URI { return resource.with({ scheme: Schemas.file, authority: '' }); } } + +export const productService: IProductService = { _serviceBrand: undefined, ...product }; diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.common.main.ts similarity index 55% rename from src/vs/workbench/workbench.main.ts rename to src/vs/workbench/workbench.common.main.ts index ba1ea7a2075..198e68e65bb 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -3,17 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -//#region --- workbench/editor core +//#region --- editor/workbench core import 'vs/editor/editor.all'; import 'vs/workbench/api/browser/extensionHost.contribution'; - -import 'vs/workbench/electron-browser/main.contribution'; import 'vs/workbench/browser/workbench.contribution'; -import 'vs/workbench/electron-browser/main'; - //#endregion @@ -39,139 +35,6 @@ import 'vs/workbench/api/browser/viewsExtensionPoint'; //#endregion -//#region --- workbench services -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IMenuService } from 'vs/platform/actions/common/actions'; -import { MenuService } from 'vs/platform/actions/common/menuService'; -import { IListService, ListService } from 'vs/platform/list/browser/listService'; -import { OpenerService } from 'vs/editor/browser/services/openerService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; -import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; -import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; -import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { MarkerService } from 'vs/platform/markers/common/markerService'; -import { IDownloadService } from 'vs/platform/download/common/download'; -import { DownloadService } from 'vs/platform/download/common/downloadService'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; -import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { AccessibilityService } from 'vs/workbench/services/accessibility/node/accessibilityService'; -import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; -import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { RequestService } from 'vs/platform/request/browser/requestService'; -import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -import { LocalizationsService } from 'vs/platform/localizations/electron-browser/localizationsService'; -import { ISharedProcessService, SharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { IProductService } from 'vs/platform/product/common/product'; -import { ProductService } from 'vs/platform/product/node/productService'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; -import { IUpdateService } from 'vs/platform/update/common/update'; -import { UpdateService } from 'vs/platform/update/electron-browser/updateService'; -import { IIssueService } from 'vs/platform/issue/common/issue'; -import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; -import { IMenubarService } from 'vs/platform/menubar/common/menubar'; -import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; -import { IURLService } from 'vs/platform/url/common/url'; -import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; -import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { TunnelService } from 'vs/workbench/services/remote/node/tunnelService'; -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; -import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; - -import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; -import 'vs/workbench/services/integrity/node/integrityService'; -import 'vs/workbench/services/keybinding/common/keybindingEditing'; -import 'vs/workbench/services/textMate/electron-browser/textMateService'; -import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; -import 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; -import 'vs/workbench/services/decorations/browser/decorationsService'; -import 'vs/workbench/services/search/node/searchService'; -import 'vs/workbench/services/progress/browser/progressService'; -import 'vs/workbench/services/editor/browser/codeEditorService'; -import 'vs/workbench/services/extensions/electron-browser/extensionHostDebugService'; -import 'vs/workbench/services/preferences/browser/preferencesService'; -import 'vs/workbench/services/output/node/outputChannelModelService'; -import 'vs/workbench/services/configuration/common/jsonEditingService'; -import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import 'vs/workbench/services/textfile/node/textFileService'; -import 'vs/workbench/services/dialogs/browser/fileDialogService'; -import 'vs/workbench/services/dialogs/electron-browser/dialogService'; -import 'vs/workbench/services/editor/browser/editorService'; -import 'vs/workbench/services/history/browser/history'; -import 'vs/workbench/services/activity/browser/activityService'; -import 'vs/workbench/browser/parts/views/views'; -import 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import 'vs/workbench/services/keybinding/electron-browser/keybinding.contribution'; -import 'vs/workbench/services/keybinding/browser/keybindingService'; -import 'vs/workbench/services/untitled/common/untitledEditorService'; -import 'vs/workbench/services/textfile/common/textResourcePropertiesService'; -import 'vs/workbench/services/mode/common/workbenchModeService'; -import 'vs/workbench/services/commands/common/commandService'; -import 'vs/workbench/services/themes/browser/workbenchThemeService'; -import 'vs/workbench/services/extensions/electron-browser/extensionService'; -import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; -import 'vs/workbench/services/label/common/labelService'; -import 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; -import 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; -import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; -import 'vs/workbench/services/notification/common/notificationService'; -import 'vs/workbench/services/window/electron-browser/windowService'; -import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; -import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; -import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/node/extensionManagementService'; - -registerSingleton(IExtensionManagementService, ExtensionManagementService); -registerSingleton(IBackupFileService, BackupFileService); -registerSingleton(IMenuService, MenuService, true); -registerSingleton(IListService, ListService, true); -registerSingleton(IOpenerService, OpenerService, true); -registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); -registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); -registerSingleton(IMarkerService, MarkerService, true); -registerSingleton(IDownloadService, DownloadService, true); -registerSingleton(IClipboardService, ClipboardService, true); -registerSingleton(IContextKeyService, ContextKeyService); -registerSingleton(IModelService, ModelServiceImpl, true); -registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); -registerSingleton(IAccessibilityService, AccessibilityService, true); -registerSingleton(IContextViewService, ContextViewService, true); -registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); -registerSingleton(IRequestService, RequestService, true); -registerSingleton(ILifecycleService, LifecycleService); -registerSingleton(ILocalizationsService, LocalizationsService); -registerSingleton(ISharedProcessService, SharedProcessService, true); -registerSingleton(IProductService, ProductService, true); -registerSingleton(IWindowsService, WindowsService); -registerSingleton(IUpdateService, UpdateService); -registerSingleton(IIssueService, IssueService); -registerSingleton(IWorkspacesService, WorkspacesService); -registerSingleton(IMenubarService, MenubarService); -registerSingleton(IURLService, RelayURLService); -registerSingleton(ITunnelService, TunnelService, true); -registerSingleton(ICredentialsService, KeytarCredentialsService, true); -registerSingleton(IWorkspaceStatsService, WorkspaceStatsService, true); - -//#endregion - - //#region --- workbench parts import 'vs/workbench/browser/parts/quickinput/quickInput'; @@ -182,6 +45,72 @@ import 'vs/workbench/browser/parts/activitybar/activitybarPart'; import 'vs/workbench/browser/parts/panel/panelPart'; import 'vs/workbench/browser/parts/sidebar/sidebarPart'; import 'vs/workbench/browser/parts/statusbar/statusbarPart'; +import 'vs/workbench/browser/parts/views/views'; + +//#endregion + + +//#region --- workbench services + +import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; +import 'vs/workbench/services/keybinding/common/keybindingEditing'; +import 'vs/workbench/services/decorations/browser/decorationsService'; +import 'vs/workbench/services/progress/browser/progressService'; +import 'vs/workbench/services/editor/browser/codeEditorService'; +import 'vs/workbench/services/preferences/browser/preferencesService'; +import 'vs/workbench/services/configuration/common/jsonEditingService'; +import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; +import 'vs/workbench/services/dialogs/browser/fileDialogService'; +import 'vs/workbench/services/editor/browser/editorService'; +import 'vs/workbench/services/history/browser/history'; +import 'vs/workbench/services/activity/browser/activityService'; +import 'vs/workbench/services/keybinding/browser/keybindingService'; +import 'vs/workbench/services/untitled/common/untitledEditorService'; +import 'vs/workbench/services/textfile/common/textResourcePropertiesService'; +import 'vs/workbench/services/mode/common/workbenchModeService'; +import 'vs/workbench/services/commands/common/commandService'; +import 'vs/workbench/services/themes/browser/workbenchThemeService'; +import 'vs/workbench/services/label/common/labelService'; +import 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; +import 'vs/workbench/services/notification/common/notificationService'; + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; +import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IListService, ListService } from 'vs/platform/list/browser/listService'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; +import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { MarkerService } from 'vs/platform/markers/common/markerService'; +import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; +import { IMenuService } from 'vs/platform/actions/common/actions'; +import { MenuService } from 'vs/platform/actions/common/menuService'; +import { IDownloadService } from 'vs/platform/download/common/download'; +import { DownloadService } from 'vs/platform/download/common/downloadService'; +import { OpenerService } from 'vs/editor/browser/services/openerService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; + +registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); +registerSingleton(IContextViewService, ContextViewService, true); +registerSingleton(IListService, ListService, true); +registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); +registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); +registerSingleton(IMarkerService, MarkerService, true); +registerSingleton(IContextKeyService, ContextKeyService); +registerSingleton(IModelService, ModelServiceImpl, true); +registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); +registerSingleton(IMenuService, MenuService, true); +registerSingleton(IDownloadService, DownloadService, true); +registerSingleton(IOpenerService, OpenerService, true); //#endregion @@ -194,19 +123,13 @@ import 'vs/workbench/services/files/common/workspaceWatcher'; // Telemetry import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; -// Localizations -import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; - // Preferences import 'vs/workbench/contrib/preferences/browser/preferences.contribution'; import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; -import { IPreferencesSearchService } from 'vs/workbench/contrib/preferences/common/preferences'; -import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/browser/preferencesSearch'; -registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); +import 'vs/workbench/contrib/preferences/browser/preferencesSearch'; // Logs import 'vs/workbench/contrib/logs/common/logs.contribution'; -import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; // Quick Open Handlers import 'vs/workbench/contrib/quickopen/browser/quickopen.contribution'; @@ -219,12 +142,6 @@ import 'vs/workbench/contrib/files/browser/files.contribution'; // Backup import 'vs/workbench/contrib/backup/common/backup.contribution'; -// Stats -import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; - -// Rapid Render Splash -import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; - // Search import 'vs/workbench/contrib/search/browser/search.contribution'; import 'vs/workbench/contrib/search/browser/searchView'; @@ -240,7 +157,6 @@ import 'vs/workbench/contrib/debug/browser/debugQuickOpen'; import 'vs/workbench/contrib/debug/browser/debugEditorContribution'; import 'vs/workbench/contrib/debug/browser/repl'; import 'vs/workbench/contrib/debug/browser/debugViewlet'; -import 'vs/workbench/contrib/debug/node/debugHelperService'; // Markers import 'vs/workbench/contrib/markers/browser/markers.contribution'; @@ -253,11 +169,9 @@ import 'vs/workbench/contrib/url/common/url.contribution'; // Webview import 'vs/workbench/contrib/webview/browser/webview.contribution'; -import 'vs/workbench/contrib/webview/electron-browser/webview.contribution'; // Extensions Management import 'vs/workbench/contrib/extensions/browser/extensions.contribution'; -import 'vs/workbench/contrib/extensions/electron-browser/extensions.contribution'; import 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; import 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; @@ -267,32 +181,26 @@ import 'vs/workbench/contrib/output/browser/outputPanel'; // Terminal import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; -import 'vs/workbench/contrib/terminal/electron-browser/terminal.contribution'; import 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; import 'vs/workbench/contrib/terminal/browser/terminalPanel'; // Relauncher -import 'vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution'; +import 'vs/workbench/contrib/relauncher/common/relauncher.contribution'; // Tasks import 'vs/workbench/contrib/tasks/browser/task.contribution'; -import { TaskService } from 'vs/workbench/contrib/tasks/electron-browser/taskService'; -import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; -registerSingleton(ITaskService, TaskService, true); // Remote import 'vs/workbench/contrib/remote/common/remote.contribution'; -import 'vs/workbench/contrib/remote/electron-browser/remote.contribution'; +import 'vs/workbench/contrib/remote/browser/remote'; // Emmet import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; // CodeEditor Contributions import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution'; -import 'vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution'; // Execution -import 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; import 'vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution'; // Snippets @@ -305,34 +213,15 @@ 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/browser/feedback.contribution'; - -// Update -import 'vs/workbench/contrib/update/electron-browser/update.contribution'; - -// Surveys -import 'vs/workbench/contrib/surveys/electron-browser/nps.contribution'; -import 'vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution'; - -// Performance -import 'vs/workbench/contrib/performance/electron-browser/performance.contribution'; - -// CLI -import 'vs/workbench/contrib/cli/node/cli.contribution'; - -// Themes Support +// Themes import 'vs/workbench/contrib/themes/browser/themes.contribution'; -import 'vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution'; // Watermark import 'vs/workbench/contrib/watermark/browser/watermark'; // Welcome import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution'; -import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; -import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; // Call Hierarchy import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; @@ -341,10 +230,9 @@ import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; import 'vs/workbench/contrib/outline/browser/outline.contribution'; // Experiments -import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution'; +import 'vs/workbench/contrib/experiments/browser/experiments.contribution'; -// Issues -import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; -import { IWorkspaceStatsService, WorkspaceStatsService } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; +// Send a Smile +import 'vs/workbench/contrib/feedback/browser/feedback.contribution'; //#endregion diff --git a/src/vs/workbench/workbench.main.css b/src/vs/workbench/workbench.desktop.main.css similarity index 100% rename from src/vs/workbench/workbench.main.css rename to src/vs/workbench/workbench.desktop.main.css diff --git a/src/vs/workbench/workbench.main.nls.js b/src/vs/workbench/workbench.desktop.main.nls.js similarity index 100% rename from src/vs/workbench/workbench.main.nls.js rename to src/vs/workbench/workbench.desktop.main.nls.js diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts new file mode 100644 index 00000000000..b44e341f9c1 --- /dev/null +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -0,0 +1,156 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +// ####################################################################### +// ### ### +// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ### +// ### ### +// ####################################################################### + + +//#region --- workbench common + +import 'vs/workbench/workbench.common.main'; + +//#endregion + + +//#region --- workbench (desktop main) + +import 'vs/workbench/electron-browser/desktop.contribution'; +import 'vs/workbench/electron-browser/desktop.main'; + +//#endregion + + +//#region --- workbench services +import 'vs/workbench/services/integrity/node/integrityService'; +import 'vs/workbench/services/textMate/electron-browser/textMateService'; +import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; +import 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; +import 'vs/workbench/services/search/node/searchService'; +import 'vs/workbench/services/output/node/outputChannelModelService'; +import 'vs/workbench/services/textfile/node/textFileService'; +import 'vs/workbench/services/dialogs/electron-browser/dialogService'; +import 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; +import 'vs/workbench/services/keybinding/electron-browser/keybinding.contribution'; +import 'vs/workbench/services/extensions/electron-browser/extensionService'; +import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; +import 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; +import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; +import 'vs/workbench/services/window/electron-browser/windowService'; +import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; +import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; +import 'vs/workbench/services/extensionManagement/node/extensionManagementService'; +import 'vs/workbench/services/accessibility/node/accessibilityService'; +import 'vs/workbench/services/remote/node/tunnelService'; +import 'vs/workbench/services/backup/node/backupFileService'; +import 'vs/workbench/services/credentials/node/credentialsService'; + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { RequestService } from 'vs/platform/request/browser/requestService'; +import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { LocalizationsService } from 'vs/platform/localizations/electron-browser/localizationsService'; +import { ISharedProcessService, SharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; +import { IUpdateService } from 'vs/platform/update/common/update'; +import { UpdateService } from 'vs/platform/update/electron-browser/updateService'; +import { IIssueService } from 'vs/platform/issue/node/issue'; +import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; +import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; +import { IMenubarService } from 'vs/platform/menubar/node/menubar'; +import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; +import { IURLService } from 'vs/platform/url/common/url'; +import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; +import { StaticExtensionsService, IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; + +registerSingleton(IClipboardService, ClipboardService, true); +registerSingleton(IRequestService, RequestService, true); +registerSingleton(ILifecycleService, LifecycleService); +registerSingleton(ILocalizationsService, LocalizationsService); +registerSingleton(ISharedProcessService, SharedProcessService, true); +registerSingleton(IWindowsService, WindowsService); +registerSingleton(IUpdateService, UpdateService); +registerSingleton(IIssueService, IssueService); +registerSingleton(IWorkspacesService, WorkspacesService); +registerSingleton(IMenubarService, MenubarService); +registerSingleton(IURLService, RelayURLService); +registerSingleton(IStaticExtensionsService, class extends StaticExtensionsService { constructor() { super([]); } }); + +//#endregion + + +//#region --- workbench contributions + +// Localizations +import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; + +// Logs +import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; + +// Stats +import 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; +import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; + +// Rapid Render Splash +import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; + +// Debug +import 'vs/workbench/contrib/debug/node/debugHelperService'; +import 'vs/workbench/contrib/debug/electron-browser/extensionHostDebugService'; + +// Webview +import 'vs/workbench/contrib/webview/electron-browser/webview.contribution'; + +// Extensions Management +import 'vs/workbench/contrib/extensions/electron-browser/extensions.contribution'; + +// Terminal +import 'vs/workbench/contrib/terminal/electron-browser/terminal.contribution'; + +// Remote +import 'vs/workbench/contrib/remote/electron-browser/remote.contribution'; + +// CodeEditor Contributions +import 'vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution'; + +// Execution +import 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; + +// Update +import 'vs/workbench/contrib/update/electron-browser/update.contribution'; + +// Surveys +import 'vs/workbench/contrib/surveys/electron-browser/nps.contribution'; +import 'vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution'; + +// Performance +import 'vs/workbench/contrib/performance/electron-browser/performance.contribution'; + +// CLI +import 'vs/workbench/contrib/cli/node/cli.contribution'; + +// Themes Support +import 'vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution'; + +// Welcome +import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; +import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; + +// Issues +import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; + +// Tasks +import 'vs/workbench/contrib/tasks/electron-browser/taskService'; + +//#endregion diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index b1be3bab964..dd84853e87a 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -7,8 +7,9 @@ import 'vs/workbench/workbench.web.main'; import { main } from 'vs/workbench/browser/web.main'; import { UriComponents } from 'vs/base/common/uri'; import { IFileSystemProvider } from 'vs/platform/files/common/files'; -import { IRequestOptions, IRequestContext } from 'vs/platform/request/common/request'; import { IWebSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory'; +import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/credentialsService'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; export interface IWorkbenchConstructionOptions { @@ -45,16 +46,25 @@ export interface IWorkbenchConstructionOptions { */ userDataProvider?: IFileSystemProvider; - /** - * Experimental: Optional request handler to handle http requests. - * In case not provided, workbench uses XMLHttpRequest. - */ - requestHandler?: (requestOptions: IRequestOptions) => Promise; - /** * A factory for web sockets. */ webSocketFactory?: IWebSocketFactory; + + /** + * Experimental: Whether to enable the smoke test driver. + */ + driver?: boolean; + + /** + * Experimental: The credentials provider to store and retrieve secrets. + */ + credentialsProvider?: ICredentialsProvider; + + /** + * Experimental: Add static extensions that cannot be uninstalled but only be disabled. + */ + staticExtensions?: { packageJSON: IExtensionManifest, extensionLocation: UriComponents }[]; } /** diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 4d5cf7bfbf9..20db7d07378 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -3,357 +3,92 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -//#region --- workbench/editor core -import 'vs/editor/editor.all'; +// ####################################################################### +// ### ### +// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ### +// ### ### +// ####################################################################### -import 'vs/workbench/api/browser/extensionHost.contribution'; -import 'vs/workbench/browser/workbench.contribution'; +//#region --- workbench common + +import 'vs/workbench/workbench.common.main'; + +//#endregion + + +//#region --- workbench (web main) import 'vs/workbench/browser/web.main'; //#endregion -//#region --- workbench actions - -import 'vs/workbench/browser/actions/layoutActions'; -import 'vs/workbench/browser/actions/windowActions'; -import 'vs/workbench/browser/actions/developerActions'; -import 'vs/workbench/browser/actions/listCommands'; -import 'vs/workbench/browser/actions/navigationActions'; -import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; -import 'vs/workbench/browser/parts/quickinput/quickInputActions'; - -//#endregion - - -//#region --- API Extension Points - -import 'vs/workbench/api/common/menusExtensionPoint'; -import 'vs/workbench/api/common/configurationExtensionPoint'; -import 'vs/workbench/api/browser/viewsExtensionPoint'; - -//#endregion - - //#region --- workbench services +import 'vs/workbench/services/integrity/browser/integrityService'; +import 'vs/workbench/services/textMate/browser/textMateService'; +import 'vs/workbench/services/search/common/searchService'; +import 'vs/workbench/services/output/common/outputChannelModelService'; +import 'vs/workbench/services/textfile/browser/textFileService'; +import 'vs/workbench/services/keybinding/browser/keymapService'; +import 'vs/workbench/services/extensions/browser/extensionService'; +import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; +import 'vs/workbench/services/telemetry/browser/telemetryService'; +import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; +import 'vs/workbench/services/credentials/browser/credentialsService'; +import 'vs/workbench/browser/web.simpleservices'; + import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IMenuService } from 'vs/platform/actions/common/actions'; -import { MenuService } from 'vs/platform/actions/common/menuService'; -import { IListService, ListService } from 'vs/platform/list/browser/listService'; -import { OpenerService } from 'vs/editor/browser/services/openerService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; -import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; -import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; -import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { MarkerService } from 'vs/platform/markers/common/markerService'; -import { IDownloadService } from 'vs/platform/download/common/download'; -import { DownloadService } from 'vs/platform/download/common/downloadService'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { RequestService } from 'vs/workbench/services/request/browser/requestService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; -import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; -import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; -import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { BrowserLifecycleService } from 'vs/platform/lifecycle/browser/lifecycleService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { DialogService } from 'vs/platform/dialogs/browser/dialogService'; -// import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -// import { LocalizationsService } from 'vs/platform/localizations/electron-browser/localizationsService'; -// import { ISharedProcessService, SharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -// import { IWindowsService } from 'vs/platform/windows/common/windows'; -// import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; -// import { IUpdateService } from 'vs/platform/update/common/update'; -// import { UpdateService } from 'vs/platform/update/electron-browser/updateService'; -// import { IIssueService } from 'vs/platform/issue/common/issue'; -// import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; -// import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -// import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; -// import { IMenubarService } from 'vs/platform/menubar/common/menubar'; -// import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; -// import { IURLService } from 'vs/platform/url/common/url'; -// import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; -// import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -// import { TunnelService } from 'vs/workbench/services/remote/node/tunnelService'; -// import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; -// import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; -import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; -// import 'vs/workbench/services/integrity/node/integrityService'; -import 'vs/workbench/services/keybinding/common/keybindingEditing'; -import 'vs/workbench/services/textMate/browser/textMateService'; -// import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; -// import 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; -import 'vs/workbench/services/decorations/browser/decorationsService'; -import 'vs/workbench/services/search/common/searchService'; -import 'vs/workbench/services/progress/browser/progressService'; -import 'vs/workbench/services/editor/browser/codeEditorService'; -// import 'vs/workbench/services/extensions/electron-browser/extensionHostDebugService'; -import 'vs/workbench/services/preferences/browser/preferencesService'; -import 'vs/workbench/services/output/common/outputChannelModelService'; -import 'vs/workbench/services/configuration/common/jsonEditingService'; -import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import 'vs/workbench/services/textfile/browser/textFileService'; -import 'vs/workbench/services/dialogs/browser/fileDialogService'; -// import 'vs/workbench/services/dialogs/electron-browser/dialogService'; -import 'vs/workbench/services/editor/browser/editorService'; -import 'vs/workbench/services/history/browser/history'; -import 'vs/workbench/services/activity/browser/activityService'; -import 'vs/workbench/browser/parts/views/views'; -import 'vs/workbench/services/keybinding/browser/keymapService'; -import 'vs/workbench/services/keybinding/browser/keybindingService'; -import 'vs/workbench/services/untitled/common/untitledEditorService'; -import 'vs/workbench/services/textfile/common/textResourcePropertiesService'; -import 'vs/workbench/services/mode/common/workbenchModeService'; -import 'vs/workbench/services/commands/common/commandService'; -import 'vs/workbench/services/themes/browser/workbenchThemeService'; -import 'vs/workbench/services/extensions/browser/extensionService'; -// import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; -import 'vs/workbench/services/label/common/labelService'; -import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; -import 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; -// import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; -import 'vs/workbench/services/notification/common/notificationService'; -// import 'vs/workbench/services/window/electron-browser/windowService'; -import 'vs/workbench/services/telemetry/browser/telemetryService'; -import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; -import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; -import 'vs/workbench/browser/web.simpleservices'; - +registerSingleton(IRequestService, RequestService, true); registerSingleton(IExtensionManagementService, ExtensionManagementService); registerSingleton(IBackupFileService, BackupFileService); registerSingleton(IDialogService, DialogService, true); -registerSingleton(IMenuService, MenuService, true); -registerSingleton(IListService, ListService, true); -registerSingleton(IOpenerService, OpenerService, true); -registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); -registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); -registerSingleton(IMarkerService, MarkerService, true); -registerSingleton(IDownloadService, DownloadService, true); registerSingleton(IClipboardService, BrowserClipboardService, true); -registerSingleton(IContextKeyService, ContextKeyService); -registerSingleton(IModelService, ModelServiceImpl, true); -registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); -registerSingleton(IContextViewService, ContextViewService, true); -registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); registerSingleton(ILifecycleService, BrowserLifecycleService); -// registerSingleton(ILocalizationsService, LocalizationsService); -// registerSingleton(ISharedProcessService, SharedProcessService, true); -// registerSingleton(IWindowsService, WindowsService); -// registerSingleton(IUpdateService, UpdateService); -// registerSingleton(IIssueService, IssueService); -// registerSingleton(IWorkspacesService, WorkspacesService); -// registerSingleton(IMenubarService, MenubarService); -// registerSingleton(IURLService, RelayURLService); -// registerSingleton(ITunnelService, TunnelService, true); -// registerSingleton(ICredentialsService, KeytarCredentialsService, true); registerSingleton(IContextMenuService, ContextMenuService); //#endregion -//#region --- workbench parts - -import 'vs/workbench/browser/parts/quickinput/quickInput'; -import 'vs/workbench/browser/parts/quickopen/quickOpenController'; -import 'vs/workbench/browser/parts/titlebar/titlebarPart'; -import 'vs/workbench/browser/parts/editor/editorPart'; -import 'vs/workbench/browser/parts/activitybar/activitybarPart'; -import 'vs/workbench/browser/parts/panel/panelPart'; -import 'vs/workbench/browser/parts/sidebar/sidebarPart'; -import 'vs/workbench/browser/parts/statusbar/statusbarPart'; - -//#endregion - - //#region --- workbench contributions // Resource Service Worker import 'vs/workbench/contrib/resources/browser/resourceServiceWorkerClient'; -// Workspace File Watching -import 'vs/workbench/services/files/common/workspaceWatcher'; - -// Telemetry -import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; - -// Localizations -// import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; - // Preferences -import 'vs/workbench/contrib/preferences/browser/preferences.contribution'; -import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; import 'vs/workbench/contrib/preferences/browser/keyboardLayoutPicker'; -import { IPreferencesSearchService } from 'vs/workbench/contrib/preferences/common/preferences'; -import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/browser/preferencesSearch'; -registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); - -// Logs -import 'vs/workbench/contrib/logs/common/logs.contribution'; - -// Quick Open Handlers -import 'vs/workbench/contrib/quickopen/browser/quickopen.contribution'; - -// Explorer -import 'vs/workbench/contrib/files/browser/explorerViewlet'; -import 'vs/workbench/contrib/files/browser/fileActions.contribution'; -import 'vs/workbench/contrib/files/browser/files.contribution'; - -// Backup -import 'vs/workbench/contrib/backup/common/backup.contribution'; - -// Stats -// import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; - -// Rapid Render Splash -// import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; - -// Search -import 'vs/workbench/contrib/search/browser/search.contribution'; -import 'vs/workbench/contrib/search/browser/searchView'; -import 'vs/workbench/contrib/search/browser/openAnythingHandler'; - -// SCM -import 'vs/workbench/contrib/scm/browser/scm.contribution'; -import 'vs/workbench/contrib/scm/browser/scmViewlet'; // Debug -import 'vs/workbench/contrib/debug/browser/debug.contribution'; -import 'vs/workbench/contrib/debug/browser/debugQuickOpen'; -import 'vs/workbench/contrib/debug/browser/debugEditorContribution'; -import 'vs/workbench/contrib/debug/browser/repl'; -import 'vs/workbench/contrib/debug/browser/debugViewlet'; -import 'vs/workbench/contrib/debug/browser/debugHelperService'; - -// Markers -import 'vs/workbench/contrib/markers/browser/markers.contribution'; - -// Comments -import 'vs/workbench/contrib/comments/browser/comments.contribution'; - -// URL Support -import 'vs/workbench/contrib/url/common/url.contribution'; +import 'vs/workbench/contrib/debug/browser/extensionHostDebugService'; // Webview -import 'vs/workbench/contrib/webview/browser/webview.contribution'; - -import { IWebviewService } from 'vs/workbench/contrib/webview/common/webview'; -import { WebviewService } from 'vs/workbench/contrib/webview/browser/webviewService'; -import { IWebviewEditorService, WebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; -registerSingleton(IWebviewService, WebviewService, true); -registerSingleton(IWebviewEditorService, WebviewEditorService, true); - -// Extensions Management -import 'vs/workbench/contrib/extensions/browser/extensions.contribution'; -import 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; -import 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; - -// Output Panel -import 'vs/workbench/contrib/output/browser/output.contribution'; -import 'vs/workbench/contrib/output/browser/outputPanel'; +import 'vs/workbench/contrib/webview/browser/webviewService'; +import 'vs/workbench/contrib/webview/browser/webviewEditorService'; // Terminal -import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; -// import 'vs/workbench/contrib/terminal/electron-browser/terminal.contribution'; -import 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; -import 'vs/workbench/contrib/terminal/browser/terminalPanel'; - -import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal'; -import { TerminalNativeService } from 'vs/workbench/contrib/terminal/browser/terminalNativeService'; -import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminalInstanceService'; -registerSingleton(ITerminalNativeService, TerminalNativeService, true); -registerSingleton(ITerminalInstanceService, TerminalInstanceService, true); - -// Relauncher -// import 'vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution'; +import 'vs/workbench/contrib/terminal/browser/terminalNativeService'; +import 'vs/workbench/contrib/terminal/browser/terminalInstanceService'; // Tasks -import 'vs/workbench/contrib/tasks/browser/task.contribution'; -import { TaskService } from 'vs/workbench/contrib/tasks/browser/taskService'; -import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; -registerSingleton(ITaskService, TaskService, true); - -// Remote -import 'vs/workbench/contrib/remote/common/remote.contribution'; -// import 'vs/workbench/contrib/remote/electron-browser/remote.contribution'; - -// Emmet -import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; - -// CodeEditor Contributions -import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution'; -// import 'vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution'; - -// External terminal -import 'vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution'; - -// Snippets -import 'vs/workbench/contrib/snippets/browser/snippets.contribution'; -import 'vs/workbench/contrib/snippets/browser/snippetsService'; -import 'vs/workbench/contrib/snippets/browser/insertSnippet'; -import 'vs/workbench/contrib/snippets/browser/configureSnippets'; -import 'vs/workbench/contrib/snippets/browser/tabCompletion'; - -// Formatter Help -import 'vs/workbench/contrib/format/browser/format.contribution'; - -// Send a Smile -// import 'vs/workbench/contrib/feedback/browser/feedback.contribution'; - -// Update -// import 'vs/workbench/contrib/update/electron-browser/update.contribution'; - -// Surveys -// import 'vs/workbench/contrib/surveys/electron-browser/nps.contribution'; -// import 'vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution'; - -// Performance -// import 'vs/workbench/contrib/performance/electron-browser/performance.contribution'; - -// CLI -// import 'vs/workbench/contrib/cli/node/cli.contribution'; - -// Themes Support -import 'vs/workbench/contrib/themes/browser/themes.contribution'; -// import 'vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution'; - -// Watermark -import 'vs/workbench/contrib/watermark/browser/watermark'; - -// Welcome -import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution'; -// import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; -import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; -// import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; - -// Call Hierarchy -import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; - -// Outline -import 'vs/workbench/contrib/outline/browser/outline.contribution'; - -// Experiments -// import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution'; - -// Issues -// import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; +import 'vs/workbench/contrib/tasks/browser/taskService'; //#endregion diff --git a/test/smoke/package.json b/test/smoke/package.json index 11b5662b207..185fc07d64f 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -18,11 +18,12 @@ "@types/mocha": "2.2.41", "@types/ncp": "2.0.1", "@types/node": "^10.14.8", + "@types/puppeteer": "^1.19.0", "@types/rimraf": "2.0.2", "@types/webdriverio": "4.6.1", "concurrently": "^3.5.1", "cpx": "^1.5.0", - "electron": "4.2.7", + "electron": "4.2.9", "htmlparser2": "^3.9.2", "mkdirp": "^0.5.1", "mocha": "^5.2.0", diff --git a/test/smoke/src/application.ts b/test/smoke/src/application.ts index 472d081ec4b..60ed794ae9a 100644 --- a/test/smoke/src/application.ts +++ b/test/smoke/src/application.ts @@ -124,7 +124,9 @@ export class Application { verbose: this.options.verbose, log: this.options.log, extraArgs, - remote: this.options.remote + remote: this.options.remote, + web: this.options.web, + headless: this.options.headless }); this._workbench = new Workbench(this._code, this.userDataPath); @@ -140,7 +142,7 @@ export class Application { await this.code.waitForElement('.monaco-workbench'); if (this.remote) { - await this.code.waitForElement('.monaco-workbench .statusbar-item[title="Editing on TestResolver"]'); + await this.code.waitForElement('.monaco-workbench .statusbar-item[id="status.host"]'); } // wait a bit, since focus might be stolen off widgets diff --git a/test/smoke/src/areas/preferences/preferences.test.ts b/test/smoke/src/areas/preferences/preferences.test.ts index f1eb589b118..c7a1ce51475 100644 --- a/test/smoke/src/areas/preferences/preferences.test.ts +++ b/test/smoke/src/areas/preferences/preferences.test.ts @@ -34,4 +34,4 @@ export function setup() { await app.workbench.settingsEditor.clearUserSettings(); }); }); -} \ No newline at end of file +} diff --git a/test/smoke/src/areas/preferences/settings.ts b/test/smoke/src/areas/preferences/settings.ts index cebd2f51497..de7bd1ffb43 100644 --- a/test/smoke/src/areas/preferences/settings.ts +++ b/test/smoke/src/areas/preferences/settings.ts @@ -30,7 +30,7 @@ export class SettingsEditor { async clearUserSettings(): Promise { const settingsPath = path.join(this.userDataPath, 'User', 'settings.json'); - await new Promise((c, e) => fs.writeFile(settingsPath, '{}', 'utf8', err => err ? e(err) : c())); + await new Promise((c, e) => fs.writeFile(settingsPath, '{\n}', 'utf8', err => err ? e(err) : c())); await this.openSettings(); await this.editor.waitForEditorContents('settings.json', c => c === '{}'); @@ -39,4 +39,4 @@ export class SettingsEditor { private async openSettings(): Promise { await this.quickopen.runCommand('Preferences: Open Settings (JSON)'); } -} \ No newline at end of file +} diff --git a/test/smoke/src/areas/statusbar/statusbar.ts b/test/smoke/src/areas/statusbar/statusbar.ts index 66e1186fd4c..67a6e0a1af0 100644 --- a/test/smoke/src/areas/statusbar/statusbar.ts +++ b/test/smoke/src/areas/statusbar/statusbar.ts @@ -60,9 +60,9 @@ export class StatusBar { case StatusBarElement.LANGUAGE_STATUS: return `${this.mainSelector} ${this.rightSelector}[title="Select Language Mode"]`; case StatusBarElement.FEEDBACK_ICON: - return `${this.mainSelector} ${this.rightSelector} .monaco-dropdown.send-feedback`; + return `${this.mainSelector} .statusbar-item.right[id="status.feedback"]`; default: throw new Error(element); } } -} \ No newline at end of file +} diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index 6d0506144f9..85e593745d2 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -12,7 +12,7 @@ export function setup() { await app.workbench.editors.newUntitledFile(); const untitled = 'Untitled-1'; - const textToTypeInUntitled = 'Hello, Unitled Code'; + const textToTypeInUntitled = 'Hello, Untitled Code'; await app.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled); const readmeMd = 'readme.md'; @@ -30,4 +30,4 @@ export function setup() { await app.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); }); }); -} \ No newline at end of file +} diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 55c2f74a362..3f6633c69c7 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -39,7 +39,7 @@ export function setup(stableCodePath: string, testDataPath: string) { insiderOptions.userDataDir = userDataDir; const insidersApp = new Application(insiderOptions); - await insidersApp!.start(false /* not expecting walkthrough parth */); + await insidersApp!.start(false /* not expecting walkthrough path */); // Verify 3 editors are open await insidersApp.workbench.editors.waitForEditorFocus('Untitled-1'); @@ -66,7 +66,7 @@ export function setup(stableCodePath: string, testDataPath: string) { await stableApp.workbench.editors.newUntitledFile(); const untitled = 'Untitled-1'; - const textToTypeInUntitled = 'Hello, Unitled Code'; + const textToTypeInUntitled = 'Hello, Untitled Code'; await stableApp.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled); const readmeMd = 'readme.md'; @@ -80,7 +80,7 @@ export function setup(stableCodePath: string, testDataPath: string) { insiderOptions.userDataDir = userDataDir; const insidersApp = new Application(insiderOptions); - await insidersApp!.start(false /* not expecting walkthrough parth */); + await insidersApp!.start(false /* not expecting walkthrough path */); await insidersApp.workbench.editors.waitForActiveTab(readmeMd, true); await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); @@ -92,4 +92,4 @@ export function setup(stableCodePath: string, testDataPath: string) { await insidersApp.stop(); }); }); -} \ No newline at end of file +} diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index e9e0b44a4f0..dd301859606 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -39,8 +39,8 @@ export function setup() { await app.workbench.debug.openDebugViewlet(); await app.workbench.debug.waitForTitle(title => /debug/i.test(title)); - await app.workbench.extensions.openExtensionsViewlet(); - await app.workbench.extensions.waitForTitle(title => /erweiterungen/i.test(title)); + // await app.workbench.extensions.openExtensionsViewlet(); + // await app.workbench.extensions.waitForTitle(title => /erweiterungen/i.test(title)); }); }); -} \ No newline at end of file +} diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index eefa5d35838..432528f7546 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -51,7 +51,9 @@ const opts = minimist(args, { ], boolean: [ 'verbose', - 'remote' + 'remote', + 'web', + 'headless' ], default: { verbose: false @@ -132,7 +134,7 @@ if (testCodePath) { process.env.VSCODE_CLI = '1'; } -if (!fs.existsSync(electronPath || '')) { +if (!opts.web && !fs.existsSync(electronPath || '')) { fail(`Can't find Code at ${electronPath}.`); } @@ -211,7 +213,9 @@ function createOptions(): ApplicationOptions { verbose: opts.verbose, log, screenshotsPath, - remote: opts.remote + remote: opts.remote, + web: opts.web, + headless: opts.headless }; } @@ -234,12 +238,14 @@ after(async function () { await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c())); }); -setupDataMigrationTests(stableCodePath, testDataPath); +if (!opts.web) { + setupDataMigrationTests(stableCodePath, testDataPath); +} describe('Running Code', () => { before(async function () { const app = new Application(this.defaultOptions); - await app!.start(); + await app!.start(opts.web ? false : undefined); this.app = app; }); @@ -268,19 +274,21 @@ describe('Running Code', () => { }); } - setupDataLossTests(); + if (!opts.web) { setupDataLossTests(); } setupDataExplorerTests(); - setupDataPreferencesTests(); + if (!opts.web) { setupDataPreferencesTests(); } setupDataSearchTests(); setupDataCSSTests(); setupDataEditorTests(); - setupDataDebugTests(); + if (!opts.web) { setupDataDebugTests(); } setupDataGitTests(); setupDataStatusbarTests(); setupDataExtensionTests(); setupTerminalTests(); - setupDataMultirootTests(); + if (!opts.web) { setupDataMultirootTests(); } setupDataLocalizationTests(); }); -setupLaunchTests(); \ No newline at end of file +if (!opts.web) { + setupLaunchTests(); +} diff --git a/test/smoke/src/vscode/code.ts b/test/smoke/src/vscode/code.ts index 9f468fddd8b..44cfcfe419f 100644 --- a/test/smoke/src/vscode/code.ts +++ b/test/smoke/src/vscode/code.ts @@ -9,7 +9,8 @@ import * as os from 'os'; import * as fs from 'fs'; import * as mkdirp from 'mkdirp'; import { tmpName } from 'tmp'; -import { IDriver, connect as connectDriver, IDisposable, IElement, Thenable } from './driver'; +import { IDriver, connect as connectElectronDriver, IDisposable, IElement, Thenable } from './driver'; +import { connect as connectPuppeteerDriver, launch } from './puppeteerDriver'; import { Logger } from '../logger'; import { ncp } from 'ncp'; import { URI } from 'vscode-uri'; @@ -62,7 +63,7 @@ function getBuildOutPath(root: string): string { } } -async function connect(child: cp.ChildProcess, outPath: string, handlePath: string, logger: Logger): Promise { +async function connect(connectDriver: typeof connectElectronDriver, child: cp.ChildProcess | undefined, outPath: string, handlePath: string, logger: Logger): Promise { let errCount = 0; while (true) { @@ -71,7 +72,9 @@ async function connect(child: cp.ChildProcess, outPath: string, handlePath: stri return new Code(client, driver, logger); } catch (err) { if (++errCount > 50) { - child.kill(); + if (child) { + child.kill(); + } throw err; } @@ -94,7 +97,12 @@ export interface SpawnOptions { verbose?: boolean; extraArgs?: string[]; log?: string; + /** Run in the test resolver */ remote?: boolean; + /** Run in the web */ + web?: boolean; + /** Run in headless mode (only applies when web is true) */ + headless?: boolean; } async function createDriverHandle(): Promise { @@ -161,14 +169,20 @@ export async function spawn(options: SpawnOptions): Promise { args.push(...options.extraArgs); } - const spawnOptions: cp.SpawnOptions = { env }; + let child: cp.ChildProcess | undefined; + let connectDriver: typeof connectElectronDriver; - const child = cp.spawn(electronPath, args, spawnOptions); - - instances.add(child); - child.once('exit', () => instances.delete(child)); - - return connect(child, outPath, handle, options.logger); + if (options.web) { + await launch(args); + connectDriver = connectPuppeteerDriver.bind(connectPuppeteerDriver, !!options.headless); + } else { + const spawnOptions: cp.SpawnOptions = { env }; + child = cp.spawn(electronPath, args, spawnOptions); + instances.add(child); + child.once('exit', () => instances.delete(child!)); + connectDriver = connectElectronDriver; + } + return connect(connectDriver, child, outPath, handle, options.logger); } async function poll( @@ -364,4 +378,4 @@ export function findElements(element: IElement, fn: (element: IElement) => boole } return result; -} \ No newline at end of file +} diff --git a/test/smoke/src/vscode/puppeteerDriver.ts b/test/smoke/src/vscode/puppeteerDriver.ts new file mode 100644 index 00000000000..e7ae860b551 --- /dev/null +++ b/test/smoke/src/vscode/puppeteerDriver.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as puppeteer from 'puppeteer'; +import { ChildProcess, spawn } from 'child_process'; +import { join } from 'path'; +import { mkdir } from 'fs'; +import { promisify } from 'util'; + +const width = 1200; +const height = 800; + +const vscodeToPuppeteerKey = { + cmd: 'Meta', + ctrl: 'Control', + shift: 'Shift', + enter: 'Enter', + escape: 'Escape', + right: 'ArrowRight', + up: 'ArrowUp', + down: 'ArrowDown', + left: 'ArrowLeft', + home: 'Home' +}; + +function buildDriver(browser: puppeteer.Browser, page: puppeteer.Page): IDriver { + const driver: IDriver = { + _serviceBrand: undefined, + getWindowIds: () => { + return Promise.resolve([1]); + }, + capturePage: () => Promise.resolve(''), + reloadWindow: (windowId) => Promise.resolve(), + exitApplication: () => browser.close(), + dispatchKeybinding: async (windowId, keybinding) => { + const chords = keybinding.split(' '); + for (let i = 0; i < chords.length; i++) { + const chord = chords[i]; + if (i > 0) { + await timeout(100); + } + const keys = chord.split('+'); + const keysDown: string[] = []; + for (let i = 0; i < keys.length; i++) { + if (keys[i] in vscodeToPuppeteerKey) { + keys[i] = vscodeToPuppeteerKey[keys[i]]; + } + await page.keyboard.down(keys[i]); + keysDown.push(keys[i]); + } + while (keysDown.length > 0) { + await page.keyboard.up(keysDown.pop()!); + } + } + + await timeout(100); + }, + click: async (windowId, selector, xoffset, yoffset) => { + const { x, y } = await driver.getElementXY(windowId, selector, xoffset, yoffset); + await page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0)); + }, + doubleClick: async (windowId, selector) => { + await driver.click(windowId, selector, 0, 0); + await timeout(60); + await driver.click(windowId, selector, 0, 0); + await timeout(100); + }, + setValue: async (windowId, selector, text) => page.evaluate(`window.driver.setValue('${selector}', '${text}')`), + getTitle: (windowId) => page.evaluate(`window.driver.getTitle()`), + isActiveElement: (windowId, selector) => page.evaluate(`window.driver.isActiveElement('${selector}')`), + getElements: (windowId, selector, recursive) => page.evaluate(`window.driver.getElements('${selector}', ${recursive})`), + getElementXY: (windowId, selector, xoffset?, yoffset?) => page.evaluate(`window.driver.getElementXY('${selector}', ${xoffset}, ${yoffset})`), + typeInEditor: (windowId, selector, text) => page.evaluate(`window.driver.typeInEditor('${selector}', '${text}')`), + getTerminalBuffer: (windowId, selector) => page.evaluate(`window.driver.getTerminalBuffer('${selector}')`), + writeInTerminal: (windowId, selector, text) => page.evaluate(`window.driver.writeInTerminal('${selector}', '${text}')`) + }; + return driver; +} + +function timeout(ms: number): Promise { + return new Promise(r => setTimeout(r, ms)); +} + +// function runInDriver(call: string, args: (string | boolean)[]): Promise {} + +let args: string[] | undefined; +let server: ChildProcess | undefined; +let endpoint: string | undefined; + +export async function launch(_args: string[]): Promise { + args = _args; + const webUserDataDir = args.filter(e => e.includes('--user-data-dir='))[0].replace('--user-data-dir=', ''); + await promisify(mkdir)(webUserDataDir); + server = spawn(join(args[0], `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`), ['--browser', 'none', '--driver', 'web', '--web-user-data-dir', webUserDataDir]); + server.stderr.on('data', e => console.log('Server stderr: ' + e)); + server.stdout.on('data', e => console.log('Server stdout: ' + e)); + process.on('exit', teardown); + process.on('SIGINT', teardown); + process.on('SIGTERM', teardown); + endpoint = await waitForEndpoint(); +} + +function teardown(): void { + if (server) { + server.kill(); + server = undefined; + } +} + +function waitForEndpoint(): Promise { + return new Promise(r => { + server!.stdout.on('data', d => { + const matches = d.toString('ascii').match(/Web UI available at (.+)/); + if (matches !== null) { + r(matches[1]); + } + }); + }); +} + +export function connect(headless: boolean, outPath: string, handle: string): Promise<{ client: IDisposable, driver: IDriver }> { + return new Promise(async (c) => { + const browser = await puppeteer.launch({ + // Run in Edge dev on macOS + // executablePath: '/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev', + headless, + slowMo: 80, + args: [`--window-size=${width},${height}`] + }); + const page = (await browser.pages())[0]; + await page.setViewport({ width, height }); + await page.goto(`${endpoint}&folder=${args![1]}`); + const result = { + client: { dispose: () => teardown }, + driver: buildDriver(browser, page) + }; + c(result); + }); +} + +/** + * Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise, + * and others. This API makes no assumption about what promise library is being used which + * enables reusing existing code without migrating to a specific promise implementation. Still, + * we recommend the use of native promises which are available in this editor. + */ +export interface Thenable { + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; + then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; +} + +export interface IElement { + tagName: string; + className: string; + textContent: string; + attributes: { [name: string]: string; }; + children: IElement[]; + top: number; + left: number; +} + +export interface IDriver { + _serviceBrand: any; + + getWindowIds(): Promise; + capturePage(windowId: number): Promise; + reloadWindow(windowId: number): Promise; + exitApplication(): Promise; + dispatchKeybinding(windowId: number, keybinding: string): Promise; + click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise; + doubleClick(windowId: number, selector: string): Promise; + setValue(windowId: number, selector: string, text: string): Promise; + getTitle(windowId: number): Promise; + isActiveElement(windowId: number, selector: string): Promise; + getElements(windowId: number, selector: string, recursive?: boolean): Promise; + getElementXY(windowId: number, selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }>; + typeInEditor(windowId: number, selector: string, text: string): Promise; + getTerminalBuffer(windowId: number, selector: string): Promise; + writeInTerminal(windowId: number, selector: string, text: string): Promise; +} + +export interface IDisposable { + dispose(): void; +} diff --git a/test/smoke/tools/copy-driver-definition.js b/test/smoke/tools/copy-driver-definition.js index 2af7a3acd2a..fdebfcc6b0a 100644 --- a/test/smoke/tools/copy-driver-definition.js +++ b/test/smoke/tools/copy-driver-definition.js @@ -7,7 +7,7 @@ const fs = require('fs'); const path = require('path'); const root = path.dirname(path.dirname(path.dirname(__dirname))); -const driverPath = path.join(root, 'src/vs/platform/driver/node/driver.ts'); +const driverPath = path.join(root, 'src/vs/platform/driver/common/driver.ts'); let contents = fs.readFileSync(driverPath, 'utf8'); contents = /\/\/\*START([\s\S]*)\/\/\*END/mi.exec(contents)[1].trim(); @@ -47,4 +47,4 @@ export function connect(outPath: string, handle: string): Promise<{ client: IDis const srcPath = path.join(path.dirname(__dirname), 'src/vscode'); const outDriverPath = path.join(srcPath, 'driver.d.ts'); -fs.writeFileSync(outDriverPath, contents); \ No newline at end of file +fs.writeFileSync(outDriverPath, contents); diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index b86356d3ac3..5219fecf778 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -54,6 +54,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/puppeteer@^1.19.0": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.19.0.tgz#59f0050bae019cee7c3af2bb840a25892a3078b6" + integrity sha512-Db9LWOuTm2bR/qgPE7PQCmnsCQ6flHdULuIDWTks8YdQ/SGHKg5WGWG54gl0734NDKCTF5MbqAp2qWuvBiyQ3Q== + dependencies: + "@types/node" "*" + "@types/rimraf@2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e" @@ -676,10 +683,10 @@ electron-download@^4.1.0: semver "^5.4.1" sumchecker "^2.0.2" -electron@4.2.7: - version "4.2.7" - resolved "https://registry.yarnpkg.com/electron/-/electron-4.2.7.tgz#bdd2dbf489a4a4255405bd8330cc8509831d29ba" - integrity sha512-Azpkw0OPzKVipSsN9/0DrBQhXOpG48Q1gTG7Akchtv37s8TijMe403TUgHxGGhw2ti117ek51kYf7NXLhjXqoA== +electron@4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/electron/-/electron-4.2.9.tgz#81226aa1ba58e1b05388474faf5a815010a11ea2" + integrity sha512-zC7K3GOiZKmxqllVG/qq/Gx+qQvyolKj5xKKwXMqIGekfokEW2hvoIO5Yh7KCoAh5dqBtpzOJjS4fj1se+YBcg== dependencies: "@types/node" "^10.12.18" electron-download "^4.1.0" diff --git a/tslint.json b/tslint.json index 155bd9a4115..4cffe3e1c53 100644 --- a/tslint.json +++ b/tslint.json @@ -5,11 +5,14 @@ "rules": { "no-arg": true, "no-construct": true, + "no-debugger": true, "no-duplicate-super": true, "no-duplicate-switch-case": true, "no-duplicate-variable": true, + "no-for-in-array": true, "no-eval": true, "no-redundant-jsdoc": true, + "no-restricted-globals": true, "no-sparse-arrays": true, "no-string-throw": true, "no-unsafe-finally": true, @@ -371,7 +374,7 @@ ] }, { - "target": "**/vs/workbench/workbench.main.ts", + "target": "**/vs/workbench/workbench.desktop.main.ts", "restrictions": [ "**" ] @@ -391,6 +394,14 @@ "**/vs/workbench/contrib/*/common/**" ] }, + { + "target": "**/vs/workbench/api/worker/**", + "restrictions": [ + "vscode", + "vs/nls", + "**/vs/**/{common,worker}/**" + ] + }, { "target": "**/vs/workbench/electron-browser/**", "restrictions": [ @@ -428,25 +439,41 @@ "**/vs/base/**/common/**", "**/vs/platform/**/common/**", "**/vs/editor/common/**", + "**/vs/workbench/workbench.web.api", "**/vs/workbench/common/**", "**/vs/workbench/services/**/common/**", "**/vs/workbench/api/**/common/**", "vscode-textmate" ] }, + { + "target": "**/vs/workbench/services/**/worker/**", + "restrictions": [ + "vs/nls", + "**/vs/base/**/common/**", + "**/vs/platform/**/common/**", + "**/vs/editor/common/**", + "**/vs/workbench/**/common/**", + "**/vs/workbench/**/worker/**", + "**/vs/workbench/services/**/common/**", + "vscode" + ] + }, { "target": "**/vs/workbench/services/**/browser/**", "restrictions": [ "vs/nls", "vs/css!./**/*", - "**/vs/base/**/{common,browser}/**", + "**/vs/base/**/{common,browser,worker}/**", "**/vs/platform/**/{common,browser}/**", "**/vs/editor/{common,browser}/**", "**/vs/workbench/workbench.web.api", "**/vs/workbench/{common,browser}/**", + "**/vs/workbench/api/{common,browser}/**", "**/vs/workbench/services/**/{common,browser}/**", "vscode-textmate", - "onigasm-umd" + "onigasm-umd", + "@microsoft/applicationinsights-web" ] }, { @@ -467,7 +494,7 @@ "restrictions": [ "vs/nls", "vs/css!./**/*", - "**/vs/base/**/{common,browser,node,electron-browser}/**", + "**/vs/base/**/{common,browser,worker,node,electron-browser}/**", "**/vs/platform/**/{common,browser,node,electron-browser}/**", "**/vs/editor/**", "**/vs/workbench/{common,browser,node,electron-browser,api}/**", @@ -600,6 +627,48 @@ "restrictions": "**/vs/**" } ], + "no-nodejs-globals": [ + true, + { + "target": "**/vs/base/common/{path,process,platform}.ts", + "allowed": [ + "process" // -> defines safe access to process + ] + }, + { + "target": "**/vs/**/test/{common,browser}/**", + "allowed": [ + "process", + "Buffer", + "__filename", + "__dirname" + ] + }, + { + "target": "**/vs/workbench/api/common/extHostExtensionService.ts", + "allowed": [ + "global" // -> safe access to 'global' + ] + }, + { + "target": "**/vs/**/{common,browser}/**", + "allowed": [ /* none */] + } + ], + "no-dom-globals": [ + true, + { + "target": "**/vs/**/test/{common,node,electron-main}/**", + "allowed": [ + "document", + "HTMLElement" + ] + }, + { + "target": "**/vs/**/{common,node,electron-main}/**", + "allowed": [ /* none */] + } + ], "duplicate-imports": true, "no-new-buffer": true, "translation-remind": true, diff --git a/yarn.lock b/yarn.lock index f1c162556e3..90211b1e515 100644 --- a/yarn.lock +++ b/yarn.lock @@ -95,6 +95,69 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" +"@microsoft/applicationinsights-analytics-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-analytics-js/-/applicationinsights-analytics-js-2.1.1.tgz#6d09c1915f808026e2d45165d04802f09affed59" + integrity sha512-VKIutoFKY99CyKwxLUuj6Vnq14/QwXo9/QSQDpYnHEjo+uKn7QmLsHqWw0K9uYNfNAXt4BZimX/zDg6jZtzeXg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-channel-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.1.1.tgz#e205eddd93e49d17d9e0711a612b4bfc9810888f" + integrity sha512-fYr9IAqtaEr9AmaPaL3SLQVT3t3GQzl+n74gpNKyAVakDIm0nYQ/bimjdcAhJMDf1VGNSPg/xICneyuZg7Wxlg== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-common@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.1.1.tgz#27e6074584a7a3a8ca3f11f7ff2b7ff0f395bf2d" + integrity sha512-2hkS1Ia1FmAjCuYZ5JlG20/WgObqdsKtmK5YALAFGHIB4KSQ/Za1qazS+7GsG+E0F9UJivNWL1geUIcNqg5Qjg== + dependencies: + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-core-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.1.1.tgz#30fb6a519cc1c6119c419c4811ce72c260217d9e" + integrity sha512-4t4wf6SKqIcWEQDPg/uOhm+BxtHhu/AFreyEoYZmMfcxzAu33h1FtTQRtxBNbYH1+thiNZCh80yUpnT7d9Hrlw== + dependencies: + tslib "^1.9.3" + +"@microsoft/applicationinsights-dependencies-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-dependencies-js/-/applicationinsights-dependencies-js-2.1.1.tgz#8154c3efcb24617d015d0bce7c2cc47797a8d3c4" + integrity sha512-yhb4EToBp+aI+qLo0h5NDNtoo3sDFV60uyIOK843YjzXqVotcXX/lRShlghTkJtYH09QhrdzDjViUHnD4sMFSQ== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-properties-js@2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-properties-js/-/applicationinsights-properties-js-2.1.1.tgz#ca34232766eb16167b5d87693e2ae5d94f2a1559" + integrity sha512-8l+/ppw6xKTam2RL4EHZ52Lcf217olw81j6kyBNKtIcGwSnLNHrFwEeF3vBWIteG2JKzlg1GhGjrkB3oxXsV2g== + dependencies: + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + tslib "^1.9.3" + +"@microsoft/applicationinsights-web@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web/-/applicationinsights-web-2.1.1.tgz#1a44eddda7c244b88d9eb052dab6c855682e4f05" + integrity sha512-crvhCkNsNxkFuPWmttyWNSAA96D5FxBtKS6UA9MV9f9XHevTfchf/E3AuU9JZcsXufWMQLwLrUQ9ZiA1QJ0EWA== + dependencies: + "@microsoft/applicationinsights-analytics-js" "2.1.1" + "@microsoft/applicationinsights-channel-js" "2.1.1" + "@microsoft/applicationinsights-common" "2.1.1" + "@microsoft/applicationinsights-core-js" "2.1.1" + "@microsoft/applicationinsights-dependencies-js" "2.1.1" + "@microsoft/applicationinsights-properties-js" "2.1.1" + "@types/commander@^2.11.0": version "2.12.2" resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.12.2.tgz#183041a23842d4281478fa5d23c5ca78e6fd08ae" @@ -112,11 +175,6 @@ resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.0.tgz#ca24e6ee6d0df10c003aafe26e93113b8faf0d8e" integrity sha512-cq/NkUUy6rpWD8n7PweNQQBpw2o0cf5v6fbkUVEpOB9VzzIvyPvSEId1/goIj+MciW2v1Lw5mRimKO01XgE9EA== -"@types/minimist@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" - integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= - "@types/mocha@2.2.39": version "2.2.39" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.39.tgz#f68d63db8b69c38e9558b4073525cf96c4f7a829" @@ -1774,7 +1832,7 @@ concat-stream@1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@^1.5.0, concat-stream@^1.6.0: +concat-stream@1.6.2, concat-stream@^1.5.0, concat-stream@^1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -2135,14 +2193,14 @@ debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@3.1.0, debug@^3.1.0: +debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== dependencies: ms "2.0.0" -debug@^3.2.6: +debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -3077,6 +3135,16 @@ extract-zip@^1.6.5: mkdirp "0.5.0" yauzl "2.4.1" +extract-zip@^1.6.6: + version "1.6.7" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" + integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= + dependencies: + concat-stream "1.6.2" + debug "2.6.9" + mkdirp "0.5.1" + yauzl "2.4.1" + extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -3763,10 +3831,10 @@ growl@1.9.2: resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" integrity sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8= -gulp-atom-electron@^1.21.1: - version "1.21.1" - resolved "https://registry.yarnpkg.com/gulp-atom-electron/-/gulp-atom-electron-1.21.1.tgz#4017144bf659fbbf7d0644664fcc47c64efac0f0" - integrity sha512-UHEf2pZrJD/u+AAzKCbhdPXaKrReFDa+OEJjBCAdN2SHnD+dfZqSJAz/u2OD6YR/eREuUbQOCw+VWUwex20klQ== +gulp-atom-electron@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/gulp-atom-electron/-/gulp-atom-electron-1.22.0.tgz#0e2f4fe7c7310145c6c81d5660d0277cd8338d27" + integrity sha512-K13Ze2+iRIvC1igl+BIbZdF8M2ZzEIM6LF+/j83oWNGNhe7+en77qT9y9rs6SipYeOyoInlbly+pzbFgvwpSHA== dependencies: event-stream "3.3.4" github-releases-ms "^0.5.0" @@ -5744,6 +5812,11 @@ mime@^1.4.1, mime@^1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.0.3: + version "2.4.4" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" + integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== + mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -5950,11 +6023,6 @@ nan@2.14.0, nan@^2.0.0, nan@^2.13.2, nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== -nan@^2.12.1: - version "2.12.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" - integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== - nan@^2.9.2: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" @@ -5982,10 +6050,10 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== -native-is-elevated@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.2.1.tgz#70a2123a8575b9f624a3ef465d98cb74ae017385" - integrity sha1-cKISOoV1ufYko+9GXZjLdK4Bc4U= +native-is-elevated@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.3.0.tgz#6c5d8f57daeec129abd03b5606a55e56e4337423" + integrity sha512-QJgU7vaCZ199PSEC4LAmwtGfqwGaz8a51YDeze3DPiRzcOq25LIQpxCbBWunIu+csMMHFsDuyO2OVfeSD4ioHQ== native-keymap@2.0.0: version "2.0.0" @@ -7190,7 +7258,7 @@ progress@^1.1.8: resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= -progress@^2.0.0: +progress@^2.0.0, progress@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -7213,6 +7281,11 @@ proxy-addr@~2.0.2: forwarded "~0.1.2" ipaddr.js "1.5.2" +proxy-from-env@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" + integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -7295,6 +7368,20 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +puppeteer@^1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.19.0.tgz#e3b7b448c2c97933517078d7a2c53687361bebea" + integrity sha512-2S6E6ygpoqcECaagDbBopoSOPDv0pAZvTbnBgUY+6hq0/XDFDOLEMNlHF/SKJlzcaZ9ckiKjKDuueWI3FN/WXw== + dependencies: + debug "^4.1.0" + extract-zip "^1.6.6" + https-proxy-agent "^2.2.1" + mime "^2.0.3" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^2.6.1" + ws "^6.1.0" + q@^1.0.1, q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -8971,6 +9058,11 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +tslib@^1.9.3: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + tslint@^5.16.0: version "5.16.0" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.16.0.tgz#ae61f9c5a98d295b9a4f4553b1b1e831c1984d67" @@ -9539,10 +9631,10 @@ vscode-chokidar@2.1.7: optionalDependencies: vscode-fsevents "1.2.12" -vscode-debugprotocol@1.35.0: - version "1.35.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.35.0.tgz#565140cd42945e30c6c85cafb38c631457d4a46c" - integrity sha512-+OMm11R1bGYbpIJ5eQIkwoDGFF4GvBz3Ztl6/VM+/RNNb2Gjk2c0Ku+oMmfhlTmTlPCpgHBsH4JqVCbUYhu5bA== +vscode-debugprotocol@1.36.0-pre.0: + version "1.36.0-pre.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.36.0-pre.0.tgz#4f998e143acae9e3ce13c308d4ad322f96841926" + integrity sha512-nQhImfsUJFfr73JqA2Uc1bjTvpgZRabqkkKQWUbmrrnTLCPKRCr9AgE44Ehb/vkLXNdo+vPnWjJXWnPGjT10mA== vscode-fsevents@1.2.12: version "1.2.12" @@ -9551,6 +9643,11 @@ vscode-fsevents@1.2.12: dependencies: nan "^2.14.0" +vscode-minimist@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a" + integrity sha512-cmB72+qDoiCFJ1UKnGUBdGYfXzdpJ3bQM/D/+XhkVk5v7uZgLbYiCz5JcwVyk7NC7hSi5VGtQ4wihzmi12NeXw== + vscode-nls-dev@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/vscode-nls-dev/-/vscode-nls-dev-3.3.1.tgz#15fc03e0c9ca5a150abb838690d9554ac06f77e4" @@ -9579,10 +9676,10 @@ vscode-proxy-agent@0.4.0: https-proxy-agent "2.2.1" socks-proxy-agent "4.0.1" -vscode-ripgrep@^1.5.5: - version "1.5.5" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.5.tgz#24c0e9cb356cf889c98e15ecb58f9cf654a1d961" - integrity sha512-OrPrAmcun4+uZAuNcQvE6CCPskh+5AsjANod/Q3zRcJcGNxgoOSGlQN9RPtatkUNmkN8Nn8mZBnS1jMylu/dKg== +vscode-ripgrep@^1.5.6: + version "1.5.6" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.5.6.tgz#93bf5c99ca5f8248950a305e224f6ca153c30af4" + integrity sha512-WRIM9XpUj6dsfdAmuI3ANbmT1ysPUVsYy/2uCLDHJa9kbiB4T7uGvFnnc0Rgx2qQnyRAwL7PeWaFgUljPPxf2g== vscode-sqlite3@4.0.8: version "4.0.8" @@ -9605,12 +9702,10 @@ vscode-windows-ca-certs@0.1.0: dependencies: node-addon-api "1.6.2" -vscode-windows-registry@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.1.tgz#bc9f765563eb6dc1c9ad9a41f9eaacc84dfadc7c" - integrity sha512-q0aKXi9Py1OBdmXIJJFeJBzpPJMMUxMJNBU9FysWIXEwJyMQGEVevKzM2J3Qz/cHSc5LVqibmoUWzZ7g+97qRg== - dependencies: - nan "^2.12.1" +vscode-windows-registry@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.2.tgz#b863e704a6a69c50b3098a55fbddbe595b0c124a" + integrity sha512-/CLLvuOSM2Vme2z6aNyB+4Omd7hDxpf4Thrt8ImxnXeQtxzel2bClJpFQvQqK/s4oaXlkBKS7LqVLeZM+uSVIA== vso-node-api@6.1.2-preview: version "6.1.2-preview" @@ -9823,6 +9918,13 @@ ws@^3.3.3: safe-buffer "~5.1.0" ultron "~1.1.0" +ws@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + xml-name-validator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-1.0.0.tgz#dcf82ee092322951ef8cc1ba596c9cbfd14a83f1" @@ -9885,20 +9987,20 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xterm-addon-search@0.2.0-beta3: - version "0.2.0-beta3" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0-beta3.tgz#710ce14658e269c5a4791f5a9e2f520883a2e62b" - integrity sha512-KzVdkEtGbKJe9ER2TmrI7XjF/wUq1lir9U63vPJi0t2ymQvIECl1V63f9QtOp1vvpdhbZiXBxO+vGTj+y0tRow== +xterm-addon-search@0.2.0-beta5: + version "0.2.0-beta5" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.2.0-beta5.tgz#258d7cb1511d9060cd4520f0f82e408000fd4f53" + integrity sha512-Tg+d8scch0rYOVmzBbX35Y1GXtq+eu/YlzbXznmTo/yD83j3BQlXOhgECu/Yv8EX5JwFmzbfVRWC+JWnfigwGg== xterm-addon-web-links@0.1.0-beta10: version "0.1.0-beta10" resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta10.tgz#610fa9773a2a5ccd41c1c83ba0e2dd2c9eb66a23" integrity sha512-xfpjy0V6bB4BR44qIgZQPoCMVakxb65gMscPkHpO//QxvUxKzabV3dxOsIbeZRFkUGsWTFlvz2OoaBLoNtv5gg== -xterm@3.15.0-beta89: - version "3.15.0-beta89" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta89.tgz#255962e2595deefb42b8c0043001256526163a3f" - integrity sha512-rNaoUamacPRg+ejbKDGRDNqR3SZ3Uf/pUW0mO+FF25/lIgdLq8x7RgZVBgFweCZ/dijPjxoyMcgfNDTH9h8LOg== +xterm@3.15.0-beta99: + version "3.15.0-beta99" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta99.tgz#0010a7ea5d56cbb08a1e3a525b353c96a158e7a0" + integrity sha512-Vm0ZWToWwO4uk/28Kqvqt9L92h5EU2z4WR9I6xcQaPIBmkJPINIARU4LWQnvaOfgFhRbpwBMveTfh8/jM97lPg== y18n@^3.2.1: version "3.2.1"