From a53b78c0c84612f21ec54cf820606f6ad6db9af4 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Mon, 16 Mar 2020 23:16:40 -0700 Subject: [PATCH] Merge from vscode 099a7622e6e90dbcc226e428d4e35a72cb19ecbc (#9646) * Merge from vscode 099a7622e6e90dbcc226e428d4e35a72cb19ecbc * fix strict --- .github/commands.yml | 13 + build/lib/treeshaking.js | 2 +- build/lib/treeshaking.ts | 2 +- build/package.json | 2 +- build/yarn.lock | 10 +- .../markdown-language-features/yarn.lock | 6 +- .../theme-abyss/themes/abyss-color-theme.json | 3 +- .../theme-defaults/themes/dark_defaults.json | 5 +- .../themes/hc_black_defaults.json | 5 +- .../theme-defaults/themes/light_defaults.json | 3 +- .../themes/kimbie-dark-color-theme.json | 3 +- .../themes/dimmed-monokai-color-theme.json | 3 +- .../themes/monokai-color-theme.json | 3 +- .../themes/quietlight-color-theme.json | 3 +- .../theme-red/themes/Red-color-theme.json | 3 +- .../themes/solarized-dark-color-theme.json | 3 +- .../themes/solarized-light-color-theme.json | 3 +- .../themes/tomorrow-night-blue-theme.json | 3 +- package.json | 2 +- .../ui/scrollableSplitview/heightMap.ts | 4 +- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 5 + .../base/browser/ui/iconLabel/iconlabel.css | 5 + src/vs/base/browser/ui/inputbox/inputBox.ts | 4 + src/vs/base/browser/ui/list/list.ts | 2 +- src/vs/base/browser/ui/splitview/paneview.ts | 4 + src/vs/base/common/filters.ts | 4 +- src/vs/base/common/uri.ts | 2 + .../parts/quickinput/browser/quickInput.ts | 37 +- .../parts/quickinput/browser/quickInputBox.ts | 12 +- .../quickinput/browser/quickInputList.ts | 3 +- .../parts/quickinput/common/quickInput.ts | 25 +- .../base/parts/tree/browser/treeViewModel.ts | 5 +- .../sharedProcess/sharedProcessMain.ts | 10 +- src/vs/code/electron-main/window.ts | 10 +- src/vs/editor/common/model/textModel.ts | 4 + src/vs/editor/common/model/textModelEvents.ts | 1 + .../common/services/modelServiceImpl.ts | 60 +-- src/vs/editor/common/standaloneStrings.ts | 23 +- .../quickAccess/commandsQuickAccess.ts | 2 +- .../editorNavigationQuickAccess.ts | 192 ++++++++ .../quickAccess/gotoLineQuickAccess.ts | 75 +--- .../quickAccess/gotoSymbolQuickAccess.ts | 420 ++++++++++++++++++ .../editor/contrib/quickAccess/quickAccess.ts | 104 ----- src/vs/editor/editor.main.ts | 4 +- .../standaloneCommandsQuickAccess.ts | 36 +- .../standaloneGotoLineQuickAccess.ts | 35 +- .../standaloneGotoSymbolQuickAccess.ts | 67 +++ .../quickAccess/standaloneHelpQuickAccess.ts | 4 +- .../browser/quickOpen/editorQuickOpen.css | 19 - .../browser/quickOpen/editorQuickOpen.ts | 172 ------- .../standalone/browser/quickOpen/gotoLine.css | 8 - .../standalone/browser/quickOpen/gotoLine.ts | 177 -------- .../browser/quickOpen/quickCommand.ts | 144 ------ .../quickOpen/quickOpenEditorWidget.ts | 107 ----- .../browser/quickOpen/quickOutline.css | 8 - .../browser/quickOpen/quickOutline.ts | 325 -------------- .../browser/standaloneThemeServiceImpl.ts | 2 + .../test/browser/standaloneLanguages.test.ts | 2 + src/vs/monaco.d.ts | 2 + .../environment/common/environment.ts | 4 +- src/vs/platform/environment/node/argv.ts | 1 + .../environment/node/environmentService.ts | 2 +- .../common/extensionGalleryService.ts | 47 +- .../test/node/extensionGalleryService.test.ts | 7 +- src/vs/platform/opener/browser/link.ts | 9 +- src/vs/platform/progress/common/progress.ts | 3 +- .../quickinput/browser/commandsQuickAccess.ts | 13 +- .../quickinput/browser/helpQuickAccess.ts | 2 +- .../quickinput/browser/pickerQuickAccess.ts | 167 +++++++ .../quickinput/browser/quickAccess.ts | 19 +- .../platform/quickinput/common/quickAccess.ts | 197 ++------ .../common/serviceMachineId.ts | 42 ++ src/vs/platform/theme/common/themeService.ts | 5 + .../theme/test/common/testThemeService.ts | 2 + .../userDataSync/common/settingsSync.ts | 4 +- .../userDataSync/common/userDataSync.ts | 8 - .../common/userDataSyncEnablementService.ts | 10 + .../userDataSync/common/userDataSyncIpc.ts | 36 +- .../common/userDataSyncService.ts | 6 +- .../common/userDataSyncStoreService.ts | 24 +- .../test/common/userDataSyncClient.ts | 5 +- src/vs/vscode.d.ts | 9 +- src/vs/vscode.proposed.d.ts | 50 ++- .../api/browser/mainThreadTerminalService.ts | 21 +- .../api/browser/mainThreadWebview.ts | 89 ++-- .../workbench/api/common/extHost.api.impl.ts | 6 +- .../workbench/api/common/extHost.protocol.ts | 12 +- .../workbench/api/common/extHostProgress.ts | 4 +- .../api/common/extHostTerminalService.ts | 39 +- .../api/common/extHostTypeConverters.ts | 7 +- src/vs/workbench/api/common/extHostTypes.ts | 3 +- src/vs/workbench/api/common/extHostWebview.ts | 24 +- .../api/node/extHostExtensionService.ts | 2 +- src/vs/workbench/browser/labels.ts | 50 ++- .../workbench/browser/parts/compositePart.ts | 2 +- .../browser/parts/editor/editorQuickAccess.ts | 12 +- .../parts/editor/noTabsTitleControl.ts | 17 +- .../browser/parts/editor/tabsTitleControl.ts | 8 +- .../notifications/notificationsViewer.ts | 5 +- .../browser/parts/views/media/views.css | 4 + .../browser/parts/views/viewPaneContainer.ts | 2 +- src/vs/workbench/common/editor.ts | 29 +- src/vs/workbench/common/resources.ts | 4 +- .../browser/codeEditor.contribution.ts | 2 + .../inspectEditorTokens.ts | 17 +- .../quickaccess/gotoLineQuickAccess.ts | 30 +- .../quickaccess/gotoSymbolQuickAccess.ts | 68 +++ .../codeEditor/browser/semanticTokensHelp.ts | 86 ++++ .../contrib/customEditor/browser/commands.ts | 2 +- .../customEditor/browser/customEditorInput.ts | 45 +- .../customEditor/browser/customEditors.ts | 20 +- .../customEditor/common/customEditor.ts | 8 +- .../common/customTextEditorModel.ts | 8 +- .../contrib/debug/browser/callStackView.ts | 5 + .../contrib/debug/browser/debugActions.ts | 22 +- .../contrib/debug/browser/debugQuickAccess.ts | 4 +- .../debug/browser/media/debugViewlet.css | 2 + .../browser/extensionsQuickAccess.ts | 2 +- .../files/browser/files.contribution.ts | 10 +- .../files/browser/views/openEditorsView.ts | 2 +- .../browser/commandsQuickAccess.ts | 4 +- .../browser/quickAccess.contribution.ts | 7 - .../quickaccess/browser/viewQuickAccess.ts | 2 +- .../contrib/scm/browser/media/scmViewlet.css | 2 +- .../search/browser/search.contribution.ts | 18 +- .../contrib/search/browser/searchWidget.ts | 25 +- .../search/browser/symbolsQuickAccess.ts | 229 ++++++++++ .../search/test/browser/searchActions.test.ts | 3 + .../search/test/browser/searchViewlet.test.ts | 3 + .../search/test/common/searchModel.test.ts | 3 + .../search/test/common/searchResult.test.ts | 3 + .../contrib/tasks/browser/tasksQuickAccess.ts | 2 +- .../terminal/browser/terminaQuickAccess.ts | 14 +- .../terminal/browser/terminal.contribution.ts | 4 +- .../contrib/terminal/browser/terminal.ts | 25 ++ .../terminal/browser/terminalInstance.ts | 10 +- .../terminal/browser/terminalLinkHandler.ts | 111 +++-- .../terminal/browser/terminalService.ts | 48 +- .../contrib/terminal/common/terminal.ts | 2 +- .../test/browser/terminalLinkHandler.test.ts | 55 ++- .../test/common/terminalColorRegistry.test.ts | 4 +- .../userDataSync/browser/userDataSyncView.ts | 3 +- .../contrib/webview/browser/webviewEditor.ts | 7 +- .../webview/browser/webviewEditorInput.ts | 18 +- .../browser/webviewWorkbenchService.ts | 16 +- src/vs/workbench/electron-browser/window.ts | 4 +- .../services/backup/common/backup.ts | 4 +- .../common/extensionEnablementService.ts | 6 +- .../extensionEnablementService.test.ts | 22 +- .../node/extensionHostProcessSetup.ts | 5 +- .../services/extensions/node/proxyResolver.ts | 22 +- .../services/history/browser/history.ts | 28 +- .../keybindingEditing.test.ts | 3 + .../lifecycle/browser/lifecycleService.ts | 7 +- .../progress/browser/progressIndicator.ts | 9 +- .../test/browser/progressIndicator.test.ts | 2 +- .../services/search/common/search.ts | 16 +- .../services/themes/common/colorThemeData.ts | 51 ++- .../themes/common/colorThemeSchema.ts | 4 + .../themes/common/themeConfiguration.ts | 5 + .../themes/common/workbenchThemeService.ts | 3 +- .../electron-browser/settingsSyncService.ts | 111 ----- .../test/browser/parts/editor/editor.test.ts | 22 +- .../browser/parts/editor/editorModel.test.ts | 3 + .../parts/editor/rangeDecorations.test.ts | 3 + .../test/browser/quickAccess.test.ts | 107 +++-- .../test/browser/workbenchTestServices.ts | 4 +- src/vs/workbench/workbench.desktop.main.ts | 1 - src/vs/workbench/workbench.web.main.ts | 4 +- yarn.lock | 16 +- 170 files changed, 2601 insertions(+), 2026 deletions(-) create mode 100644 src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts create mode 100644 src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts delete mode 100644 src/vs/editor/contrib/quickAccess/quickAccess.ts create mode 100644 src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts delete mode 100644 src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css delete mode 100644 src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts delete mode 100644 src/vs/editor/standalone/browser/quickOpen/gotoLine.css delete mode 100644 src/vs/editor/standalone/browser/quickOpen/gotoLine.ts delete mode 100644 src/vs/editor/standalone/browser/quickOpen/quickCommand.ts delete mode 100644 src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts delete mode 100644 src/vs/editor/standalone/browser/quickOpen/quickOutline.css delete mode 100644 src/vs/editor/standalone/browser/quickOpen/quickOutline.ts create mode 100644 src/vs/platform/quickinput/browser/pickerQuickAccess.ts create mode 100644 src/vs/platform/serviceMachineId/common/serviceMachineId.ts create mode 100644 src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts create mode 100644 src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts create mode 100644 src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts delete mode 100644 src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts diff --git a/.github/commands.yml b/.github/commands.yml index 967ef134c0..83d8c72780 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -1,3 +1,16 @@ +# { +# perform: true, +# commands: [ +# { +# type: 'comment', +# name: 'findDuplicates', +# allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], +# action: 'comment', +# comment: "Potential duplicates:\n${potentialDuplicates}" +# } +# ] +# } + { perform: true, commands: [ diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 7d11b990e6..0c8b5dea26 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -333,7 +333,7 @@ function markNodes(languageService, options) { } setColor(node, 2 /* Black */); black_queue.push(node); - if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { + if (options.shakeLevel === 2 /* ClassMembers */ && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); if (references) { for (let i = 0, len = references.length; i < len; i++) { diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 706053fe6e..b2959ee1eb 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -436,7 +436,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt setColor(node, NodeColor.Black); black_queue.push(node); - if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { + if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); if (references) { for (let i = 0, len = references.length; i < len; i++) { diff --git a/build/package.json b/build/package.json index 3bc16ea9e5..da74bf4ba2 100644 --- a/build/package.json +++ b/build/package.json @@ -50,7 +50,7 @@ "rollup-plugin-node-resolve": "^5.2.0", "service-downloader": "0.2.1", "terser": "4.3.8", - "typescript": "3.9.0-dev.20200304", + "typescript": "^3.9.0-dev.20200313", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index c0bd8cdf8f..b3b24d7eaf 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3676,16 +3676,16 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@3.9.0-dev.20200304: - version "3.9.0-dev.20200304" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200304.tgz#3cc35357eff29dc5604b4fa56d6597e13daf86ed" - integrity sha512-eUip/GgJmjp4qtHiJDxVhE5SDDiPzBUg7KBAFUgb7HgL/tv10JAHej7fnS1i+7xrq1eDtbkJyPaYOVnhL9db7Q== - typescript@^3.0.1: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== +typescript@^3.9.0-dev.20200313: + version "3.9.0-dev.20200313" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200313.tgz#f66aeb2c08268f2b1fc6d1d96e15554c6e7ed29b" + integrity sha512-85/IJPm1nEUbQDxK3aN+svIy4X3kPcAipihB3704NY1HXncJ1daNLJW1OktOacb8tD/URpIGs9nMgbUrKvglGg== + typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index 5c7a22c74f..23cd174400 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -191,9 +191,9 @@ abbrev@1: integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== acorn@^6.2.1: - version "6.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" - integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== ajv-errors@^1.0.0: version "1.0.1" diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index 1df39d31c9..39f93305b8 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -434,5 +434,6 @@ "terminal.ansiBrightMagenta": "#d778ff", "terminal.ansiBrightCyan": "#78ffff", "terminal.ansiBrightWhite": "#ffffff" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-defaults/themes/dark_defaults.json b/extensions/theme-defaults/themes/dark_defaults.json index 00c2ac8c36..89f0a5beec 100644 --- a/extensions/theme-defaults/themes/dark_defaults.json +++ b/extensions/theme-defaults/themes/dark_defaults.json @@ -18,5 +18,6 @@ "menu.foreground": "#CCCCCC", "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D" - } -} \ No newline at end of file + }, + "semanticHighlighting": true +} diff --git a/extensions/theme-defaults/themes/hc_black_defaults.json b/extensions/theme-defaults/themes/hc_black_defaults.json index 9d11138a99..1a03010abf 100644 --- a/extensions/theme-defaults/themes/hc_black_defaults.json +++ b/extensions/theme-defaults/themes/hc_black_defaults.json @@ -337,5 +337,6 @@ "foreground": "#569cd6" } } - ] -} \ No newline at end of file + ], + "semanticHighlighting": true +} diff --git a/extensions/theme-defaults/themes/light_defaults.json b/extensions/theme-defaults/themes/light_defaults.json index 018da38e93..9ea03a9e31 100644 --- a/extensions/theme-defaults/themes/light_defaults.json +++ b/extensions/theme-defaults/themes/light_defaults.json @@ -18,5 +18,6 @@ "settings.numberInputBorder": "#CECECE", "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json index 111a4a23d9..cdd2230711 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -394,5 +394,6 @@ "foreground": "#dc3958" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index f0b6126d5f..8b1fe2dd80 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -572,5 +572,6 @@ "foreground": "#c7444a" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index c695640f29..a305089465 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -476,5 +476,6 @@ "foreground": "#FD971F" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index ae19ba7889..ffcb30cff0 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -494,5 +494,6 @@ "walkThrough.embeddedEditorBackground": "#00000014", "editorIndentGuide.background": "#aaaaaa60", "editorIndentGuide.activeBackground": "#777777b0" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-red/themes/Red-color-theme.json b/extensions/theme-red/themes/Red-color-theme.json index 277e7a8db3..8ebbf48c22 100644 --- a/extensions/theme-red/themes/Red-color-theme.json +++ b/extensions/theme-red/themes/Red-color-theme.json @@ -385,5 +385,6 @@ "foreground": "#ec0d1e" } } - ] + ], + "semanticHighlighting": true } diff --git a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json index 682444485d..b23ff8bb85 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -477,5 +477,6 @@ "terminal.ansiBrightMagenta": "#6c71c4", "terminal.ansiBrightCyan": "#93a1a1", "terminal.ansiBrightWhite": "#fdf6e3" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json index a29c8fb32f..2c1f501d85 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -484,5 +484,6 @@ // Interactive Playground "walkThrough.embeddedEditorBackground": "#00000014" - } + }, + "semanticHighlighting": true } diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json index f8c47a29e7..0baee6822e 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json @@ -255,5 +255,6 @@ "foreground": "#b267e6" } } - ] + ], + "semanticHighlighting": true } diff --git a/package.json b/package.json index b860178327..7b8662a00b 100644 --- a/package.json +++ b/package.json @@ -178,7 +178,7 @@ "style-loader": "^1.0.0", "ts-loader": "^4.4.2", "typemoq": "^0.3.2", - "typescript": "3.9.0-dev.20200304", + "typescript": "^3.9.0-dev.20200313", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/src/sql/base/browser/ui/scrollableSplitview/heightMap.ts b/src/sql/base/browser/ui/scrollableSplitview/heightMap.ts index f404c1b31a..48278b7d1d 100644 --- a/src/sql/base/browser/ui/scrollableSplitview/heightMap.ts +++ b/src/sql/base/browser/ui/scrollableSplitview/heightMap.ts @@ -53,7 +53,7 @@ export class HeightMap { totalSize = viewItem.top + viewItem.height; } - let boundSplice = this.heightMap.splice.bind(this.heightMap, i, 0); + const startingIndex = i; let itemsToInsert: IViewItem[] = []; @@ -65,7 +65,7 @@ export class HeightMap { sizeDiff += viewItem.height; } - boundSplice.apply(this.heightMap, itemsToInsert); + this.heightMap.splice(startingIndex, 0, ...itemsToInsert); for (j = i; j < this.heightMap.length; j++) { viewItem = this.heightMap[j]; diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 0a6c3392c9..c42ca480c3 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -23,6 +23,7 @@ export interface IIconLabelValueOptions { hideIcon?: boolean; extraClasses?: string[]; italic?: boolean; + strikethrough?: boolean; matches?: IMatch[]; labelEscapeNewLines?: boolean; descriptionMatches?: IMatch[]; @@ -136,6 +137,10 @@ export class IconLabel extends Disposable { if (options.italic) { classes.push('italic'); } + + if (options.strikethrough) { + classes.push('strikethrough'); + } } this.domNode.className = classes.join(' '); diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index 39950a7cda..d833beb9de 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -60,6 +60,11 @@ font-style: italic; } +.monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-name-container > .label-name, +.monaco-icon-label.strikethrough > .monaco-icon-description-container > .label-description { + text-decoration: line-through; +} + .monaco-icon-label::after { opacity: 0.75; font-size: 90%; diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 3a72d73ae4..ac9e8e3a3c 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -319,6 +319,10 @@ export class InputBox extends Widget { } } + public isSelectionAtEnd(): boolean { + return this.input.selectionEnd === this.input.value.length && this.input.selectionStart === this.input.selectionEnd; + } + public enable(): void { this.input.removeAttribute('disabled'); } diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index d20bf85bdf..9912d07098 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -65,7 +65,7 @@ export interface IIdentityProvider { export enum ListAriaRootRole { /** default list structure role */ - LIST = 'list', + LIST = 'listbox', /** default tree structure role */ TREE = 'tree', diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index c416ccca87..5df8c7b677 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -209,6 +209,10 @@ export abstract class Pane extends Disposable implements IView { this.body = append(this.element, $('.pane-body')); this.renderBody(this.body); + + if (!this.isExpanded()) { + this.body.remove(); + } } layout(size: number): void { diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 01ae3025d3..6bc7135d9c 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -392,7 +392,7 @@ export function anyScore(pattern: string, lowPattern: string, _patternPos: numbe //#region --- fuzzyScore --- -export function createMatches(score: undefined | FuzzyScore): IMatch[] { +export function createMatches(score: undefined | FuzzyScore, offset = 0): IMatch[] { if (typeof score === 'undefined') { return []; } @@ -407,7 +407,7 @@ export function createMatches(score: undefined | FuzzyScore): IMatch[] { if (last && last.end === pos) { last.end = pos + 1; } else { - res.push({ start: pos, end: pos + 1 }); + res.push({ start: pos + offset, end: pos + 1 + offset }); } } } diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 8e72cba783..92eb50a131 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -85,6 +85,7 @@ const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation * and encoding. * + * ```txt * foo://example.com:8042/over/there?name=ferret#nose * \_/ \______________/\_________/ \_________/ \__/ * | | | | | @@ -92,6 +93,7 @@ const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; * | _____________________|__ * / \ / \ * urn:example:animal:ferret:nose + * ``` */ export class URI implements UriComponents { diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index a44df9e677..067b537d4d 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/quickInput'; -import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput'; import * as dom from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { QuickInputList } from './quickInputList'; @@ -379,11 +379,12 @@ class QuickPick extends QuickInput implements IQuickPi private _ariaLabel = QuickPick.DEFAULT_ARIA_LABEL; private _placeholder: string | undefined; private readonly onDidChangeValueEmitter = this._register(new Emitter()); - private readonly onDidAcceptEmitter = this._register(new Emitter()); + private readonly onDidAcceptEmitter = this._register(new Emitter()); private readonly onDidCustomEmitter = this._register(new Emitter()); private _items: Array = []; private itemsUpdated = false; private _canSelectMany = false; + private _canAcceptInBackground = false; private _matchOnDescription = false; private _matchOnDetail = false; private _matchOnLabel = true; @@ -462,6 +463,14 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + get canAcceptInBackground() { + return this._canAcceptInBackground; + } + + set canAcceptInBackground(canAcceptInBackground: boolean) { + this._canAcceptInBackground = canAcceptInBackground; + } + get matchOnDescription() { return this._matchOnDescription; } @@ -663,6 +672,22 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.list.domFocus(); } event.preventDefault(); + break; + case KeyCode.RightArrow: + if (!this._canAcceptInBackground) { + return; // needs to be enabled + } + + if (!this.ui.inputBox.isSelectionAtEnd()) { + return; // ensure input box selection at end + } + + if (this.activeItems[0]) { + this._selectedItems = [this.activeItems[0]]; + this.onDidChangeSelectionEmitter.fire(this.selectedItems); + this.onDidAcceptEmitter.fire({ inBackground: true }); + } + break; } })); @@ -671,7 +696,7 @@ class QuickPick extends QuickInput implements IQuickPi this._selectedItems = [this.activeItems[0]]; this.onDidChangeSelectionEmitter.fire(this.selectedItems); } - this.onDidAcceptEmitter.fire(undefined); + this.onDidAcceptEmitter.fire({ inBackground: false }); })); this.visibleDisposables.add(this.ui.onDidCustom(() => { this.onDidCustomEmitter.fire(undefined); @@ -686,7 +711,7 @@ class QuickPick extends QuickInput implements IQuickPi this._activeItems = focusedItems as T[]; this.onDidChangeActiveEmitter.fire(focusedItems as T[]); })); - this.visibleDisposables.add(this.ui.list.onDidChangeSelection(selectedItems => { + this.visibleDisposables.add(this.ui.list.onDidChangeSelection(({ items: selectedItems, event }) => { if (this.canSelectMany) { if (selectedItems.length) { this.ui.list.setSelectedElements([]); @@ -699,7 +724,7 @@ class QuickPick extends QuickInput implements IQuickPi this._selectedItems = selectedItems as T[]; this.onDidChangeSelectionEmitter.fire(selectedItems as T[]); if (selectedItems.length) { - this.onDidAcceptEmitter.fire(undefined); + this.onDidAcceptEmitter.fire({ inBackground: event instanceof MouseEvent && event.button === 1 /* mouse middle click */ }); } })); this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => { @@ -762,7 +787,7 @@ class QuickPick extends QuickInput implements IQuickPi if (wasTriggerKeyPressed && this.activeItems[0]) { this._selectedItems = [this.activeItems[0]]; this.onDidChangeSelectionEmitter.fire(this.selectedItems); - this.onDidAcceptEmitter.fire(undefined); + this.onDidAcceptEmitter.fire({ inBackground: false }); } }); } diff --git a/src/vs/base/parts/quickinput/browser/quickInputBox.ts b/src/vs/base/parts/quickinput/browser/quickInputBox.ts index 983bd41ea4..2ac03ba12c 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputBox.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputBox.ts @@ -54,7 +54,11 @@ export class QuickInputBox extends Disposable { this.inputBox.select(range); } - setPlaceholder(placeholder: string) { + isSelectionAtEnd(): boolean { + return this.inputBox.isSelectionAtEnd(); + } + + setPlaceholder(placeholder: string): void { this.inputBox.setPlaceHolder(placeholder); } @@ -90,11 +94,11 @@ export class QuickInputBox extends Disposable { return this.inputBox.hasFocus(); } - setAttribute(name: string, value: string) { + setAttribute(name: string, value: string): void { this.inputBox.inputElement.setAttribute(name, value); } - removeAttribute(name: string) { + removeAttribute(name: string): void { this.inputBox.inputElement.removeAttribute(name); } @@ -118,7 +122,7 @@ export class QuickInputBox extends Disposable { this.inputBox.layout(); } - style(styles: IInputBoxStyles) { + style(styles: IInputBoxStyles): void { this.inputBox.style(styles); } } diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index e69f7e5171..26e0817d8b 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -154,6 +154,7 @@ class ListElementRenderer implements IListRenderer e.elements.map(e => e.item)); + return Event.map(this.list.onDidChangeSelection, e => ({ items: e.elements.map(e => e.item), event: e.browserEvent })); } getAllVisibleChecked() { diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index 45ed700d16..b2dcfe9c81 100644 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -24,9 +24,15 @@ export interface IQuickPickItem { ariaLabel?: string; description?: string; detail?: string; + /** + * Allows to show a keybinding next to the item to indicate + * how the item can be triggered outside of the picker using + * keyboard shortcut. + */ keybinding?: ResolvedKeybinding; iconClasses?: string[]; italic?: boolean; + strikethrough?: boolean; highlights?: IQuickPickItemHighlights; buttons?: IQuickInputButton[]; /** @@ -170,6 +176,15 @@ export interface IQuickInput extends IDisposable { hide(): void; } +export interface IQuickPickAcceptEvent { + + /** + * Signals if the picker item is to be accepted + * in the background while keeping the picker open. + */ + inBackground: boolean; +} + export interface IQuickPick extends IQuickInput { value: string; @@ -186,7 +201,14 @@ export interface IQuickPick extends IQuickInput { readonly onDidChangeValue: Event; - readonly onDidAccept: Event; + readonly onDidAccept: Event; + + /** + * If enabled, will fire the `onDidAccept` event when + * pressing the arrow-right key with the idea of accepting + * the selected item without closing the picker. + */ + canAcceptInBackground: boolean; ok: boolean | 'default'; @@ -268,7 +290,6 @@ export interface IQuickInputButton { /** iconPath or iconClass required */ iconClass?: string; tooltip?: string; - alwaysShow?: boolean; } export interface IQuickPickItemButtonEvent { diff --git a/src/vs/base/parts/tree/browser/treeViewModel.ts b/src/vs/base/parts/tree/browser/treeViewModel.ts index ed252c9879..876cc17588 100644 --- a/src/vs/base/parts/tree/browser/treeViewModel.ts +++ b/src/vs/base/parts/tree/browser/treeViewModel.ts @@ -45,8 +45,7 @@ export class HeightMap { totalSize = viewItem.top + viewItem.height; } - let boundSplice = this.heightMap.splice.bind(this.heightMap, i, 0); - + const startingIndex = i; let itemsToInsert: IViewItem[] = []; while (item = iterator.next()) { @@ -58,7 +57,7 @@ export class HeightMap { sizeDiff += viewItem.height; } - boundSplice.apply(this.heightMap, itemsToInsert); + this.heightMap.splice(startingIndex, 0, ...itemsToInsert); for (j = i; j < this.heightMap.length; j++) { viewItem = this.heightMap[j]; diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 4b88f6a784..694d68ff19 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -49,17 +49,16 @@ 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/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAutoSyncChannel, UserDataSyncStoreServiceChannel, UserDataSyncBackupStoreServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, UserDataSyncStoreServiceChannel, UserDataSyncBackupStoreServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-browser/userDataAutoSyncService'; -import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { NativeStorageService } from 'vs/platform/storage/node/storageService'; import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -197,7 +196,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService)); services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService)); - services.set(ISettingsSyncService, new SyncDescriptor(SettingsSynchroniser)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); registerConfiguration(); @@ -229,10 +227,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const userDataSyncBackupStoreServiceChannel = new UserDataSyncBackupStoreServiceChannel(userDataSyncBackupStoreService); server.registerChannel('userDataSyncBackupStoreService', userDataSyncBackupStoreServiceChannel); - const settingsSyncService = accessor.get(ISettingsSyncService); - const settingsSyncChannel = new SettingsSyncChannel(settingsSyncService); - server.registerChannel('settingsSync', settingsSyncChannel); - const userDataSyncService = accessor.get(IUserDataSyncService); const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService); server.registerChannel('userDataSync', userDataSyncChannel); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 985fb8733d..796712af27 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -28,12 +28,13 @@ import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/commo import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { endsWith } from 'vs/base/common/strings'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IFileService } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { IStorageMainService } from 'vs/platform/storage/node/storageMainService'; +import { IFileService } from 'vs/platform/files/common/files'; const RUN_TEXTMATE_IN_WORKER = false; @@ -97,6 +98,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileService private readonly fileService: IFileService, + @IStorageMainService private readonly storageService: IStorageMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeMainService private readonly themeMainService: IThemeMainService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @@ -226,7 +228,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { this.createTouchBar(); // Request handling - this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService); + const that = this; + this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService, { + get(key) { return that.storageService.get(key); }, + store(key, value) { that.storageService.store(key, value); } + }); // Eventing this.registerListeners(); diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 4cb44048c6..bf9388a08e 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1754,6 +1754,7 @@ export class TextModel extends Disposable implements model.ITextModel { if (ranges.length > 0) { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, + semanticTokensApplied: false, ranges: ranges }); } @@ -1764,6 +1765,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, + semanticTokensApplied: tokens !== null, ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] }); } @@ -1778,6 +1780,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._tokens.flush(); this._emitModelTokensChangedEvent({ tokenizationSupportChanged: true, + semanticTokensApplied: false, ranges: [{ fromLineNumber: 1, toLineNumber: this._buffer.getLineCount() @@ -1790,6 +1793,7 @@ export class TextModel extends Disposable implements model.ITextModel { this._emitModelTokensChangedEvent({ tokenizationSupportChanged: false, + semanticTokensApplied: false, ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }] }); } diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index 940102e435..739e6596b7 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -87,6 +87,7 @@ export interface IModelDecorationsChangedEvent { */ export interface IModelTokensChangedEvent { readonly tokenizationSupportChanged: boolean; + readonly semanticTokensApplied: boolean; readonly ranges: { /** * The start of the range (inclusive) diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index b29df25e7d..2574e13045 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -466,15 +466,16 @@ class SemanticColoringFeature extends Disposable { private _watchers: Record; private _semanticStyling: SemanticStyling; - private _configurationService: IConfigurationService; constructor(modelService: IModelService, themeService: IThemeService, configurationService: IConfigurationService, logService: ILogService) { super(); - this._configurationService = configurationService; this._watchers = Object.create(null); this._semanticStyling = this._register(new SemanticStyling(themeService, logService)); const isSemanticColoringEnabled = (model: ITextModel) => { + if (!themeService.getColorTheme().semanticHighlighting) { + return false; + } const options = configurationService.getValue(SemanticColoringFeature.SETTING_ID, { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }); return options && options.enabled; }; @@ -485,6 +486,20 @@ class SemanticColoringFeature extends Disposable { modelSemanticColoring.dispose(); delete this._watchers[model.uri.toString()]; }; + const handleSettingOrThemeChange = () => { + for (let model of modelService.getModels()) { + const curr = this._watchers[model.uri.toString()]; + if (isSemanticColoringEnabled(model)) { + if (!curr) { + register(model); + } + } else { + if (curr) { + deregister(model, curr); + } + } + } + }; this._register(modelService.onModelAdded((model) => { if (isSemanticColoringEnabled(model)) { register(model); @@ -496,22 +511,12 @@ class SemanticColoringFeature extends Disposable { deregister(model, curr); } })); - this._configurationService.onDidChangeConfiguration(e => { + this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(SemanticColoringFeature.SETTING_ID)) { - for (let model of modelService.getModels()) { - const curr = this._watchers[model.uri.toString()]; - if (isSemanticColoringEnabled(model)) { - if (!curr) { - register(model); - } - } else { - if (curr) { - deregister(model, curr); - } - } - } + handleSettingOrThemeChange(); } - }); + })); + this._register(themeService.onDidColorThemeChange(handleSettingOrThemeChange)); } } @@ -525,12 +530,9 @@ class SemanticStyling extends Disposable { ) { super(); this._caches = new WeakMap(); - if (this._themeService) { - // workaround for tests which use undefined... :/ - this._register(this._themeService.onDidColorThemeChange(() => { - this._caches = new WeakMap(); - })); - } + this._register(this._themeService.onDidColorThemeChange(() => { + this._caches = new WeakMap(); + })); } public get(provider: DocumentSemanticTokensProvider): SemanticColoringProviderStyling { @@ -768,14 +770,12 @@ class ModelSemanticColoring extends Disposable { this._fetchSemanticTokens.schedule(); })); - if (themeService) { - // workaround for tests which use undefined... :/ - this._register(themeService.onDidColorThemeChange(_ => { - // clear out existing tokens - this._setSemanticTokens(null, null, null, []); - this._fetchSemanticTokens.schedule(); - })); - } + this._register(themeService.onDidColorThemeChange(_ => { + // clear out existing tokens + this._setSemanticTokens(null, null, null, []); + this._fetchSemanticTokens.schedule(); + })); + this._fetchSemanticTokens.schedule(0); } diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 909053dadf..71672bed36 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -36,12 +36,6 @@ export namespace InspectTokensNLS { } export namespace GoToLineNLS { - export const gotoLineLabelValidLineAndColumn = nls.localize('gotoLineLabelValidLineAndColumn', "Go to line {0} and character {1}"); - export const gotoLineLabelValidLine = nls.localize('gotoLineLabelValidLine', "Go to line {0}"); - export const gotoLineLabelEmptyWithLineLimit = nls.localize('gotoLineLabelEmptyWithLineLimit', "Type a line number between 1 and {0} to navigate to"); - export const gotoLineLabelEmptyWithLineAndColumnLimit = nls.localize('gotoLineLabelEmptyWithLineAndColumnLimit', "Type a character between 1 and {0} to navigate to"); - export const gotoLineAriaLabel = nls.localize('gotoLineAriaLabel', "Current Line: {0}. Go to line {1}."); - export const gotoLineActionInput = nls.localize('gotoLineActionInput', "Type a line number, followed by an optional colon and a character number to navigate to"); export const gotoLineActionLabel = nls.localize('gotoLineActionLabel', "Go to Line..."); } @@ -50,28 +44,13 @@ export namespace QuickHelpNLS { } export namespace QuickCommandNLS { - export const ariaLabelEntryWithKey = nls.localize('ariaLabelEntryWithKey', "{0}, {1}, commands"); - export const ariaLabelEntry = nls.localize('ariaLabelEntry', "{0}, commands"); - export const quickCommandActionInput = nls.localize('quickCommandActionInput', "Type the name of an action you want to execute"); export const quickCommandActionLabel = nls.localize('quickCommandActionLabel', "Command Palette"); export const quickCommandHelp = nls.localize('quickCommandActionHelp', "Show And Run Commands"); } export namespace QuickOutlineNLS { - export const entryAriaLabel = nls.localize('entryAriaLabel', "{0}, symbols"); - export const quickOutlineActionInput = nls.localize('quickOutlineActionInput', "Type the name of an identifier you wish to navigate to"); export const quickOutlineActionLabel = nls.localize('quickOutlineActionLabel', "Go to Symbol..."); - export const _symbols_ = nls.localize('symbols', "symbols ({0})"); - export const _modules_ = nls.localize('modules', "modules ({0})"); - export const _class_ = nls.localize('class', "classes ({0})"); - export const _interface_ = nls.localize('interface', "interfaces ({0})"); - export const _method_ = nls.localize('method', "methods ({0})"); - export const _function_ = nls.localize('function', "functions ({0})"); - export const _property_ = nls.localize('property', "properties ({0})"); - export const _variable_ = nls.localize('variable', "variables ({0})"); - export const _variable2_ = nls.localize('variable2', "variables ({0})"); - export const _constructor_ = nls.localize('_constructor', "constructors ({0})"); - export const _call_ = nls.localize('call', "calls ({0})"); + export const quickOutlineByCategoryActionLabel = nls.localize('quickOutlineByCategoryActionLabel', "Go to Symbol by Category..."); } export namespace StandaloneCodeEditorNLS { diff --git a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts index 7638caf2d6..f041d35753 100644 --- a/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/commandsQuickAccess.ts @@ -27,7 +27,7 @@ export abstract class AbstractEditorCommandsQuickAccessProvider extends Abstract /** * Subclasses to provide the current active editor control. */ - abstract activeTextEditorControl: IEditor | undefined; + protected abstract activeTextEditorControl: IEditor | undefined; protected getCodeEditorCommandPicks(): ICommandQuickPick[] { const activeTextEditorControl = this.activeTextEditorControl; diff --git a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts new file mode 100644 index 0000000000..92f12acf96 --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { IModelDeltaDecoration, OverviewRulerLane, ITextModel } from 'vs/editor/common/model'; +import { IRange } from 'vs/editor/common/core/range'; +import { themeColorFromId } from 'vs/platform/theme/common/themeService'; +import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; +import { IQuickPick, IQuickPickItem, IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; +import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { once } from 'vs/base/common/functional'; + +interface IEditorLineDecoration { + rangeHighlightId: string; + overviewRulerDecorationId: string; +} + +/** + * A reusable quick access provider for the editor with support + * for adding decorations for navigating in the currently active file + * (for example "Go to line", "Go to symbol"). + */ +export abstract class AbstractEditorNavigationQuickAccessProvider implements IQuickAccessProvider { + + //#region Provider methods + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Provide based on current active editor + let pickerDisposable = this.doProvide(picker, token); + disposables.add(toDisposable(() => pickerDisposable.dispose())); + + // Re-create whenever the active editor changes + disposables.add(this.onDidActiveTextEditorControlChange(() => { + pickerDisposable.dispose(); + pickerDisposable = this.doProvide(picker, token); + })); + + return disposables; + } + + private doProvide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // With text control + const editor = this.activeTextEditorControl; + if (editor && this.canProvideWithTextEditor(editor)) { + + // Restore any view state if this picker was closed + // without actually going to a line + const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); + once(token.onCancellationRequested)(() => { + if (lastKnownEditorViewState) { + editor.restoreViewState(lastKnownEditorViewState); + } + }); + + // Clean up decorations on dispose + disposables.add(toDisposable(() => this.clearDecorations(editor))); + + // Ask subclass for entries + disposables.add(this.provideWithTextEditor(editor, picker, token)); + } + + // Without text control + else { + disposables.add(this.provideWithoutTextEditor(picker, token)); + } + + return disposables; + } + + /** + * Subclasses to implement if they can operate on the text editor. + */ + protected canProvideWithTextEditor(editor: IEditor): boolean { + return true; + } + + /** + * Subclasses to implement to provide picks for the picker when an editor is active. + */ + protected abstract provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable; + + /** + * Subclasses to implement to provide picks for the picker when no editor is active. + */ + protected abstract provideWithoutTextEditor(picker: IQuickPick, token: CancellationToken): IDisposable; + + protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean): void { + editor.setSelection(range); + editor.revealRangeInCenter(range, ScrollType.Smooth); + editor.focus(); + } + + protected getModel(editor: IEditor | IDiffEditor): ITextModel | undefined { + return isDiffEditor(editor) ? + editor.getModel()?.modified : + editor.getModel() as ITextModel; + } + + //#endregion + + + //#region Editor access + + /** + * Subclasses to provide an event when the active editor control changes. + */ + protected abstract readonly onDidActiveTextEditorControlChange: Event; + + /** + * Subclasses to provide the current active editor control. + */ + protected abstract activeTextEditorControl: IEditor | undefined; + + //#endregion + + + //#region Decorations Utils + + private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined; + + protected addDecorations(editor: IEditor, range: IRange): void { + editor.changeDecorations(changeAccessor => { + + // Reset old decorations if any + const deleteDecorations: string[] = []; + if (this.rangeHighlightDecorationId) { + deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId); + deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); + + this.rangeHighlightDecorationId = undefined; + } + + // Add new decorations for the range + const newDecorations: IModelDeltaDecoration[] = [ + + // highlight the entire line on the range + { + range, + options: { + className: 'rangeHighlight', + isWholeLine: true + } + }, + + // also add overview ruler highlight + { + range, + options: { + overviewRuler: { + color: themeColorFromId(overviewRulerRangeHighlight), + position: OverviewRulerLane.Full + } + } + } + ]; + + const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations); + + this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId }; + }); + } + + protected clearDecorations(editor: IEditor): void { + const rangeHighlightDecorationId = this.rangeHighlightDecorationId; + if (rangeHighlightDecorationId) { + editor.changeDecorations(changeAccessor => { + changeAccessor.deltaDecorations([ + rangeHighlightDecorationId.overviewRulerDecorationId, + rangeHighlightDecorationId.rangeHighlightId + ], []); + }); + + this.rangeHighlightDecorationId = undefined; + } + } + + //#endregion +} diff --git a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts index 724f205b2e..c60bbfa792 100644 --- a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts @@ -4,74 +4,32 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IQuickPick, IQuickPickItem, IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { DisposableStore, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { once } from 'vs/base/common/functional'; -import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; -import { ITextModel } from 'vs/editor/common/model'; -import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; import { IRange } from 'vs/editor/common/core/range'; -import { withNullAsUndefined } from 'vs/base/common/types'; -import { AbstractEditorQuickAccessProvider } from 'vs/editor/contrib/quickAccess/quickAccess'; +import { AbstractEditorNavigationQuickAccessProvider } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; import { IPosition } from 'vs/editor/common/core/position'; interface IGotoLineQuickPickItem extends IQuickPickItem, Partial { } -export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorQuickAccessProvider { +export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { static PREFIX = ':'; - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { + const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line."); - // Disable filtering & sorting, we control the results - picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; - - // Provide based on current active editor - let pickerDisposable = this.doProvide(picker, token); - disposables.add(toDisposable(() => pickerDisposable.dispose())); - - // Re-create whenever the active editor changes - disposables.add(this.onDidActiveTextEditorControlChange(() => { - pickerDisposable.dispose(); - pickerDisposable = this.doProvide(picker, token); - })); - - return disposables; - } - - private doProvide(picker: IQuickPick, token: CancellationToken): IDisposable { - - // With text control - if (this.activeTextEditorControl) { - return this.doProvideWithTextEditor(this.activeTextEditorControl, picker, token); - } - - // Without text control - return this.doProvideWithoutTextEditor(picker); - } - - private doProvideWithoutTextEditor(picker: IQuickPick): IDisposable { - const label = localize('cannotRunGotoLine', "Open a text file first to go to a line."); picker.items = [{ label }]; picker.ariaLabel = label; return Disposable.None; } - private doProvideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { + protected provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { const disposables = new DisposableStore(); - // Restore any view state if this picker was closed - // without actually going to a line - const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); - once(token.onCancellationRequested)(() => { - if (lastKnownEditorViewState) { - editor.restoreViewState(lastKnownEditorViewState); - } - }); - // Goto line once picked disposables.add(picker.onDidAccept(() => { const [item] = picker.selectedItems; @@ -80,7 +38,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor return; } - this.gotoLine(editor, this.toRange(item.lineNumber, item.column), picker.keyMods); + this.gotoLocation(editor, this.toRange(item.lineNumber, item.column), picker.keyMods); picker.hide(); } @@ -117,9 +75,6 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor updatePickerAndEditor(); disposables.add(picker.onDidChangeValue(() => updatePickerAndEditor())); - // Clean up decorations on dispose - disposables.add(toDisposable(() => this.clearDecorations(editor))); - return disposables; } @@ -191,16 +146,4 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor private lineCount(editor: IEditor): number { return this.getModel(editor)?.getLineCount() ?? 0; } - - private getModel(editor: IEditor | IDiffEditor): ITextModel | undefined { - return isDiffEditor(editor) ? - editor.getModel()?.modified : - editor.getModel() as ITextModel; - } - - protected gotoLine(editor: IEditor, range: IRange, keyMods: IKeyMods): void { - editor.setSelection(range); - editor.revealRangeInCenter(range, ScrollType.Smooth); - editor.focus(); - } } diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts new file mode 100644 index 0000000000..ac83aff021 --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -0,0 +1,420 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPick, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { DisposableStore, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; +import { ITextModel } from 'vs/editor/common/model'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { AbstractEditorNavigationQuickAccessProvider } from 'vs/editor/contrib/quickAccess/editorNavigationQuickAccess'; +import { DocumentSymbol, SymbolKinds, SymbolTag, DocumentSymbolProviderRegistry, SymbolKind } from 'vs/editor/common/modes'; +import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { values } from 'vs/base/common/collections'; +import { trim, format } from 'vs/base/common/strings'; +import { fuzzyScore, FuzzyScore, createMatches } from 'vs/base/common/filters'; + +interface IGotoSymbolQuickPickItem extends IQuickPickItem { + kind: SymbolKind, + index: number, + score?: FuzzyScore; + range?: { decoration: IRange, selection: IRange }, +} + +export interface IGotoSymbolQuickAccessProviderOptions { + openSideBySideDirection: () => undefined | 'right' | 'down' +} + +export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { + + static PREFIX = '@'; + static SCOPE_PREFIX = ':'; + static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`; + + constructor(private options?: IGotoSymbolQuickAccessProviderOptions) { + super(); + } + + protected provideWithoutTextEditor(picker: IQuickPick): IDisposable { + const label = localize('cannotRunGotoSymbolWithoutEditor', "Open a text editor first to go to a symbol."); + + picker.items = [{ label, index: 0, kind: SymbolKind.String }]; + picker.ariaLabel = label; + + return Disposable.None; + } + + protected provideWithTextEditor(editor: IEditor, picker: IQuickPick, token: CancellationToken): IDisposable { + const model = this.getModel(editor); + if (!model) { + return Disposable.None; + } + + // Provide symbols from model if available in registry + if (DocumentSymbolProviderRegistry.has(model)) { + return this.doProvideWithEditorSymbols(editor, model, picker, token); + } + + // Otherwise show an entry for a model without registry + // But give a chance to resolve the symbols at a later + // point if possible + return this.doProvideWithoutEditorSymbols(editor, model, picker, token); + } + + private doProvideWithoutEditorSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Generic pick for not having any symbol information + const label = localize('cannotRunGotoSymbolWithoutSymbolProvider', "Open a text editor with symbol information first to go to a symbol."); + picker.items = [{ label, index: 0, kind: SymbolKind.String }]; + picker.ariaLabel = label; + + // Listen to changes to the registry and see if eventually + // we do get symbols. This can happen if the picker is opened + // very early after the model has loaded but before the + // language registry is ready. + // https://github.com/microsoft/vscode/issues/70607 + const symbolProviderListener = disposables.add(DocumentSymbolProviderRegistry.onDidChange(() => { + if (DocumentSymbolProviderRegistry.has(model)) { + symbolProviderListener.dispose(); + + disposables.add(this.doProvideWithEditorSymbols(editor, model, picker, token)); + } + })); + + return disposables; + } + + private doProvideWithEditorSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Goto symbol once picked + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (item && item.range) { + this.gotoLocation(editor, item.range.selection, picker.keyMods); + + picker.hide(); + } + })); + + // Goto symbol side by side if enabled + disposables.add(picker.onDidTriggerItemButton(({ item }) => { + if (item && item.range) { + this.gotoLocation(editor, item.range.selection, picker.keyMods, true); + + picker.hide(); + } + })); + + // Resolve symbols from document once and reuse this + // request for all filtering and typing then on + const symbolsPromise = this.getDocumentSymbols(model, true, token); + + // Set initial picks and update on type + let picksCts: CancellationTokenSource | undefined = undefined; + const updatePickerItems = async () => { + + // Cancel any previous ask for picks and busy + picksCts?.dispose(true); + picker.busy = false; + + // Create new cancellation source for this run + picksCts = new CancellationTokenSource(token); + + // Collect symbol picks + picker.busy = true; + try { + const items = await this.getSymbolPicks(symbolsPromise, picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim(), picksCts.token); + if (token.isCancellationRequested) { + return; + } + + picker.items = items; + } finally { + if (!token.isCancellationRequested) { + picker.busy = false; + } + } + }; + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Reveal and decorate when active item changes + // However, ignore the very first event so that + // opening the picker is not immediately revealing + // and decorating the first entry. + let ignoreFirstActiveEvent = true; + disposables.add(picker.onDidChangeActive(() => { + const [item] = picker.activeItems; + if (item && item.range) { + if (ignoreFirstActiveEvent) { + ignoreFirstActiveEvent = false; + return; + } + + // Reveal + editor.revealRangeInCenter(item.range.selection, ScrollType.Smooth); + + // Decorate + this.addDecorations(editor, item.range.decoration); + } + })); + + return disposables; + } + + private async getSymbolPicks(symbolsPromise: Promise, filter: string, token: CancellationToken): Promise> { + const symbols = await symbolsPromise; + if (token.isCancellationRequested) { + return []; + } + + // Normalize filter + const filterBySymbolKind = filter.indexOf(AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX) === 0; + const filterPos = filterBySymbolKind ? 1 : 0; + const [symbolFilter, containerFilter] = filter.split(' ') as [string, string | undefined]; + const symbolFilterLow = symbolFilter.toLowerCase(); + const containerFilterLow = containerFilter?.toLowerCase(); + + // Convert to symbol picks and apply filtering + const filteredSymbolPicks: IGotoSymbolQuickPickItem[] = []; + for (let index = 0; index < symbols.length; index++) { + const symbol = symbols[index]; + + const symbolLabel = trim(symbol.name); + const containerLabel = symbol.containerName; + + let symbolScore: FuzzyScore | undefined = undefined; + let containerScore: FuzzyScore | undefined = undefined; + + let includeSymbol = true; + if (filter.length > filterPos) { + + // Score by symbol + symbolScore = fuzzyScore(symbolFilter, symbolFilterLow, filterPos, symbolLabel, symbolLabel.toLowerCase(), 0, true); + includeSymbol = !!symbolScore; + + // Score by container if specified + if (includeSymbol && containerFilter && containerFilterLow) { + if (containerLabel) { + containerScore = fuzzyScore(containerFilter, containerFilterLow, filterPos, containerLabel, containerLabel.toLowerCase(), 0, true); + } + + includeSymbol = !!containerScore; + } + } + + if (includeSymbol) { + const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; + const deprecated = symbol.tags && symbol.tags.indexOf(SymbolTag.Deprecated) >= 0; + + filteredSymbolPicks.push({ + index, + kind: symbol.kind, + score: symbolScore, + label: symbolLabelWithIcon, + ariaLabel: localize('symbolsAriaLabel', "{0}, symbols picker", symbolLabel), + description: containerLabel, + highlights: deprecated ? undefined : { + label: createMatches(symbolScore, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */), + description: createMatches(containerScore) + }, + range: { + selection: Range.collapseToStart(symbol.selectionRange), + decoration: symbol.range + }, + strikethrough: deprecated, + buttons: (() => { + const openSideBySideDirection = this.options?.openSideBySideDirection(); + if (!openSideBySideDirection) { + return undefined; + } + + return [ + { + iconClass: openSideBySideDirection === 'right' ? 'codicon-split-horizontal' : 'codicon-split-vertical', + tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom") + } + ]; + })() + }); + } + } + + // Sort by score + const sortedFilteredSymbolPicks = filteredSymbolPicks.sort((symbolA, symbolB) => filterBySymbolKind ? + this.compareByKindAndScore(symbolA, symbolB) : + this.compareByScore(symbolA, symbolB) + ); + + // Add separator for types + // - @ only total number of symbols + // - @: grouped by symbol kind + let symbolPicks: Array = []; + if (filterBySymbolKind) { + let lastSymbolKind: SymbolKind | undefined = undefined; + let lastSeparator: IQuickPickSeparator | undefined = undefined; + let lastSymbolKindCounter = 0; + + function updateLastSeparatorLabel(): void { + if (lastSeparator && typeof lastSymbolKind === 'number' && lastSymbolKindCounter > 0) { + lastSeparator.label = format(NLS_SYMBOL_KIND_CACHE[lastSymbolKind] || FALLBACK_NLS_SYMBOL_KIND, lastSymbolKindCounter); + } + } + + for (const symbolPick of sortedFilteredSymbolPicks) { + + // Found new kind + if (lastSymbolKind !== symbolPick.kind) { + + // Update last separator with number of symbols we found for kind + updateLastSeparatorLabel(); + + lastSymbolKind = symbolPick.kind; + lastSymbolKindCounter = 1; + + // Add new separator for new kind + lastSeparator = { type: 'separator' }; + symbolPicks.push(lastSeparator); + } + + // Existing kind, keep counting + else { + lastSymbolKindCounter++; + } + + // Add to final result + symbolPicks.push(symbolPick); + } + + // Update last separator with number of symbols we found for kind + updateLastSeparatorLabel(); + } else { + symbolPicks = [ + { label: localize('symbols', "symbols ({0})", filteredSymbolPicks.length), type: 'separator' }, + ...sortedFilteredSymbolPicks + ]; + } + + return symbolPicks; + } + + private compareByScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number { + if (!symbolA.score && symbolB.score) { + return 1; + } else if (symbolA.score && !symbolB.score) { + return -1; + } + + if (symbolA.score && symbolB.score) { + if (symbolA.score[0] > symbolB.score[0]) { + return -1; + } else if (symbolA.score[0] < symbolB.score[0]) { + return 1; + } + } + + if (symbolA.index < symbolB.index) { + return -1; + } else if (symbolA.index > symbolB.index) { + return 1; + } + + return 0; + } + + private compareByKindAndScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number { + const kindA = NLS_SYMBOL_KIND_CACHE[symbolA.kind] || FALLBACK_NLS_SYMBOL_KIND; + const kindB = NLS_SYMBOL_KIND_CACHE[symbolB.kind] || FALLBACK_NLS_SYMBOL_KIND; + + // Sort by type first if scoped search + const result = kindA.localeCompare(kindB); + if (result === 0) { + return this.compareByScore(symbolA, symbolB); + } + + return result; + } + + private async getDocumentSymbols(document: ITextModel, flatten: boolean, token: CancellationToken): Promise { + const model = await OutlineModel.create(document, token); + if (token.isCancellationRequested) { + return []; + } + + const roots: DocumentSymbol[] = []; + for (const child of values(model.children)) { + if (child instanceof OutlineElement) { + roots.push(child.symbol); + } else { + roots.push(...values(child.children).map(child => child.symbol)); + } + } + + let flatEntries: DocumentSymbol[] = []; + if (flatten) { + this.flattenDocumentSymbols(flatEntries, roots, ''); + } else { + flatEntries = roots; + } + + return flatEntries.sort((symbolA, symbolB) => Range.compareRangesUsingStarts(symbolA.range, symbolB.range)); + } + + private flattenDocumentSymbols(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void { + for (const entry of entries) { + bucket.push({ + kind: entry.kind, + tags: entry.tags, + name: entry.name, + detail: entry.detail, + containerName: entry.containerName || overrideContainerLabel, + range: entry.range, + selectionRange: entry.selectionRange, + children: undefined, // we flatten it... + }); + + // Recurse over children + if (entry.children) { + this.flattenDocumentSymbols(bucket, entry.children, entry.name); + } + } + } +} + +// #region NLS Helpers + +const FALLBACK_NLS_SYMBOL_KIND = localize('property', "properties ({0})"); +const NLS_SYMBOL_KIND_CACHE: { [type: number]: string } = { + [SymbolKind.Method]: localize('method', "methods ({0})"), + [SymbolKind.Function]: localize('function', "functions ({0})"), + [SymbolKind.Constructor]: localize('_constructor', "constructors ({0})"), + [SymbolKind.Variable]: localize('variable', "variables ({0})"), + [SymbolKind.Class]: localize('class', "classes ({0})"), + [SymbolKind.Struct]: localize('struct', "structs ({0})"), + [SymbolKind.Event]: localize('event', "events ({0})"), + [SymbolKind.Operator]: localize('operator', "operators ({0})"), + [SymbolKind.Interface]: localize('interface', "interfaces ({0})"), + [SymbolKind.Namespace]: localize('namespace', "namespaces ({0})"), + [SymbolKind.Package]: localize('package', "packages ({0})"), + [SymbolKind.TypeParameter]: localize('typeParameter', "type parameters ({0})"), + [SymbolKind.Module]: localize('modules', "modules ({0})"), + [SymbolKind.Property]: localize('property', "properties ({0})"), + [SymbolKind.Enum]: localize('enum', "enumerations ({0})"), + [SymbolKind.EnumMember]: localize('enumMember', "enumeration members ({0})"), + [SymbolKind.String]: localize('string', "strings ({0})"), + [SymbolKind.File]: localize('file', "files ({0})"), + [SymbolKind.Array]: localize('array', "arrays ({0})"), + [SymbolKind.Number]: localize('number', "numbers ({0})"), + [SymbolKind.Boolean]: localize('boolean', "booleans ({0})"), + [SymbolKind.Object]: localize('object', "objects ({0})"), + [SymbolKind.Key]: localize('key', "keys ({0})"), + [SymbolKind.Field]: localize('field', "fields ({0})"), + [SymbolKind.Constant]: localize('constant', "constants ({0})") +}; + +//#endregion diff --git a/src/vs/editor/contrib/quickAccess/quickAccess.ts b/src/vs/editor/contrib/quickAccess/quickAccess.ts deleted file mode 100644 index 08f2b4bf72..0000000000 --- a/src/vs/editor/contrib/quickAccess/quickAccess.ts +++ /dev/null @@ -1,104 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; -import { IEditor } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration, OverviewRulerLane } from 'vs/editor/common/model'; -import { IRange } from 'vs/editor/common/core/range'; -import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; -import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { Event } from 'vs/base/common/event'; - -interface IEditorLineDecoration { - rangeHighlightId: string; - overviewRulerDecorationId: string; -} - -/** - * A reusable quick access provider for the editor with support for adding decorations. - */ -export abstract class AbstractEditorQuickAccessProvider implements IQuickAccessProvider { - - /** - * Subclasses to provide an event when the active editor control changes. - */ - abstract readonly onDidActiveTextEditorControlChange: Event; - - /** - * Subclasses to provide the current active editor control. - */ - abstract activeTextEditorControl: IEditor | undefined; - - /** - * Subclasses to implement the quick access picker. - */ - abstract provide(picker: IQuickPick, token: CancellationToken): IDisposable; - - - //#region Decorations Utils - - private rangeHighlightDecorationId: IEditorLineDecoration | undefined = undefined; - - protected addDecorations(editor: IEditor, range: IRange): void { - editor.changeDecorations(changeAccessor => { - - // Reset old decorations if any - const deleteDecorations: string[] = []; - if (this.rangeHighlightDecorationId) { - deleteDecorations.push(this.rangeHighlightDecorationId.overviewRulerDecorationId); - deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); - - this.rangeHighlightDecorationId = undefined; - } - - // Add new decorations for the range - const newDecorations: IModelDeltaDecoration[] = [ - - // highlight the entire line on the range - { - range, - options: { - className: 'rangeHighlight', - isWholeLine: true - } - }, - - // also add overview ruler highlight - { - range, - options: { - overviewRuler: { - color: themeColorFromId(overviewRulerRangeHighlight), - position: OverviewRulerLane.Full - } - } - } - ]; - - const [rangeHighlightId, overviewRulerDecorationId] = changeAccessor.deltaDecorations(deleteDecorations, newDecorations); - - this.rangeHighlightDecorationId = { rangeHighlightId, overviewRulerDecorationId }; - }); - } - - protected clearDecorations(editor: IEditor): void { - const rangeHighlightDecorationId = this.rangeHighlightDecorationId; - if (rangeHighlightDecorationId) { - editor.changeDecorations(changeAccessor => { - changeAccessor.deltaDecorations([ - rangeHighlightDecorationId.overviewRulerDecorationId, - rangeHighlightDecorationId.rangeHighlightId - ], []); - }); - - this.rangeHighlightDecorationId = undefined; - } - } - - //#endregion -} diff --git a/src/vs/editor/editor.main.ts b/src/vs/editor/editor.main.ts index cd04664bf4..f89c442bc3 100644 --- a/src/vs/editor/editor.main.ts +++ b/src/vs/editor/editor.main.ts @@ -7,11 +7,9 @@ import 'vs/editor/editor.all'; import 'vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp'; import 'vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard'; import 'vs/editor/standalone/browser/inspectTokens/inspectTokens'; -import 'vs/editor/standalone/browser/quickOpen/gotoLine'; -import 'vs/editor/standalone/browser/quickOpen/quickCommand'; -import 'vs/editor/standalone/browser/quickOpen/quickOutline'; import 'vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess'; import 'vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess'; +import 'vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess'; import 'vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess'; import 'vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch'; import 'vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast'; diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts index f94ac022cc..baa89f0a48 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.ts @@ -11,15 +11,20 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { AbstractEditorCommandsQuickAccessProvider } from 'vs/editor/contrib/quickAccess/commandsQuickAccess'; import { IEditor } from 'vs/editor/common/editorCommon'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class StandaloneCommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider { - get activeTextEditorControl(): IEditor | undefined { return withNullAsUndefined(this.codeEditorService.getFocusedCodeEditor()); } + protected get activeTextEditorControl(): IEditor | undefined { return withNullAsUndefined(this.codeEditorService.getFocusedCodeEditor()); } constructor( @IInstantiationService instantiationService: IInstantiationService, @@ -42,3 +47,30 @@ Registry.as(Extensions.Quickaccess).registerQuickAccessPro prefix: StandaloneCommandsQuickAccessProvider.PREFIX, helpEntries: [{ description: QuickCommandNLS.quickCommandHelp, needsEditor: true }] }); + +export class GotoLineAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.quickCommand', + label: QuickCommandNLS.quickCommandActionLabel, + alias: 'Command Palette', + precondition: undefined, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyCode.F1, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'z_commands', + order: 1 + } + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show(StandaloneCommandsQuickAccessProvider.PREFIX); + } +} + +registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts index d9857fb156..d64b412ebd 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.ts @@ -10,22 +10,51 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { withNullAsUndefined } from 'vs/base/common/types'; import { GoToLineNLS } from 'vs/editor/common/standaloneStrings'; import { Event } from 'vs/base/common/event'; +import { EditorAction, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class StandaloneGotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { - readonly onDidActiveTextEditorControlChange = Event.None; + protected readonly onDidActiveTextEditorControlChange = Event.None; constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) { super(); } - get activeTextEditorControl() { + protected get activeTextEditorControl() { return withNullAsUndefined(this.editorService.getFocusedCodeEditor()); } } Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ ctor: StandaloneGotoLineQuickAccessProvider, - prefix: AbstractGotoLineQuickAccessProvider.PREFIX, + prefix: StandaloneGotoLineQuickAccessProvider.PREFIX, helpEntries: [{ description: GoToLineNLS.gotoLineActionLabel, needsEditor: true }] }); + +export class GotoLineAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.gotoLine', + label: GoToLineNLS.gotoLineActionLabel, + alias: 'Go to Line...', + precondition: undefined, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyMod.CtrlCmd | KeyCode.KEY_G, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G }, + weight: KeybindingWeight.EditorContrib + } + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show(StandaloneGotoLineQuickAccessProvider.PREFIX); + } +} + +registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts new file mode 100644 index 0000000000..3c3b1e974b --- /dev/null +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings'; +import { Event } from 'vs/base/common/event'; +import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; + +export class StandaloneGotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { + + protected readonly onDidActiveTextEditorControlChange = Event.None; + + constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) { + super(); + } + + protected get activeTextEditorControl() { + return withNullAsUndefined(this.editorService.getFocusedCodeEditor()); + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: StandaloneGotoSymbolQuickAccessProvider, + prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, + helpEntries: [ + { description: QuickOutlineNLS.quickOutlineActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true }, + { description: QuickOutlineNLS.quickOutlineByCategoryActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } + ] +}); + +export class GotoLineAction extends EditorAction { + + constructor() { + super({ + id: 'editor.action.quickOutline', + label: QuickOutlineNLS.quickOutlineActionLabel, + alias: 'Go to Symbol...', + precondition: EditorContextKeys.hasDocumentSymbolProvider, + kbOpts: { + kbExpr: EditorContextKeys.focus, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O, + weight: KeybindingWeight.EditorContrib + }, + contextMenuOpts: { + group: 'navigation', + order: 3 + } + }); + } + + run(accessor: ServicesAccessor): void { + accessor.get(IQuickInputService).quickAccess.show(AbstractGotoSymbolQuickAccessProvider.PREFIX); + } +} + +registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts index ebd630299a..2caa480fe2 100644 --- a/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.ts @@ -8,8 +8,8 @@ import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/ import { QuickHelpNLS } from 'vs/editor/common/standaloneStrings'; import { HelpQuickAccessProvider } from 'vs/platform/quickinput/browser/helpQuickAccess'; -Registry.as(Extensions.Quickaccess).defaultProvider = { +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ ctor: HelpQuickAccessProvider, prefix: '', helpEntries: [{ description: QuickHelpNLS.helpQuickAccessActionLabel, needsEditor: true }] -}; +}); diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css deleted file mode 100644 index b584d26a67..0000000000 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.css +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, -.monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { - color: #0066BF; -} - -.vs-dark .monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, -.vs-dark .monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { - color: #0097fb; -} - -.hc-black .monaco-quick-open-widget .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, -.hc-black .monaco-quick-open-widget .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { - color: #F38518; -} \ No newline at end of file diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts deleted file mode 100644 index 37b874b8f4..0000000000 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts +++ /dev/null @@ -1,172 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./editorQuickOpen'; -import { QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, IActionOptions, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import { IEditorContribution, ScrollType, IEditor } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration } from 'vs/editor/common/model'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { QuickOpenEditorWidget } from 'vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; - -export interface IQuickOpenControllerOpts { - inputAriaLabel: string; - getModel(value: string): QuickOpenModel; - getAutoFocus(searchValue: string): IAutoFocus; -} - -export class QuickOpenController implements IEditorContribution, IDecorator { - - public static readonly ID = 'editor.controller.quickOpenController'; - - public static get(editor: ICodeEditor): QuickOpenController { - return editor.getContribution(QuickOpenController.ID); - } - - private readonly editor: ICodeEditor; - 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; - } - - public dispose(): void { - // Dispose widget - if (this.widget) { - this.widget.destroy(); - this.widget = null; - } - } - - public run(opts: IQuickOpenControllerOpts): void { - if (this.widget) { - this.widget.destroy(); - this.widget = null; - } - - // Create goto line widget - let onClose = (canceled: boolean) => { - // Clear Highlight Decorations if present - this.clearDecorations(); - - // Restore selection if canceled - if (canceled && this.lastKnownEditorSelection) { - this.editor.setSelection(this.lastKnownEditorSelection); - this.editor.revealRangeInCenterIfOutsideViewport(this.lastKnownEditorSelection, ScrollType.Smooth); - } - - this.lastKnownEditorSelection = null; - - // Return focus to the editor if - // - focus is back on the element because no other focusable element was clicked - // - a command was picked from the picker which indicates the editor should get focused - if (document.activeElement === document.body || !canceled) { - this.editor.focus(); - } - }; - - this.widget = new QuickOpenEditorWidget( - this.editor, - () => onClose(false), - () => onClose(true), - (value: string) => { - this.widget!.setInput(opts.getModel(value), opts.getAutoFocus(value)); - }, - { - inputAriaLabel: opts.inputAriaLabel - }, - this.themeService - ); - - // Remember selection to be able to restore on cancel - if (!this.lastKnownEditorSelection) { - this.lastKnownEditorSelection = this.editor.getSelection(); - } - - // Show - this.widget.show(''); - } - - private static readonly _RANGE_HIGHLIGHT_DECORATION = ModelDecorationOptions.register({ - className: 'rangeHighlight', - isWholeLine: true - }); - - public decorateLine(range: Range, editor: ICodeEditor): void { - const oldDecorations: string[] = []; - if (this.rangeHighlightDecorationId) { - oldDecorations.push(this.rangeHighlightDecorationId); - this.rangeHighlightDecorationId = null; - } - - const newDecorations: IModelDeltaDecoration[] = [ - { - range: range, - options: QuickOpenController._RANGE_HIGHLIGHT_DECORATION - } - ]; - - const decorations = editor.deltaDecorations(oldDecorations, newDecorations); - this.rangeHighlightDecorationId = decorations[0]; - } - - public clearDecorations(): void { - if (this.rangeHighlightDecorationId) { - this.editor.deltaDecorations([this.rangeHighlightDecorationId], []); - this.rangeHighlightDecorationId = null; - } - } -} - -export interface IQuickOpenOpts { - /** - * provide the quick open model for the given search value. - */ - getModel(value: string): QuickOpenModel; - - /** - * provide the quick open auto focus mode for the given search value. - */ - getAutoFocus(searchValue: string): IAutoFocus; -} - -/** - * Base class for providing quick open in the editor. - */ -export abstract class BaseEditorQuickOpenAction extends EditorAction { - - private readonly _inputAriaLabel: string; - - constructor(inputAriaLabel: string, opts: IActionOptions) { - super(opts); - this._inputAriaLabel = inputAriaLabel; - } - - protected getController(editor: ICodeEditor): QuickOpenController { - return QuickOpenController.get(editor); - } - - protected _show(controller: QuickOpenController, opts: IQuickOpenOpts): void { - controller.run({ - inputAriaLabel: this._inputAriaLabel, - getModel: (value: string): QuickOpenModel => opts.getModel(value), - getAutoFocus: (searchValue: string): IAutoFocus => opts.getAutoFocus(searchValue) - }); - } -} - -export interface IDecorator { - decorateLine(range: Range, editor: IEditor): void; - clearDecorations(): void; -} - -registerEditorContribution(QuickOpenController.ID, QuickOpenController); diff --git a/src/vs/editor/standalone/browser/quickOpen/gotoLine.css b/src/vs/editor/standalone/browser/quickOpen/gotoLine.css deleted file mode 100644 index 958652fc3d..0000000000 --- a/src/vs/editor/standalone/browser/quickOpen/gotoLine.css +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-quick-open-widget { - font-size: 13px; -} \ No newline at end of file diff --git a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts b/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts deleted file mode 100644 index 0fcd25dd3f..0000000000 --- a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts +++ /dev/null @@ -1,177 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./gotoLine'; -import * as strings from 'vs/base/common/strings'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor, IDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import { IEditor, ScrollType } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ITextModel } from 'vs/editor/common/model'; -import { BaseEditorQuickOpenAction, IDecorator } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { GoToLineNLS } from 'vs/editor/common/standaloneStrings'; - -interface ParseResult { - position: Position; - isValid: boolean; - label: string; -} - -export class GotoLineEntry extends QuickOpenEntry { - private readonly parseResult: ParseResult; - private readonly decorator: IDecorator; - private readonly editor: IEditor; - - constructor(line: string, editor: IEditor, decorator: IDecorator) { - super(); - - this.editor = editor; - this.decorator = decorator; - this.parseResult = this.parseInput(line); - } - - private parseInput(line: string): ParseResult { - const numbers = line.split(',').map(part => parseInt(part, 10)).filter(part => !isNaN(part)); - let position: Position; - - if (numbers.length === 0) { - position = new Position(-1, -1); - } else if (numbers.length === 1) { - position = new Position(numbers[0], 1); - } else { - position = new Position(numbers[0], numbers[1]); - } - - let model: ITextModel | null; - if (isCodeEditor(this.editor)) { - model = this.editor.getModel(); - } else { - const diffModel = (this.editor).getModel(); - model = diffModel ? diffModel.modified : null; - } - - const isValid = model ? model.validatePosition(position).equals(position) : false; - let label: string; - - if (isValid) { - if (position.column && position.column > 1) { - label = strings.format(GoToLineNLS.gotoLineLabelValidLineAndColumn, position.lineNumber, position.column); - } else { - label = strings.format(GoToLineNLS.gotoLineLabelValidLine, position.lineNumber); - } - } else if (position.lineNumber < 1 || position.lineNumber > (model ? model.getLineCount() : 0)) { - label = strings.format(GoToLineNLS.gotoLineLabelEmptyWithLineLimit, model ? model.getLineCount() : 0); - } else { - label = strings.format(GoToLineNLS.gotoLineLabelEmptyWithLineAndColumnLimit, model ? model.getLineMaxColumn(position.lineNumber) : 0); - } - - return { - position: position, - isValid: isValid, - label: label - }; - } - - getLabel(): string { - return this.parseResult.label; - } - - getAriaLabel(): string { - const position = this.editor.getPosition(); - const currentLine = position ? position.lineNumber : 0; - return strings.format(GoToLineNLS.gotoLineAriaLabel, currentLine, this.parseResult.label); - } - - run(mode: Mode, _context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(); - } - - return this.runPreview(); - } - - runOpen(): boolean { - - // No-op if range is not valid - if (!this.parseResult.isValid) { - return false; - } - - // Apply selection and focus - const range = this.toSelection(); - (this.editor).setSelection(range); - (this.editor).revealRangeInCenter(range, ScrollType.Smooth); - this.editor.focus(); - - return true; - } - - runPreview(): boolean { - - // No-op if range is not valid - if (!this.parseResult.isValid) { - this.decorator.clearDecorations(); - return false; - } - - // Select Line Position - const range = this.toSelection(); - this.editor.revealRangeInCenter(range, ScrollType.Smooth); - - // Decorate if possible - this.decorator.decorateLine(range, this.editor); - - return false; - } - - private toSelection(): Range { - return new Range( - this.parseResult.position.lineNumber, - this.parseResult.position.column, - this.parseResult.position.lineNumber, - this.parseResult.position.column - ); - } -} - -export class GotoLineAction extends BaseEditorQuickOpenAction { - - constructor() { - super(GoToLineNLS.gotoLineActionInput, { - id: 'editor.action.gotoLine', - label: GoToLineNLS.gotoLineActionLabel, - alias: 'Go to Line...', - precondition: undefined, - kbOpts: { - kbExpr: EditorContextKeys.focus, - primary: KeyMod.CtrlCmd | KeyCode.KEY_G, - mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G }, - weight: KeybindingWeight.EditorContrib - } - }); - } - - run(accessor: ServicesAccessor, editor: ICodeEditor): void { - this._show(this.getController(editor), { - getModel: (value: string): QuickOpenModel => { - return new QuickOpenModel([new GotoLineEntry(value, editor, this.getController(editor))]); - }, - - getAutoFocus: (searchValue: string): IAutoFocus => { - return { - autoFocusFirstEntry: searchValue.length > 0 - }; - } - }); - } -} - -registerEditorAction(GotoLineAction); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts deleted file mode 100644 index 6bb2c1c65d..0000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts +++ /dev/null @@ -1,144 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as strings from 'vs/base/common/strings'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { matchesFuzzy } from 'vs/base/common/filters'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; -import { IEditor, IEditorAction } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { BaseEditorQuickOpenAction } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { QuickCommandNLS } from 'vs/editor/common/standaloneStrings'; - -export class EditorActionCommandEntry extends QuickOpenEntryGroup { - private readonly key: string; - private readonly action: IEditorAction; - private readonly editor: IEditor; - private readonly keyAriaLabel: string; - - constructor(key: string, keyAriaLabel: string, highlights: IHighlight[], action: IEditorAction, editor: IEditor) { - super(); - - this.key = key; - this.keyAriaLabel = keyAriaLabel; - this.setHighlights(highlights); - this.action = action; - this.editor = editor; - } - - public getLabel(): string { - return this.action.label; - } - - public getAriaLabel(): string { - if (this.keyAriaLabel) { - return strings.format(QuickCommandNLS.ariaLabelEntryWithKey, this.getLabel(), this.keyAriaLabel); - } - - return strings.format(QuickCommandNLS.ariaLabelEntry, this.getLabel()); - } - - public getGroupLabel(): string { - return this.key; - } - - public run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - - // Use a timeout to give the quick open widget a chance to close itself first - setTimeout(() => { - - // Some actions are enabled only when editor has focus - this.editor.focus(); - - try { - let promise = this.action.run() || Promise.resolve(); - promise.then(undefined, onUnexpectedError); - } catch (error) { - onUnexpectedError(error); - } - }, 50); - - return true; - } - - return false; - } -} - -export class QuickCommandAction extends BaseEditorQuickOpenAction { - - constructor() { - super(QuickCommandNLS.quickCommandActionInput, { - id: 'editor.action.quickCommand', - label: QuickCommandNLS.quickCommandActionLabel, - alias: 'Command Palette', - precondition: undefined, - kbOpts: { - kbExpr: EditorContextKeys.focus, - primary: KeyCode.F1, - weight: KeybindingWeight.EditorContrib - }, - contextMenuOpts: { - group: 'z_commands', - order: 1 - } - }); - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const keybindingService = accessor.get(IKeybindingService); - - this._show(this.getController(editor), { - getModel: (value: string): QuickOpenModel => { - return new QuickOpenModel(this._editorActionsToEntries(keybindingService, editor, value)); - }, - - getAutoFocus: (searchValue: string): IAutoFocus => { - return { - autoFocusFirstEntry: true, - autoFocusPrefixMatch: searchValue - }; - } - }); - } - - private _sort(elementA: QuickOpenEntryGroup, elementB: QuickOpenEntryGroup): number { - let elementAName = (elementA.getLabel() || '').toLowerCase(); - let elementBName = (elementB.getLabel() || '').toLowerCase(); - - return elementAName.localeCompare(elementBName); - } - - private _editorActionsToEntries(keybindingService: IKeybindingService, editor: ICodeEditor, searchValue: string): EditorActionCommandEntry[] { - let actions: IEditorAction[] = editor.getSupportedActions(); - let entries: EditorActionCommandEntry[] = []; - - for (const action of actions) { - - let keybinding = keybindingService.lookupKeybinding(action.id); - - if (action.label) { - let highlights = matchesFuzzy(searchValue, action.label); - if (highlights) { - entries.push(new EditorActionCommandEntry(keybinding ? keybinding.getLabel() || '' : '', keybinding ? keybinding.getAriaLabel() || '' : '', highlights, action, editor)); - } - } - } - - // Sort by name - entries = entries.sort(this._sort); - - return entries; - } -} - -registerEditorAction(QuickCommandAction); diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts b/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts deleted file mode 100644 index d9e06fed03..0000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts +++ /dev/null @@ -1,107 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Dimension } from 'vs/base/browser/dom'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenWidget } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; -import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; -import { foreground } from 'vs/platform/theme/common/colorRegistry'; -import { attachQuickOpenStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; - -export interface IQuickOpenEditorWidgetOptions { - inputAriaLabel: string; -} - -export class QuickOpenEditorWidget implements IOverlayWidget { - - private static readonly ID = 'editor.contrib.quickOpenEditorWidget'; - - private readonly codeEditor: ICodeEditor; - private readonly themeService: IThemeService; - private visible: boolean | undefined; - private quickOpenWidget: QuickOpenWidget; - private domNode: HTMLElement; - private styler: IDisposable; - - constructor(codeEditor: ICodeEditor, onOk: () => void, onCancel: () => void, onType: (value: string) => void, configuration: IQuickOpenEditorWidgetOptions, themeService: IThemeService) { - this.codeEditor = codeEditor; - this.themeService = themeService; - this.visible = false; - - this.domNode = document.createElement('div'); - - this.quickOpenWidget = new QuickOpenWidget( - this.domNode, - { - onOk: onOk, - onCancel: onCancel, - onType: onType - }, { - inputPlaceHolder: undefined, - inputAriaLabel: configuration.inputAriaLabel, - keyboardSupport: true - } - ); - this.styler = attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { - pickerGroupForeground: foreground - }); - - this.quickOpenWidget.create(); - this.codeEditor.addOverlayWidget(this); - } - - setInput(model: QuickOpenModel, focus: IAutoFocus): void { - this.quickOpenWidget.setInput(model, focus); - } - - getId(): string { - return QuickOpenEditorWidget.ID; - } - - getDomNode(): HTMLElement { - return this.domNode; - } - - destroy(): void { - this.codeEditor.removeOverlayWidget(this); - this.quickOpenWidget.dispose(); - this.styler.dispose(); - } - - isVisible(): boolean { - return !!this.visible; - } - - show(value: string): void { - this.visible = true; - - const editorLayout = this.codeEditor.getLayoutInfo(); - if (editorLayout) { - this.quickOpenWidget.layout(new Dimension(editorLayout.width, editorLayout.height)); - } - - this.quickOpenWidget.show(value); - this.codeEditor.layoutOverlayWidget(this); - } - - hide(): void { - this.visible = false; - this.quickOpenWidget.hide(); - this.codeEditor.layoutOverlayWidget(this); - } - - getPosition(): IOverlayWidgetPosition | null { - if (this.visible) { - return { - preference: OverlayWidgetPositionPreference.TOP_CENTER - }; - } - - return null; - } -} diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.css b/src/vs/editor/standalone/browser/quickOpen/quickOutline.css deleted file mode 100644 index faa0b540c8..0000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.css +++ /dev/null @@ -1,8 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-quick-open-widget { - font-size: 13px; -} diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts deleted file mode 100644 index 5fee1d41f0..0000000000 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ /dev/null @@ -1,325 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./quickOutline'; -import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded -import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded -import { CancellationToken } from 'vs/base/common/cancellation'; -import { matchesFuzzy } from 'vs/base/common/filters'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import * as strings from 'vs/base/common/strings'; -import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; -import { IRange, Range } from 'vs/editor/common/core/range'; -import { ScrollType } from 'vs/editor/common/editorCommon'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { DocumentSymbol, DocumentSymbolProviderRegistry, SymbolKinds } from 'vs/editor/common/modes'; -import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen'; -import { BaseEditorQuickOpenAction, IDecorator } from 'vs/editor/standalone/browser/quickOpen/editorQuickOpen'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings'; - -let SCOPE_PREFIX = ':'; - -export class SymbolEntry extends QuickOpenEntryGroup { - private readonly name: string; - private readonly type: string; - private readonly description: string | undefined; - private readonly range: Range; - private readonly editor: ICodeEditor; - private readonly decorator: IDecorator; - - constructor(name: string, type: string, description: string | undefined, range: Range, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator) { - super(); - - this.name = name; - this.type = type; - this.description = description; - this.range = range; - this.setHighlights(highlights); - this.editor = editor; - this.decorator = decorator; - } - - public getLabel(): string { - return this.name; - } - - public getAriaLabel(): string { - return strings.format(QuickOutlineNLS.entryAriaLabel, this.name); - } - - public getIcon(): string { - return this.type; - } - - public getDescription(): string | undefined { - return this.description; - } - - public getType(): string { - return this.type; - } - - public getRange(): Range { - return this.range; - } - - public run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(context); - } - - return this.runPreview(); - } - - private runOpen(_context: IEntryRunContext): boolean { - - // Apply selection and focus - let range = this.toSelection(); - this.editor.setSelection(range); - this.editor.revealRangeInCenter(range, ScrollType.Smooth); - this.editor.focus(); - - return true; - } - - private runPreview(): boolean { - - // Select Outline Position - let range = this.toSelection(); - this.editor.revealRangeInCenter(range, ScrollType.Smooth); - - // Decorate if possible - this.decorator.decorateLine(this.range, this.editor); - - return false; - } - - private toSelection(): Range { - return new Range( - this.range.startLineNumber, - this.range.startColumn || 1, - this.range.startLineNumber, - this.range.startColumn || 1 - ); - } -} - -export class QuickOutlineAction extends BaseEditorQuickOpenAction { - - constructor() { - super(QuickOutlineNLS.quickOutlineActionInput, { - id: 'editor.action.quickOutline', - label: QuickOutlineNLS.quickOutlineActionLabel, - alias: 'Go to Symbol...', - precondition: EditorContextKeys.hasDocumentSymbolProvider, - kbOpts: { - kbExpr: EditorContextKeys.focus, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O, - weight: KeybindingWeight.EditorContrib - }, - contextMenuOpts: { - group: 'navigation', - order: 3 - } - }); - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor) { - if (!editor.hasModel()) { - return undefined; - } - - const model = editor.getModel(); - - if (!DocumentSymbolProviderRegistry.has(model)) { - return undefined; - } - - // Resolve outline - return getDocumentSymbols(model, true, CancellationToken.None).then((result: DocumentSymbol[]) => { - if (result.length === 0) { - return; - } - - this._run(editor, result); - }); - } - - private _run(editor: ICodeEditor, result: DocumentSymbol[]): void { - this._show(this.getController(editor), { - getModel: (value: string): QuickOpenModel => { - return new QuickOpenModel(this.toQuickOpenEntries(editor, result, value)); - }, - - getAutoFocus: (searchValue: string): IAutoFocus => { - // Remove any type pattern (:) from search value as needed - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - searchValue = searchValue.substr(SCOPE_PREFIX.length); - } - - return { - autoFocusPrefixMatch: searchValue, - autoFocusFirstEntry: !!searchValue - }; - } - }); - } - - private symbolEntry(name: string, type: string, description: string | undefined, range: IRange, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator): SymbolEntry { - return new SymbolEntry(name, type, description, Range.lift(range), highlights, editor, decorator); - } - - private toQuickOpenEntries(editor: ICodeEditor, flattened: DocumentSymbol[], searchValue: string): SymbolEntry[] { - const controller = this.getController(editor); - - let results: SymbolEntry[] = []; - - // Convert to Entries - let normalizedSearchValue = searchValue; - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - normalizedSearchValue = normalizedSearchValue.substr(SCOPE_PREFIX.length); - } - - for (const element of flattened) { - let label = strings.trim(element.name); - - // Check for meatch - let highlights = matchesFuzzy(normalizedSearchValue, label); - if (highlights) { - - // Show parent scope as description - let description: string | undefined = undefined; - if (element.containerName) { - description = element.containerName; - } - - // Add - results.push(this.symbolEntry(label, SymbolKinds.toCssClassName(element.kind), description, element.range, highlights, editor, controller)); - } - } - - // Sort properly if actually searching - if (searchValue) { - if (searchValue.indexOf(SCOPE_PREFIX) === 0) { - results = results.sort(this.sortScoped.bind(this, searchValue.toLowerCase())); - } else { - results = results.sort(this.sortNormal.bind(this, searchValue.toLowerCase())); - } - } - - // Mark all type groups - if (results.length > 0 && searchValue.indexOf(SCOPE_PREFIX) === 0) { - let currentType: string | null = null; - let currentResult: SymbolEntry | null = null; - let typeCounter = 0; - - for (let i = 0; i < results.length; i++) { - let result = results[i]; - - // Found new type - if (currentType !== result.getType()) { - - // Update previous result with count - if (currentResult) { - currentResult.setGroupLabel(this.typeToLabel(currentType || '', typeCounter)); - } - - currentType = result.getType(); - currentResult = result; - typeCounter = 1; - - result.setShowBorder(i > 0); - } - - // Existing type, keep counting - else { - typeCounter++; - } - } - - // Update previous result with count - if (currentResult) { - currentResult.setGroupLabel(this.typeToLabel(currentType || '', typeCounter)); - } - } - - // Mark first entry as outline - else if (results.length > 0) { - results[0].setGroupLabel(strings.format(QuickOutlineNLS._symbols_, results.length)); - } - - return results; - } - - private typeToLabel(type: string, count: number): string { - switch (type) { - case 'module': return strings.format(QuickOutlineNLS._modules_, count); - case 'class': return strings.format(QuickOutlineNLS._class_, count); - case 'interface': return strings.format(QuickOutlineNLS._interface_, count); - case 'method': return strings.format(QuickOutlineNLS._method_, count); - case 'function': return strings.format(QuickOutlineNLS._function_, count); - case 'property': return strings.format(QuickOutlineNLS._property_, count); - case 'variable': return strings.format(QuickOutlineNLS._variable_, count); - case 'var': return strings.format(QuickOutlineNLS._variable2_, count); - case 'constructor': return strings.format(QuickOutlineNLS._constructor_, count); - case 'call': return strings.format(QuickOutlineNLS._call_, count); - } - - return type; - } - - private sortNormal(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number { - let elementAName = elementA.getLabel().toLowerCase(); - let elementBName = elementB.getLabel().toLowerCase(); - - // Compare by name - let r = elementAName.localeCompare(elementBName); - if (r !== 0) { - return r; - } - - // If name identical sort by range instead - let elementARange = elementA.getRange(); - let elementBRange = elementB.getRange(); - return elementARange.startLineNumber - elementBRange.startLineNumber; - } - - private sortScoped(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number { - - // Remove scope char - searchValue = searchValue.substr(SCOPE_PREFIX.length); - - // Sort by type first if scoped search - let elementAType = elementA.getType(); - let elementBType = elementB.getType(); - let r = elementAType.localeCompare(elementBType); - if (r !== 0) { - return r; - } - - // Special sort when searching in scoped mode - if (searchValue) { - let elementAName = elementA.getLabel().toLowerCase(); - let elementBName = elementB.getLabel().toLowerCase(); - - // Compare by name - let r = elementAName.localeCompare(elementBName); - if (r !== 0) { - return r; - } - } - - // Default to sort by range - let elementARange = elementA.getRange(); - let elementBRange = elementB.getRange(); - return elementARange.startLineNumber - elementBRange.startLineNumber; - } -} - -registerEditorAction(QuickOutlineAction); diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 0e215ed9c9..af4e7fd70f 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -138,6 +138,8 @@ class StandaloneTheme implements IStandaloneTheme { public get tokenColorMap(): string[] { return []; } + + public readonly semanticHighlighting = false; } function isBuiltinTheme(themeName: string): themeName is BuiltinTheme { diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index e7f6ab414a..17d9a277ed 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -60,6 +60,8 @@ suite('TokenizationSupport2Adapter', () => { return undefined; }, + semanticHighlighting: false, + tokenColorMap: [] }; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 84306fe343..9e00d61c79 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -74,6 +74,7 @@ declare namespace monaco { * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation * and encoding. * + * ```txt * foo://example.com:8042/over/there?name=ferret#nose * \_/ \______________/\_________/ \_________/ \__/ * | | | | | @@ -81,6 +82,7 @@ declare namespace monaco { * | _____________________|__ * / \ / \ * urn:example:animal:ferret:nose + * ``` */ export class Uri implements UriComponents { static isUri(thing: any): thing is Uri; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 31a0156dce..09faf5eb26 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -81,6 +81,8 @@ export interface ParsedArgs { user?: string; command?: string; // {{SQL CARBON EDIT}} End + 'sync'?: 'on' | 'off'; + // chromium command line args: https://electronjs.org/docs/all#supported-chrome-command-line-switches 'no-proxy-server'?: boolean; 'proxy-server'?: string; @@ -173,5 +175,5 @@ export interface IEnvironmentService extends IUserHomeProvider { driverHandle?: string; driverVerbose: boolean; - galleryMachineIdResource?: URI; + serviceMachineIdResource?: URI; } diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 968460b20d..9b94c102ea 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -52,6 +52,7 @@ export const OPTIONS: OptionDescriptions> = { 'telemetry': { type: 'boolean', cat: 'o', description: localize('telemetry', "Shows all telemetry events which VS code collects.") }, 'folder-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") }, 'file-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") }, + 'sync': { type: 'string', cat: 'o', description: localize('turn sync', "Turn sync on or off"), args: ['on', 'off'] }, 'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, 'builtin-extensions-dir': { type: 'string' }, diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 922cad38a4..1b1e801c39 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -255,7 +255,7 @@ export class EnvironmentService implements IEnvironmentService { get nodeCachedDataDir(): string | undefined { return process.env['VSCODE_NODE_CACHED_DATA_DIR'] || undefined; } @memoize - get galleryMachineIdResource(): URI { return resources.joinPath(URI.file(this.userDataPath), 'machineid'); } + get serviceMachineIdResource(): URI { return resources.joinPath(URI.file(this.userDataPath), 'machineid'); } get disableUpdates(): boolean { return !!this._args['disable-updates']; } get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index acf92c6165..6f70d9b3bc 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -13,7 +13,7 @@ import { IRequestService, asJson, asText } from 'vs/platform/request/common/requ import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { generateUuid, isUUID } from 'vs/base/common/uuid'; +import { generateUuid } from 'vs/base/common/uuid'; import { values } from 'vs/base/common/map'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; // {{SQL CARBON EDIT}} import { CancellationToken } from 'vs/base/common/cancellation'; @@ -22,11 +22,10 @@ import { IExtensionManifest, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/pl import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; -import { VSBuffer } from 'vs/base/common/buffer'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { optional } from 'vs/platform/instantiation/common/instantiation'; import { find } from 'vs/base/common/arrays'; +import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; interface IRawGalleryExtensionFile { assetType: string; @@ -388,7 +387,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { @IConfigurationService private configurationService: IConfigurationService, // {{SQL CARBON EDIT}} @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, - @optional(IStorageService) private readonly storageService: IStorageService, + @IStorageService private readonly storageService: IStorageService, ) { const config = productService.extensionsGallery; this.extensionsGalleryUrl = config && config.serviceUrl; @@ -926,43 +925,15 @@ export class ExtensionGalleryService implements IExtensionGalleryService { } } -export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService?: IStorageService): Promise<{ [key: string]: string; }> { +export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService: { + get: (key: string, scope: StorageScope) => string | undefined, + store: (key: string, value: string, scope: StorageScope) => void +}): Promise<{ [key: string]: string; }> { const headers: IHeaders = { 'X-Market-Client-Id': `VSCode ${version}`, 'User-Agent': `VSCode ${version}` }; - let uuid: string | null = null; - if (environmentService.galleryMachineIdResource) { - try { - const contents = await fileService.readFile(environmentService.galleryMachineIdResource); - const value = contents.value.toString(); - uuid = isUUID(value) ? value : null; - } catch (e) { - uuid = null; - } - - if (!uuid) { - uuid = generateUuid(); - try { - await fileService.writeFile(environmentService.galleryMachineIdResource, VSBuffer.fromString(uuid)); - } catch (error) { - //noop - } - } - } - - if (storageService) { - uuid = storageService.get('marketplace.userid', StorageScope.GLOBAL) || null; - if (!uuid) { - uuid = generateUuid(); - storageService.store('marketplace.userid', uuid, StorageScope.GLOBAL); - } - } - - if (uuid) { - headers['X-Market-User-Id'] = uuid; - } - + const uuid: string = await getServiceMachineId(environmentService, fileService, storageService); + headers['X-Market-User-Id'] = uuid; return headers; - } diff --git a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts index fde16be7db..c71f948564 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts @@ -19,6 +19,8 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import product from 'vs/platform/product/common/product'; +import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IStorageService } from 'vs/platform/storage/common/storage'; suite('Extension Gallery Service', () => { const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'extensiongalleryservice'); @@ -52,11 +54,12 @@ suite('Extension Gallery Service', () => { test('marketplace machine id', () => { const args = ['--user-data-dir', marketplaceHome]; const environmentService = new EnvironmentService(parseArgs(args, OPTIONS), process.execPath); + const storageService: IStorageService = new TestStorageService(); - return resolveMarketplaceHeaders(product.version, environmentService, fileService).then(headers => { + return resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService).then(headers => { assert.ok(isUUID(headers['X-Market-User-Id'])); - return resolveMarketplaceHeaders(product.version, environmentService, fileService).then(headers2 => { + return resolveMarketplaceHeaders(product.version, environmentService, fileService, storageService).then(headers2 => { assert.equal(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); }); }); diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index b9987fddd1..42f08981fb 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { $ } from 'vs/base/browser/dom'; +import { $, EventHelper, EventLike } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -46,9 +46,12 @@ export class Link extends Disposable { .map(e => new StandardKeyboardEvent(e)) .filter(e => e.keyCode === KeyCode.Enter) .event; - const onOpen = Event.any(onClick, onEnterPress); + const onOpen = Event.any(onClick, onEnterPress); - this._register(onOpen(_ => openerService.open(link.href))); + this._register(onOpen(e => { + EventHelper.stop(e, true); + openerService.open(link.href); + })); this.applyStyles(); } diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index ff3e15a756..271f16c80d 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -45,8 +45,7 @@ export const enum ProgressLocation { Extensions = 5, Window = 10, Notification = 15, - Dialog = 20, - View = 25 + Dialog = 20 } export interface IProgressOptions { diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index ca42846ac7..216345cdd2 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { distinct } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -40,9 +40,7 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc private static WORD_FILTER = or(matchesPrefix, matchesWords, matchesContiguousSubString); - private readonly disposables = new DisposableStore(); - - private readonly commandsHistory = this.disposables.add(this.instantiationService.createInstance(CommandsHistory)); + private readonly commandsHistory = this._register(this.instantiationService.createInstance(CommandsHistory)); constructor( private options: ICommandsQuickAccessOptions, @@ -173,11 +171,10 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc return commandPicks; } + /** + * Subclasses to provide the actual command entries. + */ protected abstract getCommandPicks(disposables: DisposableStore, token: CancellationToken): Promise>; - - dispose(): void { - this.disposables.dispose(); - } } interface ISerializedCommandHistory { diff --git a/src/vs/platform/quickinput/browser/helpQuickAccess.ts b/src/vs/platform/quickinput/browser/helpQuickAccess.ts index bdfcec7073..148e3b9336 100644 --- a/src/vs/platform/quickinput/browser/helpQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/helpQuickAccess.ts @@ -36,7 +36,7 @@ export class HelpQuickAccessProvider implements IQuickAccessProvider { // name of a provider (e.g. `?term` for terminals) disposables.add(picker.onDidChangeValue(value => { const providerDescriptor = this.registry.getQuickAccessProvider(value.substr(HelpQuickAccessProvider.PREFIX.length)); - if (providerDescriptor && providerDescriptor.prefix !== HelpQuickAccessProvider.PREFIX) { + if (providerDescriptor && providerDescriptor.prefix && providerDescriptor.prefix !== HelpQuickAccessProvider.PREFIX) { this.quickInputService.quickAccess.show(providerDescriptor.prefix); } })); diff --git a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts new file mode 100644 index 0000000000..4b3ffc3f32 --- /dev/null +++ b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput'; +import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; + +export enum TriggerAction { + + /** + * Do nothing after the button was clicked. + */ + NO_ACTION, + + /** + * Close the picker. + */ + CLOSE_PICKER, + + /** + * Update the results of the picker. + */ + REFRESH_PICKER +} + +export interface IPickerQuickAccessItem extends IQuickPickItem { + + /** + * A method that will be executed when the pick item is accepted from + * the picker. The picker will close automatically before running this. + * + * @param keyMods the state of modifier keys when the item was accepted. + * @param event the underlying event that caused the accept to trigger. + */ + accept?(keyMods: IKeyMods, event: IQuickPickAcceptEvent): void; + + /** + * A method that will be executed when a button of the pick item was + * clicked on. + * + * @param buttonIndex index of the button of the item that + * was clicked. + * + * @param the state of modifier keys when the button was triggered. + * + * @returns a value that indicates what should happen after the trigger + * which can be a `Promise` for long running operations. + */ + trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise; +} + +export abstract class PickerQuickAccessProvider extends Disposable implements IQuickAccessProvider { + + constructor(private prefix: string) { + super(); + } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Allow subclasses to configure picker + this.configure(picker); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Set initial picks and update on type + let picksCts: CancellationTokenSource | undefined = undefined; + const updatePickerItems = async () => { + + // Cancel any previous ask for picks and busy + picksCts?.dispose(true); + picker.busy = false; + + // Create new cancellation source for this run + picksCts = new CancellationTokenSource(token); + + // Collect picks and support both long running and short + const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), disposables.add(new DisposableStore()), picksCts.token); + if (Array.isArray(res)) { + picker.items = res; + } else { + picker.busy = true; + try { + const items = await res; + if (token.isCancellationRequested) { + return; + } + + picker.items = items; + } finally { + if (!token.isCancellationRequested) { + picker.busy = false; + } + } + } + }; + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Accept the pick on accept and hide picker + disposables.add(picker.onDidAccept(event => { + const [item] = picker.selectedItems; + if (typeof item?.accept === 'function') { + if (!event.inBackground) { + picker.hide(); // hide picker unless we accept in background + } + item.accept(picker.keyMods, event); + } + })); + + // Trigger the pick with button index if button triggered + disposables.add(picker.onDidTriggerItemButton(async ({ button, item }) => { + if (typeof item.trigger === 'function') { + const buttonIndex = item.buttons?.indexOf(button) ?? -1; + if (buttonIndex >= 0) { + const result = item.trigger(buttonIndex, picker.keyMods); + const action = (typeof result === 'number') ? result : await result; + + if (token.isCancellationRequested) { + return; + } + + switch (action) { + case TriggerAction.NO_ACTION: + break; + case TriggerAction.CLOSE_PICKER: + picker.hide(); + break; + case TriggerAction.REFRESH_PICKER: + updatePickerItems(); + break; + } + } + } + })); + + return disposables; + } + + /** + * Subclasses can override this method to configure the picker before showing it. + * + * @param picker the picker instance used for the quick access before it opens. + */ + protected configure(picker: IQuickPick): void { } + + /** + * Returns an array of picks and separators as needed. If the picks are resolved + * long running, the provided cancellation token should be used to cancel the + * operation when the token signals this. + * + * The implementor is responsible for filtering and sorting the picks given the + * provided `filter`. + * + * @param filter a filter to apply to the picks. + * @param disposables can be used to register disposables that should be cleaned + * up when the picker closes. + * @param token for long running tasks, implementors need to check on cancellation + * through this token. + */ + protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array | Promise>; +} diff --git a/src/vs/platform/quickinput/browser/quickAccess.ts b/src/vs/platform/quickinput/browser/quickAccess.ts index a1c83bfdd9..2b2fa72df6 100644 --- a/src/vs/platform/quickinput/browser/quickAccess.ts +++ b/src/vs/platform/quickinput/browser/quickAccess.ts @@ -37,11 +37,11 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon // Create a picker for the provider to use with the initial value // and adjust the filtering to exclude the prefix from filtering const picker = disposables.add(this.quickInputService.createQuickPick()); - picker.placeholder = descriptor.placeholder; + picker.placeholder = descriptor?.placeholder; picker.value = value; picker.valueSelection = [value.length, value.length]; - picker.contextKey = descriptor.contextKey; - picker.filterValue = (value: string) => value.substring(descriptor.prefix.length); + picker.contextKey = descriptor?.contextKey; + picker.filterValue = (value: string) => value.substring(descriptor ? descriptor.prefix.length : 0); // Remember as last active picker and clean up once picker get's disposed this.lastActivePicker = picker; @@ -72,8 +72,10 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon } })); - // Ask provider to fill the picker as needed - disposables.add(provider.provide(picker, cts.token)); + // Ask provider to fill the picker as needed if we have one + if (provider) { + disposables.add(provider.provide(picker, cts.token)); + } // Finally, show the picker. This is important because a provider // may not call this and then our disposables would leak that rely @@ -81,8 +83,11 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon picker.show(); } - private getOrInstantiateProvider(value: string): [IQuickAccessProvider, IQuickAccessProviderDescriptor] { - const providerDescriptor = this.registry.getQuickAccessProvider(value) || this.registry.defaultProvider; + private getOrInstantiateProvider(value: string): [IQuickAccessProvider | undefined, IQuickAccessProviderDescriptor | undefined] { + const providerDescriptor = this.registry.getQuickAccessProvider(value); + if (!providerDescriptor) { + return [undefined, undefined]; + } let provider = this.mapProviderToDescriptor.get(providerDescriptor); if (!provider) { diff --git a/src/vs/platform/quickinput/common/quickAccess.ts b/src/vs/platform/quickinput/common/quickAccess.ts index 4bc2508ef8..db1fac175f 100644 --- a/src/vs/platform/quickinput/common/quickAccess.ts +++ b/src/vs/platform/quickinput/common/quickAccess.ts @@ -4,13 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Registry } from 'vs/platform/registry/common/platform'; -import { first } from 'vs/base/common/arrays'; +import { first, coalesce } from 'vs/base/common/arrays'; import { startsWith } from 'vs/base/common/strings'; -import { assertIsDefined } from 'vs/base/common/types'; -import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; export interface IQuickAccessController { @@ -93,11 +91,6 @@ export const Extensions = { export interface IQuickAccessRegistry { - /** - * The default provider to use when no other provider matches. - */ - defaultProvider: IQuickAccessProviderDescriptor; - /** * Registers a quick access provider to the platform. */ @@ -114,172 +107,54 @@ export interface IQuickAccessRegistry { getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined; } -class QuickAccessRegistry implements IQuickAccessRegistry { +export class QuickAccessRegistry implements IQuickAccessRegistry { private providers: IQuickAccessProviderDescriptor[] = []; - - private _defaultProvider: IQuickAccessProviderDescriptor | undefined = undefined; - get defaultProvider(): IQuickAccessProviderDescriptor { return assertIsDefined(this._defaultProvider); } - set defaultProvider(provider: IQuickAccessProviderDescriptor) { this._defaultProvider = provider; } + private defaultProvider: IQuickAccessProviderDescriptor | undefined = undefined; registerQuickAccessProvider(provider: IQuickAccessProviderDescriptor): IDisposable { - this.providers.push(provider); + + // Extract the default provider when no prefix is present + if (provider.prefix.length === 0) { + this.defaultProvider = provider; + } else { + this.providers.push(provider); + } // sort the providers by decreasing prefix length, such that longer // prefixes take priority: 'ext' vs 'ext install' - the latter should win this.providers.sort((providerA, providerB) => providerB.prefix.length - providerA.prefix.length); - return toDisposable(() => this.providers.splice(this.providers.indexOf(provider), 1)); + return toDisposable(() => { + this.providers.splice(this.providers.indexOf(provider), 1); + + if (this.defaultProvider === provider) { + this.defaultProvider = undefined; + } + }); } getQuickAccessProviders(): IQuickAccessProviderDescriptor[] { - return [this.defaultProvider, ...this.providers]; + return coalesce([this.defaultProvider, ...this.providers]); } getQuickAccessProvider(prefix: string): IQuickAccessProviderDescriptor | undefined { - return prefix ? (first(this.providers, provider => startsWith(prefix, provider.prefix)) || undefined) : undefined; + const result = prefix ? (first(this.providers, provider => startsWith(prefix, provider.prefix)) || undefined) : undefined; + + return result || this.defaultProvider; + } + + clear(): Function { + const providers = [...this.providers]; + const defaultProvider = this.defaultProvider; + + this.providers = []; + this.defaultProvider = undefined; + + return () => { + this.providers = providers; + this.defaultProvider = defaultProvider; + }; } } Registry.add(Extensions.Quickaccess, new QuickAccessRegistry()); - -//#region Helper class for simple picker based providers - -export enum TriggerAction { - - /** - * Do nothing after the button was clicked. - */ - NO_ACTION, - - /** - * Close the picker. - */ - CLOSE_PICKER, - - /** - * Update the results of the picker. - */ - REFRESH_PICKER -} - -export interface IPickerQuickAccessItem extends IQuickPickItem { - - /** - * A method that will be executed when the pick item is accepted from - * the picker. The picker will close automatically before running this. - */ - accept?(): void; - - /** - * A method that will be executed when a button of the pick item was - * clicked on. - * - * @param buttonIndex index of the button of the item that - * was clicked. - * - * @returns a value that indicates what should happen after the trigger - * which can be a `Promise` for long running operations. - */ - trigger?(buttonIndex: number): TriggerAction | Promise; -} - -export abstract class PickerQuickAccessProvider implements IQuickAccessProvider { - - constructor(private prefix: string) { } - - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - const disposables = new DisposableStore(); - - // Disable filtering & sorting, we control the results - picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; - - // Set initial picks and update on type - let picksCts: CancellationTokenSource | undefined = undefined; - const updatePickerItems = async () => { - - // Cancel any previous ask for picks and busy - picksCts?.dispose(true); - picker.busy = false; - - // Create new cancellation source for this run - picksCts = new CancellationTokenSource(token); - - // Collect picks and support both long running and short - const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), disposables.add(new DisposableStore()), picksCts.token); - if (Array.isArray(res)) { - picker.items = res; - } else { - picker.busy = true; - try { - const items = await res; - if (token.isCancellationRequested) { - return; - } - - picker.items = items; - } finally { - if (!token.isCancellationRequested) { - picker.busy = false; - } - } - } - }; - disposables.add(picker.onDidChangeValue(() => updatePickerItems())); - updatePickerItems(); - - // Accept the pick on accept and hide picker - disposables.add(picker.onDidAccept(() => { - const [item] = picker.selectedItems; - if (typeof item?.accept === 'function') { - picker.hide(); - item.accept(); - } - })); - - // Trigger the pick with button index if button triggered - disposables.add(picker.onDidTriggerItemButton(async ({ button, item }) => { - if (typeof item.trigger === 'function') { - const buttonIndex = item.buttons?.indexOf(button) ?? -1; - if (buttonIndex >= 0) { - const result = item.trigger(buttonIndex); - const action = (typeof result === 'number') ? result : await result; - - if (token.isCancellationRequested) { - return; - } - - switch (action) { - case TriggerAction.NO_ACTION: - break; - case TriggerAction.CLOSE_PICKER: - picker.hide(); - break; - case TriggerAction.REFRESH_PICKER: - updatePickerItems(); - break; - } - } - } - })); - - return disposables; - } - - /** - * Returns an array of picks and separators as needed. If the picks are resolved - * long running, the provided cancellation token should be used to cancel the - * operation when the token signals this. - * - * The implementor is responsible for filtering and sorting the picks given the - * provided `filter`. - * - * @param filter a filter to apply to the picks. - * @param disposables can be used to register disposables that should be cleaned - * up when the picker closes. - * @param token for long running tasks, implementors need to check on cancellation - * through this token. - */ - protected abstract getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Array | Promise>; -} - -//#endregion diff --git a/src/vs/platform/serviceMachineId/common/serviceMachineId.ts b/src/vs/platform/serviceMachineId/common/serviceMachineId.ts new file mode 100644 index 0000000000..02d70feaf8 --- /dev/null +++ b/src/vs/platform/serviceMachineId/common/serviceMachineId.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IFileService } from 'vs/platform/files/common/files'; +import { StorageScope } from 'vs/platform/storage/common/storage'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { isUUID, generateUuid } from 'vs/base/common/uuid'; +import { VSBuffer } from 'vs/base/common/buffer'; + +export async function getServiceMachineId(environmentService: IEnvironmentService, fileService: IFileService, storageService: { + get: (key: string, scope: StorageScope, fallbackValue?: string | undefined) => string | undefined, + store: (key: string, value: string, scope: StorageScope) => void +}): Promise { + let uuid: string | null = storageService.get('storage.serviceMachineId', StorageScope.GLOBAL) || null; + if (uuid) { + return uuid; + } + if (environmentService.serviceMachineIdResource) { + try { + const contents = await fileService.readFile(environmentService.serviceMachineIdResource); + const value = contents.value.toString(); + uuid = isUUID(value) ? value : null; + } catch (e) { + uuid = null; + } + + if (!uuid) { + uuid = generateUuid(); + try { + await fileService.writeFile(environmentService.serviceMachineIdResource, VSBuffer.fromString(uuid)); + } catch (error) { + //noop + } + } + } else { + uuid = generateUuid(); + } + storageService.store('storage.serviceMachineId', uuid, StorageScope.GLOBAL); + return uuid; +} diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index 596f0e7a44..976f567831 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -112,6 +112,11 @@ export interface IColorTheme { * List of all colors used with tokens. getTokenStyleMetadata references the colors by index into this list. */ readonly tokenColorMap: string[]; + + /** + * Defines whether semantic highlighting should be enabled for the theme. + */ + readonly semanticHighlighting: boolean; } export interface IFileIconTheme { diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index 029dbafcd3..244d6020e9 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -28,6 +28,8 @@ export class TestColorTheme implements IColorTheme { return undefined; } + readonly semanticHighlighting = false; + get tokenColorMap(): string[] { return []; } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 47f9a90a0e..da0775fc42 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, CONFIGURATION_SYNC_STORE_KEY, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -33,7 +33,7 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent { && Object.keys(thing).length === 1; } -export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements ISettingsSyncService { +export class SettingsSynchroniser extends AbstractJsonFileSynchroniser { _serviceBrand: any; diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index b1e6ba1e30..0482dd95c8 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -333,14 +333,6 @@ export interface IConflictSetting { remoteValue: any | undefined; } -export const ISettingsSyncService = createDecorator('ISettingsSyncService'); -export interface ISettingsSyncService extends IUserDataSynchroniser { - _serviceBrand: any; - readonly onDidChangeConflicts: Event; - readonly conflicts: IConflictSetting[]; - resolveSettingsConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise; -} - //#endregion export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); diff --git a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts index ed8521180b..30e80620ed 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncEnablementService.ts @@ -8,6 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; type SyncEnablementClassification = { enabled?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -29,8 +30,17 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa constructor( @IStorageService private readonly storageService: IStorageService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IEnvironmentService environmentService: IEnvironmentService, ) { super(); + switch (environmentService.args['sync']) { + case 'on': + this.setEnablement(true); + break; + case 'off': + this.setEnablement(false); + break; + } this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 73af2c0286..846fbc8372 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -5,7 +5,7 @@ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAutoSyncService, IUserDataSyncStoreService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncService, IUserDataSyncStoreService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { URI } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; @@ -41,40 +41,6 @@ export class UserDataSyncChannel implements IServerChannel { } } -export class SettingsSyncChannel implements IServerChannel { - - constructor(private readonly service: ISettingsSyncService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onDidChangeStatus': return this.service.onDidChangeStatus; - case 'onDidChangeLocal': return this.service.onDidChangeLocal; - case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; - } - throw new Error(`Event not found: ${event}`); - } - - call(context: any, command: string, args?: any): Promise { - switch (command) { - case 'sync': return this.service.sync(); - case 'accept': return this.service.accept(args[0]); - case 'pull': return this.service.pull(); - case 'push': return this.service.push(); - case '_getInitialStatus': return Promise.resolve(this.service.status); - case '_getInitialConflicts': return Promise.resolve(this.service.conflicts); - case 'stop': this.service.stop(); return Promise.resolve(); - case 'resetLocal': return this.service.resetLocal(); - case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); - case 'hasLocalData': return this.service.hasLocalData(); - case 'resolveSettingsConflicts': return this.service.resolveSettingsConflicts(args[0]); - case 'getRemoteContentFromPreview': return this.service.getRemoteContentFromPreview(); - case 'getRemoteContent': return this.service.getRemoteContent(args[0], args[1]); - case 'getLocalBackupContent': return this.service.getLocalBackupContent(args[0], args[1]); - } - throw new Error('Invalid call'); - } -} - export class UserDataAutoSyncChannel implements IServerChannel { constructor(private readonly service: IUserDataAutoSyncService) { } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index de4ffa293a..40ccb94b47 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError, resolveSyncResource, PREVIEW_QUERY } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -16,6 +16,7 @@ import { equals } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; +import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; type SyncErrorClassification = { source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -51,6 +52,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); readonly onDidChangeLastSyncTime: Event = this._onDidChangeLastSyncTime.event; + private readonly settingsSynchroniser: SettingsSynchroniser; private readonly keybindingsSynchroniser: KeybindingsSynchroniser; private readonly extensionsSynchroniser: ExtensionsSynchroniser; private readonly globalStateSynchroniser: GlobalStateSynchroniser; @@ -58,12 +60,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ constructor( @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IStorageService private readonly storageService: IStorageService, ) { super(); + this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser)); this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); this.globalStateSynchroniser = this._register(this.instantiationService.createInstance(GlobalStateSynchroniser)); this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser)); diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index b5ba165c8f..d78504a968 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -13,12 +13,19 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { IProductService } from 'vs/platform/product/common/productService'; import { URI } from 'vs/base/common/uri'; +import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { assign } from 'vs/base/common/objects'; + export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { _serviceBrand: any; readonly userDataSyncStore: IUserDataSyncStore | undefined; + private readonly commonHeadersPromise: Promise<{ [key: string]: string; }>; constructor( @IProductService productService: IProductService, @@ -26,9 +33,17 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn @IRequestService private readonly requestService: IRequestService, @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService, + @IStorageService storageService: IStorageService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(productService, configurationService); + this.commonHeadersPromise = getServiceMachineId(environmentService, fileService, storageService) + .then(uuid => ({ + 'X-Sync-Client-Id': productService.version, + 'X-Sync-Machine-Id': uuid + })); } async getAllRefs(resource: SyncResource): Promise { @@ -46,7 +61,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn } const result = await asJson<{ url: string, created: number }[]>(context) || []; - return result.map(({ url, created }) => ({ ref: relativePath(uri, URI.parse(url))!, created: created })); + return result.map(({ url, created }) => ({ ref: relativePath(uri, URI.parse(url).with({ scheme: uri.scheme, authority: uri.authority }))!, created: created * 1000 /* Server returns in seconds */ })); } async resolveContent(resource: SyncResource, ref: string): Promise { @@ -174,8 +189,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn if (!authToken) { throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, source); } - options.headers = options.headers || {}; - options.headers['authorization'] = `Bearer ${authToken}`; + + const commonHeaders = await this.commonHeadersPromise; + options.headers = assign(options.headers || {}, commonHeaders, { + 'authorization': `Bearer ${authToken}`, + }); this.logService.trace('Sending request to server', { url: options.url, type: options.type, headers: { ...options.headers, ...{ authorization: undefined } } }); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 9f9256b477..18e947bb6b 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -6,7 +6,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IUserData, IUserDataManifest, ALL_SYNC_RESOURCES, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataManifest, ALL_SYNC_RESOURCES, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { generateUuid } from 'vs/base/common/uuid'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -31,7 +31,6 @@ import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagemen import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; import { Disposable } from 'vs/base/common/lifecycle'; -import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { Emitter } from 'vs/base/common/event'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import product from 'vs/platform/product/common/product'; @@ -57,6 +56,7 @@ export class UserDataSyncClient extends Disposable { keybindingsResource: joinPath(userDataDirectory, 'keybindings.json'), keybindingsSyncPreviewResource: joinPath(userDataSyncHome, 'keybindings.json'), argvResource: joinPath(userDataDirectory, 'argv.json'), + args: {} }); const logService = new NullLogService(); @@ -105,7 +105,6 @@ export class UserDataSyncClient extends Disposable { async getCompatibleExtension() { return null; } }); - this.instantiationService.stub(ISettingsSyncService, this.instantiationService.createInstance(SettingsSynchroniser)); this.instantiationService.stub(IUserDataSyncService, this.instantiationService.createInstance(UserDataSyncService)); if (!empty) { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index dda06cc172..29ce7aed92 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2116,6 +2116,9 @@ declare module 'vscode' { /** * A [command](#Command) this code action executes. + * + * If this command throws an exception, VS Code displays the exception message to users in the editor at the + * current cursor position. */ command?: Command; @@ -2145,8 +2148,8 @@ declare module 'vscode' { * of code action, such as refactorings. * * - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) - * that auto applies a code action and only a disabled code actions are returned, VS Code will show the user a - * message with `reason` in the editor. + * that auto applies a code action and only a disabled code actions are returned, VS Code will show the user an + * error message with `reason` in the editor. */ disabled?: { /** @@ -7929,7 +7932,7 @@ declare module 'vscode' { /** * The location at which progress should show. */ - location: ProgressLocation; + location: ProgressLocation | { viewId: string }; /** * A human-readable string which will be used to describe the diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d58665d6b0..db47f18838 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1086,6 +1086,22 @@ declare module 'vscode' { //#endregion + //#region Terminal link handlers https://github.com/microsoft/vscode/issues/91606 + + export namespace window { + export function registerTerminalLinkHandler(handler: TerminalLinkHandler): Disposable; + } + + export interface TerminalLinkHandler { + /** + * @return true when the link was handled (and should not be considered by + * other providers including the default), false when the link was not handled. + */ + handleLink(terminal: Terminal, link: string): ProviderResult; + } + + //#endregion + //#region Joh -> exclusive document filters export interface DocumentFilter { @@ -1457,6 +1473,21 @@ declare module 'vscode' { * @return Thenable indicating that the webview editor has been resolved. */ resolveCustomTextEditor(document: TextDocument, webviewPanel: WebviewPanel): Thenable; + + /** + * TODO: discuss this at api sync. + * + * Handle when the underlying resource for a custom editor is renamed. + * + * This allows the webview for the editor be preserved throughout the rename. If this method is not implemented, + * VS Code will destory the previous custom editor and create a replacement one. + * + * @param newDocument New text document to use for the custom editor. + * @param existingWebviewPanel Webview panel for the custom editor. + * + * @return Thenable indicating that the webview editor has been moved. + */ + moveCustomTextEditor?(newDocument: TextDocument, existingWebviewPanel: WebviewPanel): Thenable; } namespace window { @@ -1860,23 +1891,4 @@ declare module 'vscode' { //#endregion - //#region https://github.com/microsoft/vscode/issues/92421 - - export enum ProgressLocation { - /** - * Show progress for a view, as progress bar inside the view (when visible), - * and as an overlay on the activity bar icon. Doesn't support cancellation or discrete progress. - */ - View = 25, - } - - export interface ProgressOptions { - /** - * The target view identifier for showing progress when using [ProgressLocation.View](#ProgressLocation.View). - */ - viewId?: string - } - - //#endregion - } diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 74e929e4ce..bcc8c54054 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -3,13 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IShellLaunchConfig, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal'; 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, ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalService, ITerminalInstance, ITerminalBeforeHandleLinkEvent } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; @@ -23,6 +23,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape private readonly _terminalProcesses = new Map>(); private readonly _terminalProcessesReady = new Map void>(); private _dataEventTracker: TerminalDataEventTracker | undefined; + private _linkHandler: IDisposable | undefined; constructor( extHostContext: IExtHostContext, @@ -146,6 +147,22 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } } + public $startHandlingLinks(): void { + this._linkHandler?.dispose(); + this._linkHandler = this._terminalService.addLinkHandler(this._remoteAuthority || '', e => this._handleLink(e)); + } + + public $stopHandlingLinks(): void { + this._linkHandler?.dispose(); + } + + private async _handleLink(e: ITerminalBeforeHandleLinkEvent): Promise { + if (!e.terminal) { + return false; + } + return this._proxy.$handleLink(e.terminal.id, e.link); + } + private _onActiveTerminalChanged(terminalId: number | null): void { this._proxy.$acceptActiveTerminalChanged(terminalId); } diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 6fd7f6bf26..e48d078551 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -279,12 +279,12 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._revivers.delete(viewType); } - public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void { - return this.registerEditorProvider(ModelType.Text, extensionData, viewType, options); + public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: extHostProtocol.CustomTextEditorCapabilities): void { + this.registerEditorProvider(ModelType.Text, extensionData, viewType, options, capabilities); } public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void { - return this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options); + this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options, {}); } private registerEditorProvider( @@ -292,41 +292,45 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, - ): void { + capabilities: extHostProtocol.CustomTextEditorCapabilities, + ): DisposableStore { if (this._editorProviders.has(viewType)) { throw new Error(`Provider for ${viewType} already registered`); } const extension = reviveWebviewExtension(extensionData); - this._editorProviders.set(viewType, this._webviewWorkbenchService.registerResolver({ + const disposables = new DisposableStore(); + disposables.add(this._webviewWorkbenchService.registerResolver({ canResolve: (webviewInput) => { return webviewInput instanceof CustomEditorInput && webviewInput.viewType === viewType; }, resolveWebview: async (webviewInput: CustomEditorInput) => { const handle = webviewInput.id; this._webviewInputs.add(handle, webviewInput); - this.hookupWebviewEventDelegate(handle, webviewInput); + this.hookupWebviewEventDelegate(handle, webviewInput); webviewInput.webview.options = options; webviewInput.webview.extension = extension; const resource = webviewInput.resource; + let modelRef = await this.getOrCreateCustomEditorModel(modelType, resource, viewType); - const modelRef = await this.getOrCreateCustomEditorModel(modelType, webviewInput, resource, viewType); webviewInput.webview.onDispose(() => { modelRef.dispose(); }); + if (capabilities.supportsMove) { + webviewInput.onMove(async (newResource: URI) => { + const oldModel = modelRef; + modelRef = await this.getOrCreateCustomEditorModel(modelType, newResource, viewType); + this._proxy.$onMoveCustomEditor(handle, newResource, viewType); + oldModel.dispose(); + }); + } + try { - await this._proxy.$resolveWebviewEditor( - resource, - handle, - viewType, - webviewInput.getTitle(), - editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), - webviewInput.webview.options - ); + await this._proxy.$resolveWebviewEditor(resource, handle, viewType, webviewInput.getTitle(), editorGroupToViewColumn(this._editorGroupService, webviewInput.group || 0), webviewInput.webview.options); } catch (error) { onUnexpectedError(error); webviewInput.webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); @@ -334,6 +338,10 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma } } })); + + this._editorProviders.set(viewType, disposables); + + return disposables; } public $unregisterEditorProvider(viewType: string): void { @@ -350,11 +358,10 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private async getOrCreateCustomEditorModel( modelType: ModelType, - webviewInput: WebviewInput, resource: URI, viewType: string, ): Promise> { - const existingModel = this._customEditorService.models.tryRetain(webviewInput.resource, webviewInput.viewType); + const existingModel = this._customEditorService.models.tryRetain(resource, viewType); if (existingModel) { return existingModel; } @@ -383,14 +390,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma disposables.add(input.webview.onMessage((message: any) => { this._proxy.$onMessage(handle, message); })); disposables.add(input.webview.onMissingCsp((extension: ExtensionIdentifier) => this._proxy.$onMissingCsp(handle, extension.value))); - input.onDispose(() => { + disposables.add(input.webview.onDispose(() => { disposables.dispose(); - }); - input.webview.onDispose(() => { + this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviewInputs.delete(handle); }); - }); + })); } private registerWebviewFromDiffEditorListeners(diffEditorInput: DiffEditorInput): void { @@ -536,6 +542,8 @@ namespace HotExitState { export type State = typeof Allowed | typeof NotAllowed | Pending; } +const customDocumentFileScheme = 'custom'; + class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy { private _hotExitState: HotExitState.State = HotExitState.Allowed; @@ -556,7 +564,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod constructor( private readonly _proxy: extHostProtocol.ExtHostWebviewsShape, private readonly _viewType: string, - private readonly _resource: URI, + private readonly _realResource: URI, private readonly _editable: boolean, @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILabelService private readonly _labelService: ILabelService, @@ -564,6 +572,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod @IUndoRedoService private readonly _undoService: IUndoRedoService, ) { super(); + if (_editable) { this._register(workingCopyService.registerWorkingCopy(this)); } @@ -571,18 +580,26 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod dispose() { if (this._editable) { - this._undoService.removeElements(this.resource); + this._undoService.removeElements(this._realResource); } - this._proxy.$disposeWebviewCustomEditorDocument(this.resource, this._viewType); + this._proxy.$disposeWebviewCustomEditorDocument(this._realResource, this._viewType); super.dispose(); } //#region IWorkingCopy - public get resource() { return this._resource; } // custom://viewType/path/file + public get resource() { + // Make sure each custom editor has a unique resource for backup and edits + return URI.from({ + scheme: customDocumentFileScheme, + authority: this._viewType, + path: this._realResource.path, + query: JSON.stringify(this._realResource.toJSON()) + }); + } public get name() { - return basename(this._labelService.getUriLabel(this._resource)); + return basename(this._labelService.getUriLabel(this._realResource)); } public get capabilities(): WorkingCopyCapabilities { @@ -601,6 +618,10 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod //#endregion + public isReadonly() { + return this._editable; + } + public get viewType() { return this._viewType; } @@ -617,7 +638,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod this._undoService.pushElement({ type: UndoRedoElementType.Resource, - resource: this.resource, + resource: this._realResource, label: label ?? localize('defaultEditLabel', "Edit"), undo: () => this.undo(), redo: () => this.redo(), @@ -635,7 +656,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod } const undoneEdit = this._edits[this._currentEditIndex]; - await this._proxy.$undo(this.resource, this.viewType, undoneEdit); + await this._proxy.$undo(this._realResource, this.viewType, undoneEdit); this.change(() => { --this._currentEditIndex; @@ -653,7 +674,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod } const redoneEdit = this._edits[this._currentEditIndex + 1]; - await this._proxy.$redo(this.resource, this.viewType, redoneEdit); + await this._proxy.$redo(this._realResource, this.viewType, redoneEdit); this.change(() => { ++this._currentEditIndex; }); @@ -668,7 +689,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod : this._edits.splice(start, toRemove); if (removedEdits.length) { - this._proxy.$disposeEdits(this.resource, this._viewType, removedEdits); + this._proxy.$disposeEdits(this._realResource, this._viewType, removedEdits); } } @@ -700,7 +721,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); } - this._proxy.$revert(this.resource, this.viewType, { undoneEdits: editsToUndo, redoneEdits: editsToRedo }); + this._proxy.$revert(this._realResource, this.viewType, { undoneEdits: editsToUndo, redoneEdits: editsToRedo }); this.change(() => { this._currentEditIndex = this._savePoint; this.spliceEdits(); @@ -711,7 +732,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod if (!this._editable) { return false; } - await createCancelablePromise(token => this._proxy.$onSave(this.resource, this.viewType, token)); + await createCancelablePromise(token => this._proxy.$onSave(this._realResource, this.viewType, token)); this.change(() => { this._savePoint = this._currentEditIndex; }); @@ -720,7 +741,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { if (this._editable) { - await this._proxy.$onSaveAs(this.resource, this.viewType, targetResource); + await this._proxy.$onSaveAs(this._realResource, this.viewType, targetResource); this.change(() => { this._savePoint = this._currentEditIndex; }); @@ -749,7 +770,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod const pendingState = new HotExitState.Pending( createCancelablePromise(token => - this._proxy.$backup(this.resource.toJSON(), this.viewType, token))); + this._proxy.$backup(this._realResource.toJSON(), this.viewType, token))); this._hotExitState = pendingState; try { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3e3e5702ce..b8619eb793 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -543,7 +543,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } })); }, withProgress(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable) { - if (options.location === extHostTypes.ProgressLocation.View) { + if (typeof options.location === 'object') { checkProposedApiEnabled(extension); } return extHostProgress.withProgress(extension, options, task); @@ -567,6 +567,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); }, + registerTerminalLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostTerminalService.registerLinkHandler(handler); + }, registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index d71a96e909..8bc410e28c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -70,6 +70,7 @@ export interface IEnvironment { userHome: URI; webviewResourceRoot: string; webviewCspSource: string; + useHostProxy?: boolean; } export interface IStaticWorkspaceData { @@ -435,6 +436,8 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $show(terminalId: number, preserveFocus: boolean): void; $startSendingDataEvents(): void; $stopSendingDataEvents(): void; + $startHandlingLinks(): void; + $stopHandlingLinks(): void; // Process $sendProcessTitle(terminalId: number, title: string): void; @@ -577,6 +580,10 @@ export interface WebviewExtensionDescription { readonly location: UriComponents; } +export interface CustomTextEditorCapabilities { + readonly supportsMove?: boolean; +} + export interface MainThreadWebviewsShape extends IDisposable { $createWebviewPanel(extension: WebviewExtensionDescription, handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions): void; $disposeWebview(handle: WebviewPanelHandle): void; @@ -592,7 +599,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; - $registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; + $registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: CustomTextEditorCapabilities): void; $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; @@ -628,6 +635,8 @@ export interface ExtHostWebviewsShape { $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise; $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; + + $onMoveCustomEditor(handle: WebviewPanelHandle, newResource: UriComponents, viewType: string): Promise; } export interface MainThreadUrlsShape extends IDisposable { @@ -1315,6 +1324,7 @@ export interface ExtHostTerminalServiceShape { $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; $getAvailableShells(): Promise; $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; + $handleLink(id: number, link: string): Promise; } export interface ExtHostSCMShape { diff --git a/src/vs/workbench/api/common/extHostProgress.ts b/src/vs/workbench/api/common/extHostProgress.ts index a00d96295f..5f775812c4 100644 --- a/src/vs/workbench/api/common/extHostProgress.ts +++ b/src/vs/workbench/api/common/extHostProgress.ts @@ -24,10 +24,10 @@ export class ExtHostProgress implements ExtHostProgressShape { withProgress(extension: IExtensionDescription, options: ProgressOptions, task: (progress: Progress, token: CancellationToken) => Thenable): Thenable { const handle = this._handles++; - const { title, location, cancellable, viewId } = options; + const { title, location, cancellable } = options; const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name); - this._proxy.$startProgress(handle, { location: ProgressLocation.from(location, viewId), title, source, cancellable }, extension); + this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }, extension); return this._withProgress(handle, task, !!cancellable); } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 2a214549e9..2ebb8a73b0 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -14,6 +14,7 @@ import { timeout } from 'vs/base/common/async'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable as VSCodeDisposable } from './extHostTypes'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { @@ -34,6 +35,7 @@ export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { attachPtyToTerminal(id: number, pty: vscode.Pseudoterminal): void; getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string; getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string; + registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable } export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); @@ -295,6 +297,9 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {}; protected _getTerminalPromises: { [id: number]: Promise } = {}; + private readonly _bufferer: TerminalDataBufferer; + private readonly _linkHandlers: Set = new Set(); + public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; } public get terminals(): ExtHostTerminal[] { return this._terminals; } @@ -309,8 +314,6 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected readonly _onDidWriteTerminalData: Emitter; public get onDidWriteTerminalData(): Event { return this._onDidWriteTerminalData && this._onDidWriteTerminalData.event; } - private readonly _bufferer: TerminalDataBufferer; - constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService ) { @@ -535,6 +538,38 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ return id; } + public registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable { + this._linkHandlers.add(handler); + if (this._linkHandlers.size === 1) { + this._proxy.$startHandlingLinks(); + } + return new VSCodeDisposable(() => { + this._linkHandlers.delete(handler); + if (this._linkHandlers.size === 0) { + this._proxy.$stopHandlingLinks(); + } + }); + } + + public async $handleLink(id: number, link: string): Promise { + const terminal = this._getTerminalById(id); + if (!terminal) { + return false; + } + + // Call each handler synchronously so multiple handlers aren't triggered at once + const it = this._linkHandlers.values(); + let next = it.next(); + while (!next.done) { + const handled = await next.value.handleLink(terminal, link); + if (handled) { + return true; + } + next = it.next(); + } + return false; + } + private _onProcessExit(id: number, exitCode: number | undefined): void { this._bufferer.stopBuffering(id); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 9910742d57..1fcc46f4d9 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1093,12 +1093,15 @@ export namespace EndOfLine { } export namespace ProgressLocation { - export function from(loc: vscode.ProgressLocation, viewId?: string): MainProgressLocation | string { + export function from(loc: vscode.ProgressLocation | { viewId: string }): MainProgressLocation | string { + if (typeof loc === 'object') { + return loc.viewId; + } + switch (loc) { case types.ProgressLocation.SourceControl: return MainProgressLocation.Scm; case types.ProgressLocation.Window: return MainProgressLocation.Window; case types.ProgressLocation.Notification: return MainProgressLocation.Notification; - case types.ProgressLocation.View: return viewId ?? ''; } throw new Error(`Unknown 'ProgressLocation'`); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 6d045d552b..2d85f4f5d6 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2082,8 +2082,7 @@ export class Task implements vscode.Task2 { export enum ProgressLocation { SourceControl = 1, Window = 10, - Notification = 15, - View = 25 + Notification = 15 } @es5ClassCompat diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index c9a99c3762..59a6cde608 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -488,7 +488,9 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { const disposables = new DisposableStore(); if ('resolveCustomTextEditor' in provider) { disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider)); - this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options); + this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options, { + supportsMove: !!provider.moveCustomTextEditor, + }); } else { disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider)); this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options); @@ -663,6 +665,26 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { document._disposeEdits(editIds); } + async $onMoveCustomEditor(handle: string, newResourceComponents: UriComponents, viewType: string): Promise { + const entry = this._editorProviders.get(viewType); + if (!entry) { + throw new Error(`No provider found for '${viewType}'`); + } + + if (!(entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor) { + throw new Error(`Provider does not implement move '${viewType}'`); + } + + const webview = this.getWebviewPanel(handle); + if (!webview) { + throw new Error(`No webview found`); + } + + const resource = URI.revive(newResourceComponents); + const document = this._extHostDocuments.getDocument(resource); + await (entry.provider as vscode.CustomTextEditorProvider).moveCustomTextEditor!(document, webview); + } + async $undo(resourceComponents: UriComponents, viewType: string, editId: number): Promise { const document = this.getCustomDocument(viewType, resourceComponents); return document._undo(editId); diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 661c91a38b..f1467b7709 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -61,7 +61,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { // 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); + await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData); // Use IPC messages to forward console-calls, note that the console is // already patched to use`process.send()` diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 41affd6454..588c870578 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -24,11 +24,23 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { withNullAsUndefined } from 'vs/base/common/types'; export interface IResourceLabelProps { - resource?: URI; + resource?: URI | { master?: URI, detail?: URI }; name?: string | string[]; description?: string; } +function toResource(props: IResourceLabelProps | undefined): URI | undefined { + if (!props || !props.resource) { + return undefined; + } + + if (URI.isUri(props.resource)) { + return props.resource; + } + + return props.resource.master; +} + export interface IResourceLabelOptions extends IIconLabelValueOptions { fileKind?: FileKind; fileDecorations?: { colors: boolean, badges: boolean }; @@ -289,11 +301,16 @@ class ResourceLabelWidget extends IconLabel { } notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): void { - if (!this.options || !this.label || !this.label.resource) { + if (!this.options) { return; } - if (this.options.fileDecorations && e.affectsResource(this.label.resource)) { + const resource = toResource(this.label); + if (!resource) { + return; + } + + if (this.options.fileDecorations && e.affectsResource(resource)) { this.render(false); } } @@ -311,13 +328,13 @@ class ResourceLabelWidget extends IconLabel { } notifyFormattersChange(scheme: string): void { - if (this.label?.resource?.scheme === scheme) { + if (toResource(this.label)?.scheme === scheme) { this.render(false); } } notifyUntitledLabelChange(resource: URI): void { - if (isEqual(resource, this.label?.resource)) { + if (isEqual(resource, toResource(this.label))) { this.render(false); } } @@ -347,7 +364,10 @@ class ResourceLabelWidget extends IconLabel { } setResource(label: IResourceLabelProps, options: IResourceLabelOptions = Object.create(null)): void { - /*if (label.resource?.scheme === Schemas.untitled) { {{SQL CARBON EDIT}} we don't want to special case untitled files + /*const resource = toResource(this.label); {{SQL CARBON EDIT}} we don't want to special case untitled files + const isMasterDetail = this.label?.resource && !URI.isUri(this.label.resource); + + if (!isMasterDetail && resource?.scheme === Schemas.untitled) { // Untitled labels are very dynamic because they may change // whenever the content changes (unless a path is associated). // As such we always ask the actual editor for it's name and @@ -355,7 +375,11 @@ class ResourceLabelWidget extends IconLabel { // provided. If they are not provided from the label we got // we assume that the client does not want to display them // and as such do not override. - const untitledModel = this.textFileService.untitled.get(label.resource); + // + // We do not touch the label if it represents a master-detail + // because in that case we expect it to carry a proper label + // and description. + const untitledModel = this.textFileService.untitled.get(resource); if (untitledModel && !untitledModel.hasAssociatedFilePath) { if (typeof label.name === 'string') { label.name = untitledModel.name; @@ -415,7 +439,7 @@ class ResourceLabelWidget extends IconLabel { } private hasPathLabelChanged(newLabel: IResourceLabelProps, newOptions?: IResourceLabelOptions): boolean { - const newResource = newLabel ? newLabel.resource : undefined; + const newResource = toResource(newLabel); return !!newResource && this.computedPathLabel !== this.labelService.getUriLabel(newResource); } @@ -444,7 +468,8 @@ class ResourceLabelWidget extends IconLabel { } if (this.label) { - const detectedModeId = this.label.resource ? withNullAsUndefined(detectModeId(this.modelService, this.modeService, this.label.resource)) : undefined; + const resource = toResource(this.label); + const detectedModeId = resource ? withNullAsUndefined(detectModeId(this.modelService, this.modeService, resource)) : undefined; if (this.lastKnownDetectedModeId !== detectedModeId) { clearIconCache = true; this.lastKnownDetectedModeId = detectedModeId; @@ -463,14 +488,15 @@ class ResourceLabelWidget extends IconLabel { const iconLabelOptions: IIconLabelValueOptions & { extraClasses: string[] } = { title: '', - italic: this.options && this.options.italic, - matches: this.options && this.options.matches, + italic: this.options?.italic, + strikethrough: this.options?.strikethrough, + matches: this.options?.matches, extraClasses: [], separator: this.options?.separator, domId: this.options?.domId }; - const resource = this.label.resource; + const resource = toResource(this.label); const label = this.label.name; if (this.options && typeof this.options.title === 'string') { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 4cc61fcead..e972355616 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -169,7 +169,7 @@ export abstract class CompositePart extends Part { // Instantiate composite from registry otherwise const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { - const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), compositeDescriptor.id, !!isActive, undefined); + const compositeProgressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), compositeDescriptor.id, !!isActive); const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IEditorProgressService, compositeProgressIndicator] // provide the editor progress service for any editors instantiated within the composite )); diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index 8173fe602e..ec1cf204fe 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; +import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -28,6 +28,12 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro super(prefix); } + protected configure(picker: IQuickPick): void { + + // Allow to open editors in background without closing picker + picker.canAcceptInBackground = true; + } + protected getPicks(filter: string): Array { const query = prepareQuery(filter); const scorerCache = Object.create(null); @@ -108,7 +114,7 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro return TriggerAction.REFRESH_PICKER; }, - accept: () => this.editorGroupService.getGroup(groupId)?.openEditor(editor), + accept: (keyMods, event) => this.editorGroupService.getGroup(groupId)?.openEditor(editor, { preserveFocus: event.inBackground }), }; }); } diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 1df102895d..11d331a1f4 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -260,9 +260,6 @@ export class NoTabsTitleControl extends TitleControl { this.updateEditorDirty(editor); // Editor Label - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - const name = editor.getName(); - const { labelFormat } = this.accessor.partOptions; let description: string; if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { @@ -278,7 +275,19 @@ export class NoTabsTitleControl extends TitleControl { title = ''; // dont repeat what is already shown } - editorLabel.setResource({ name, description, resource }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); + editorLabel.setResource( + { + resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }), + name: editor.getName(), + description + }, + { + title, + italic: !isEditorPinned, + extraClasses: ['no-tabs', 'title-label'] + } + ); + if (isGroupActive) { editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || ''; } else { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index f2706b9270..89f3bf1c9c 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -975,11 +975,15 @@ export class TabsTitleControl extends TitleControl { tabContainer.title = title; // Label - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - tabLabelWidget.setResource({ name, description, resource }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); + tabLabelWidget.setResource( + { name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }) }, + { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) } + ); + this.setEditorTabColor(editor, tabContainer, this.group.isActive(editor)); // {{SQL CARBON EDIT}} -- Display the editor's tab color // Tests helper + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { tabContainer.setAttribute('data-resource-name', basenameOrAuthority(resource)); } else { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 7528b6eb3b..d66ff3f627 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -151,7 +151,10 @@ class NotificationMessageRenderer { const anchor = $('a', { href: node.href, title: title, }, node.label); if (actionHandler) { - actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, () => actionHandler.callback(node.href))); + actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, e => { + EventHelper.stop(e, true); + actionHandler.callback(node.href); + })); } messageContainer.appendChild(anchor); diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 1cf41c8eaa..a297eb6fc6 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -171,6 +171,10 @@ background-repeat: no-repeat; } +.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon { + line-height: 22px; +} + .customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon::before { vertical-align: middle; } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 0d0f184403..d5d44d12c3 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -343,7 +343,7 @@ export abstract class ViewPane extends Pane implements IView { } if (this.progressIndicator === undefined) { - this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isVisible(), { exclusiveProgressBar: true }); + this.progressIndicator = this.instantiationService.createInstance(CompositeProgressIndicator, assertIsDefined(this.progressBar), this.id, this.isVisible()); } return this.progressIndicator; } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index b32d3ef3c3..919318aea4 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1319,7 +1319,8 @@ export interface IEditorPartOptionsChangeEvent { export enum SideBySideEditor { MASTER = 1, - DETAILS = 2 + DETAILS = 2, + BOTH = 3 } export interface IResourceOptions { @@ -1327,12 +1328,22 @@ export interface IResourceOptions { filterByScheme?: string | string[]; } -export function toResource(editor: IEditorInput | undefined, options?: IResourceOptions): URI | undefined { +export function toResource(editor: IEditorInput | undefined): URI | undefined; +export function toResource(editor: IEditorInput | undefined, options: IResourceOptions & { supportSideBySide?: SideBySideEditor.MASTER | SideBySideEditor.DETAILS }): URI | undefined; +export function toResource(editor: IEditorInput | undefined, options: IResourceOptions & { supportSideBySide: SideBySideEditor.BOTH }): URI | { master?: URI, detail?: URI } | undefined; +export function toResource(editor: IEditorInput | undefined, options?: IResourceOptions): URI | { master?: URI, detail?: URI } | undefined { if (!editor) { return undefined; } if (options?.supportSideBySide && editor instanceof SideBySideEditorInput) { + if (options?.supportSideBySide === SideBySideEditor.BOTH) { + return { + master: toResource(editor.master, { filterByScheme: options.filterByScheme }), + detail: toResource(editor.details, { filterByScheme: options.filterByScheme }) + }; + } + editor = options.supportSideBySide === SideBySideEditor.MASTER ? editor.master : editor.details; } @@ -1341,12 +1352,14 @@ export function toResource(editor: IEditorInput | undefined, options?: IResource return resource; } - if (Array.isArray(options.filterByScheme) && options.filterByScheme.some(scheme => resource.scheme === scheme)) { - return resource; - } - - if (options.filterByScheme === resource.scheme) { - return resource; + if (Array.isArray(options.filterByScheme)) { + if (options.filterByScheme.some(scheme => resource.scheme === scheme)) { + return resource; + } + } else { + if (options.filterByScheme === resource.scheme) { + return resource; + } } return undefined; diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index 0c3c4fefe5..2f74ecc0f9 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -113,8 +113,8 @@ export class ResourceGlobMatcher extends Disposable { private readonly _onExpressionChange = this._register(new Emitter()); readonly onExpressionChange = this._onExpressionChange.event; - private readonly mapRootToParsedExpression: Map = new Map(); - private readonly mapRootToExpressionConfig: Map = new Map(); + private readonly mapRootToParsedExpression = new Map(); + private readonly mapRootToExpressionConfig = new Map(); constructor( private globFn: (root?: URI) => IExpression, diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index da53ddb91f..e8bb06ae83 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -10,7 +10,9 @@ import './inspectKeybindings'; import './largeFileOptimizations'; import './inspectEditorTokens/inspectEditorTokens'; import './quickaccess/gotoLineQuickAccess'; +import './quickaccess/gotoSymbolQuickAccess'; import './saveParticipants'; +import './semanticTokensHelp'; import './toggleColumnSelection'; import './toggleMinimap'; import './toggleMultiCursorModifier'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index a5b8cbe8fb..c3f4974649 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -26,7 +26,7 @@ import { findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMH import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/services/textMate/common/textMateService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition } from 'vs/workbench/services/themes/common/colorThemeData'; +import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition, TextMateThemingRuleDefinitions } from 'vs/workbench/services/themes/common/colorThemeData'; import { TokenStylingRule, TokenStyleData, TokenStyle } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -260,6 +260,9 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { } private _isSemanticColoringEnabled() { + if (!this._themeService.getColorTheme().semanticHighlighting) { + return false; + } const options = this._configurationService.getValue('editor.semanticHighlighting', { overrideIdentifier: this._model.getLanguageIdentifier().language, resource: this._model.uri }); return options && options.enabled; } @@ -303,7 +306,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const allDefValues = []; // remember the order // first collect to detect when the same rule is used fro multiple properties for (let property of properties) { - if (semanticTokenInfo.metadata[property]) { + if (semanticTokenInfo.metadata[property] !== undefined) { const definition = semanticTokenInfo.definitions[property]; const defValue = this._renderTokenStyleDefinition(definition, property); let properties = propertiesByDefValue[defValue]; @@ -532,11 +535,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const isTokenStylingRule = (d: any): d is TokenStylingRule => !!d.value; if (Array.isArray(definition)) { - for (const d of definition) { - const matchingRule = findMatchingThemeRule(theme, d, false); - if (matchingRule) { - return `${escape(d.join(' '))}
${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; - } + const scopesDefinition: TextMateThemingRuleDefinitions = {}; + theme.resolveScopes(definition, scopesDefinition); + const matchingRule = scopesDefinition[property]; + if (matchingRule && scopesDefinition.scope) { + return `${escape(scopesDefinition.scope.join(' '))}
${matchingRule.scope}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; } return ''; } else if (isTokenStylingRule(definition)) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index d7327f4093..c27d2fc6cc 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -11,29 +11,45 @@ import { IRange } from 'vs/editor/common/core/range'; import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess'; import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { - readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; + protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; - constructor(@IEditorService private readonly editorService: IEditorService) { + constructor( + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { super(); } - get activeTextEditorControl() { + private get configuration() { + const editorConfig = this.configurationService.getValue().workbench.editor; + + return { + openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + }; + } + + protected get activeTextEditorControl() { return this.editorService.activeTextEditorControl; } - protected gotoLine(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean): void { // Check for sideBySide use - if (keyMods.ctrlCmd && this.editorService.activeEditor) { - this.editorService.openEditor(this.editorService.activeEditor, { selection: range, pinned: keyMods.alt }, SIDE_GROUP); + if ((keyMods.ctrlCmd || forceSideBySide) && this.editorService.activeEditor) { + this.editorService.openEditor(this.editorService.activeEditor, { + selection: range, + pinned: keyMods.alt || this.configuration.openEditorPinned + }, SIDE_GROUP); } // Otherwise let parent handle it else { - super.gotoLine(editor, range, keyMods); + super.gotoLocation(editor, range, keyMods); } } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts new file mode 100644 index 0000000000..8eed2bd300 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IRange } from 'vs/editor/common/core/range'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; + +export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { + + protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; + + constructor( + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super({ + openSideBySideDirection: () => this.configuration.openSideBySideDirection + }); + } + + private get configuration() { + const editorConfig = this.configurationService.getValue().workbench.editor; + + return { + openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + openSideBySideDirection: editorConfig.openSideBySideDirection + }; + } + + protected get activeTextEditorControl() { + return this.editorService.activeTextEditorControl; + } + + protected gotoLocation(editor: IEditor, range: IRange, keyMods: IKeyMods, forceSideBySide?: boolean): void { + + // Check for sideBySide use + if ((keyMods.ctrlCmd || forceSideBySide) && this.editorService.activeEditor) { + this.editorService.openEditor(this.editorService.activeEditor, { + selection: range, + pinned: keyMods.alt || this.configuration.openEditorPinned + }, SIDE_GROUP); + } + + // Otherwise let parent handle it + else { + super.gotoLocation(editor, range, keyMods); + } + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: GotoSymbolQuickAccessProvider, + prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, + placeholder: localize('gotoSymbolQuickAccessPlaceholder', "Type the name of a symbol to go to."), + helpEntries: [ + { description: localize('gotoSymbolQuickAccess', "Go to Symbol in Editor"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true }, + { description: localize('gotoSymbolByCategoryQuickAccess', "Go to Symbol in Editor by Category"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } + ] +}); diff --git a/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts b/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts new file mode 100644 index 0000000000..f855acb98a --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as path from 'vs/base/common/path'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; +import { ITextModel } from 'vs/editor/common/model'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; + +/** + * Shows a message when semantic tokens are shown the first time. + */ +export class SemanticTokensHelp extends Disposable implements IEditorContribution { + + public static readonly ID = 'editor.contrib.semanticHighlightHelp'; + + constructor( + _editor: ICodeEditor, + @INotificationService _notificationService: INotificationService, + @IOpenerService _openerService: IOpenerService, + @IWorkbenchThemeService _themeService: IWorkbenchThemeService + ) { + super(); + + const toDispose = this._register(new DisposableStore()); + const localToDispose = toDispose.add(new DisposableStore()); + const installChangeTokenListener = (model: ITextModel) => { + localToDispose.add(model.onDidChangeTokens((e) => { + if (!e.semanticTokensApplied) { + return; + } + + toDispose.dispose(); // uninstall all listeners, makes sure the notification is only shown once per window + + const message = nls.localize( + { + key: 'semanticTokensHelp', + comment: [ + 'Variable 0 will be a file name.', + 'Variable 1 will be a theme name.' + ] + }, + "Semantic highlighting has been applied to '{0}' as the theme '{1}' has semantic highlighting enabled.", + path.basename(model.uri.path), _themeService.getColorTheme().label + ); + + _notificationService.prompt(Severity.Info, message, [ + { + label: nls.localize('learnMoreButton', "Learn More"), + run: () => { + const url = 'https://go.microsoft.com/fwlink/?linkid=2122588'; + + _openerService.open(URI.parse(url)); + } + } + ], { neverShowAgain: { id: 'editor.contrib.semanticTokensHelp' } }); + })); + }; + + + const model = _editor.getModel(); + if (model !== null) { + installChangeTokenListener(model); + } + + toDispose.add(_editor.onDidChangeModel((e) => { + localToDispose.clear(); + + const model = _editor.getModel(); + if (!model) { + return; + } + installChangeTokenListener(model); + })); + } +} + +registerEditorContribution(SemanticTokensHelp.ID, SemanticTokensHelp); diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index 2b1bf0658d..9819fcfb4f 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -186,7 +186,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { } } - const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup); + const newEditorInput = customEditorService.createInput(targetResource, toggleView, activeGroup.id); editorService.replaceEditors([{ editor: activeEditor, diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 3ca2f80042..a77149c925 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -10,7 +10,6 @@ import { basename } from 'vs/base/common/path'; import { isEqual } from 'vs/base/common/resources'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -98,7 +97,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } public isReadonly(): boolean { - return false; // TODO + return this._modelRef ? this._modelRef.object.isReadonly() : false; } public isDirty(): boolean { @@ -139,7 +138,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return undefined; } - return this.handleMove(groupId, target) || this.editorService.createEditorInput({ resource: target, forceFile: true }); + return this.tryMoveWebview(groupId, target) || this.editorService.createEditorInput({ resource: target, forceFile: true }); } public async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { @@ -161,21 +160,36 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return null; } - public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { + move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { + if (!this._moveHandler) { + return { + editor: this.customEditorService.createInput(newResource, this.viewType, group) + }; + } + this._moveHandler(newResource); + const newEditor = this.tryMoveWebview(group, newResource); + if (!newEditor) { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + return { editor: newEditor }; + } + + private tryMoveWebview(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(uri)) { - const webview = assertIsDefined(this.takeOwnershipOfWebview()); const newInput = this.instantiationService.createInstance(CustomEditorInput, uri, this.viewType, - generateUuid(), - new Lazy(() => webview)); + this.id, + new Lazy(() => undefined!)); // this webview is replaced in the transfer call + this.transfer(newInput); newInput.updateGroup(groupId); return newInput; } return undefined; } + public undo(): void { assertIsDefined(this._modelRef); this.undoRedoService.undo(this.resource); @@ -185,4 +199,21 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { assertIsDefined(this._modelRef); this.undoRedoService.redo(this.resource); } + + private _moveHandler?: (newResource: URI) => void; + + public onMove(handler: (newResource: URI) => void): void { + // TODO: Move this to the service + this._moveHandler = handler; + } + + protected transfer(other: CustomEditorInput): CustomEditorInput | undefined { + if (!super.transfer(other)) { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + + other._moveHandler = this._moveHandler; + this._moveHandler = undefined; + return other; + } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 00415100ea..7212cab38f 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -21,7 +21,7 @@ import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { EditorInput, EditorOptions, IEditorInput, IEditorPane } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IEditorInput, IEditorPane, GroupIdentifier } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; @@ -238,14 +238,14 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return this.promptOpenWith(resource, options, group); } - const input = this.createInput(resource, viewType, group); + const input = this.createInput(resource, viewType, group?.id); return this.openEditorForResource(resource, input, options, group); } public createInput( resource: URI, viewType: string, - group: IEditorGroup | undefined, + group: GroupIdentifier | undefined, options?: { readonly customClasses: string; }, ): IEditorInput { if (viewType === defaultEditorId) { @@ -257,8 +257,8 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return this.webviewService.createWebviewOverlay(id, { customClasses: options?.customClasses }, {}); }); const input = this.instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview); - if (group) { - input.updateGroup(group.id); + if (typeof group !== 'undefined') { + input.updateGroup(group); } return input; } @@ -317,12 +317,18 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ continue; } + if (!isEqual(editor.resource, oldResource)) { + continue; + } + const editorInfo = this._editorInfoStore.get(editor.viewType); if (!editorInfo?.matches(newResource)) { continue; } - const replacement = this.createInput(newResource, editor.viewType, group); + const moveResult = editor.move(group.id, newResource); + const replacement = moveResult ? moveResult.editor : this.createInput(newResource, editor.viewType, group.id); + this.editorService.replaceEditors([{ editor: editor, replacement: replacement, @@ -486,7 +492,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo return undefined; } - const input = this.customEditorService.createInput(resource, bestAvailableEditor.id, group, { customClasses }); + const input = this.customEditorService.createInput(resource, bestAvailableEditor.id, group.id, { customClasses }); if (input instanceof EditorInput) { return input; } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 9541de894c..7e10d84c80 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -6,14 +6,14 @@ import { distinct, mergeSort } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; +import { IDisposable, IReference } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorPane, IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IEditorInput, IEditorPane, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IDisposable, IReference } from 'vs/base/common/lifecycle'; export const ICustomEditorService = createDecorator('customEditorService'); @@ -29,7 +29,7 @@ export interface ICustomEditorService { getContributedCustomEditors(resource: URI): CustomEditorInfoCollection; getUserConfiguredCustomEditors(resource: URI): CustomEditorInfoCollection; - createInput(resource: URI, viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string }): IEditorInput; + createInput(resource: URI, viewType: string, group: GroupIdentifier | undefined, options?: { readonly customClasses: string }): IEditorInput; openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise; promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; @@ -49,6 +49,8 @@ export interface ICustomEditorModel extends IDisposable { readonly viewType: string; readonly resource: URI; + isReadonly(): boolean; + isDirty(): boolean; readonly onDidChangeDirty: Event; diff --git a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts index 99123e9bb1..c29e35c82e 100644 --- a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts @@ -31,12 +31,12 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo private constructor( public readonly viewType: string, private readonly _resource: URI, - model: IReference, + private readonly _model: IReference, @ITextFileService private readonly textFileService: ITextFileService, ) { super(); - this._register(model); + this._register(_model); this._register(this.textFileService.files.onDidChangeDirty(e => { if (isEqual(this.resource, e.resource)) { @@ -50,6 +50,10 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo return this._resource; } + public isReadonly(): boolean { + return this._model.object.isReadonly(); + } + public isDirty(): boolean { return this.textFileService.isDirty(this.resource); } diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index ac7e36c687..9c5decd579 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -643,6 +643,11 @@ class ShowMoreRenderer implements ITreeRenderer { getHeight(element: CallStackItem): number { + if (element instanceof StackFrame) { + if (!element.source || !element.source.available || isDeemphasized(element)) { + return 12; + } + } return 22; } diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 6208ae54fc..3a806ec3da 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -393,20 +393,18 @@ export class CopyValueAction extends Action { async run(): Promise { const stackFrame = this.debugService.getViewModel().focusedStackFrame; const session = this.debugService.getViewModel().focusedSession; - - if (typeof this.value === 'string') { - return this.clipboardService.writeText(this.value); + if (!stackFrame || !session) { + return; } - if (stackFrame && session && this.value.evaluateName) { - try { - const evaluation = await session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context); - this.clipboardService.writeText(evaluation.body.result); - } catch (e) { - this.clipboardService.writeText(this.value.value); - } - } else { - this.clipboardService.writeText(this.value.value); + const context = session.capabilities.supportsClipboardContext ? 'clipboard' : this.context; + const toEvaluate = typeof this.value === 'string' ? this.value : this.value.evaluateName || this.value.value; + + try { + const evaluation = await session.evaluate(toEvaluate, stackFrame.frameId, context); + this.clipboardService.writeText(evaluation.body.result); + } catch (e) { + this.clipboardService.writeText(typeof this.value === 'string' ? this.value : this.value.value); } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index ded9285894..078d5ff09b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; +import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { localize } from 'vs/nls'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; @@ -52,7 +52,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { config.launch.openConfigFile(false, false); diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 8f474f2e39..a54bcaf83f 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -83,6 +83,7 @@ .debug-pane .disabled { opacity: 0.65; + cursor: initial; } /* Call stack */ @@ -163,6 +164,7 @@ .monaco-workbench .debug-pane .debug-call-stack .monaco-action-bar .action-item > .action-label { width: 16px; height: 100%; + line-height: 22px; margin-right: 8px; vertical-align: text-top; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts index 2185426283..c9fe7aa36e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickAccess.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 7ab89d6401..e7fd65dde8 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -13,7 +13,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; -import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; +import { AutoSaveConfiguration, HotExitConfiguration, FILES_EXCLUDE_CONFIG, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { VIEWLET_ID, SortOrder, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; import { TextFileSaveErrorHandler } from 'vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler'; @@ -203,9 +203,9 @@ configurationRegistry.registerConfiguration({ 'title': nls.localize('filesConfigurationTitle', "Files"), 'type': 'object', 'properties': { - 'files.exclude': { + [FILES_EXCLUDE_CONFIG]: { 'type': 'object', - 'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), + 'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Refer to the `#search.exclude#` setting to define search specific excludes. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true }, 'scope': ConfigurationScope.RESOURCE, 'additionalProperties': { @@ -228,7 +228,7 @@ configurationRegistry.registerConfiguration({ ] } }, - 'files.associations': { + [FILES_ASSOCIATIONS_CONFIG]: { 'type': 'object', 'markdownDescription': nls.localize('associations', "Configure file associations to languages (e.g. `\"*.extension\": \"html\"`). These have precedence over the default associations of the languages installed."), }, @@ -307,7 +307,7 @@ configurationRegistry.registerConfiguration({ 'files.watcherExclude': { 'type': 'object', 'default': platform.isWindows /* https://github.com/Microsoft/vscode/issues/23954 */ ? { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/*/**': true, '**/.hg/store/**': true } : { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/**': true, '**/.hg/store/**': true }, - 'description': nls.localize('watcherExclude', "Configure glob patterns of file paths to exclude from file watching. Patterns must match on absolute paths (i.e. prefix with ** or the full path to match properly). Changing this setting requires a restart. When you experience Code consuming lots of cpu time on startup, you can exclude large folders to reduce the initial load."), + 'description': nls.localize('watcherExclude', "Configure glob patterns of file paths to exclude from file watching. Patterns must match on absolute paths (i.e. prefix with ** or the full path to match properly). Changing this setting requires a restart. When you experience Code consuming lots of CPU time on startup, you can exclude large folders to reduce the initial load."), 'scope': ConfigurationScope.RESOURCE }, 'files.hotExit': hotExitConfiguration, diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index ec1c62b450..cf387c2fc1 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -592,7 +592,7 @@ class OpenEditorRenderer implements IListRenderer(Extensions.Quickaccess); -registry.defaultProvider = { - ctor: HelpQuickAccessProvider, - prefix: '', - placeholder: localize('defaultAccessPlaceholder', "Type the name of a file to open."), - helpEntries: [{ description: localize('gotoFileQuickAccess', "Go to File"), needsEditor: false }] -}; - registry.registerQuickAccessProvider({ ctor: HelpQuickAccessProvider, prefix: HelpQuickAccessProvider.PREFIX, diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts index 3db33ede3d..edf403c3a0 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewDescriptorService, IViewsService, ViewContainer, IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry } from 'vs/workbench/common/views'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index 8e39e7ef8e..634bcff6c0 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -154,7 +154,7 @@ } .scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label.codicon { - height: 22px; + line-height: 22px; } .scm-viewlet .scm-editor { diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index eef1092482..ef71dbc93c 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -47,7 +47,7 @@ import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService, FileMatch, Match, FolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { VIEWLET_ID, VIEW_ID, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { VIEWLET_ID, VIEW_ID, SEARCH_EXCLUDE_CONFIG, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; @@ -55,6 +55,8 @@ import { assertType, assertIsDefined } from 'vs/base/common/types'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; +import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); registerSingleton(ISearchHistoryService, SearchHistoryService, true); @@ -651,6 +653,16 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen ) ); +// Register Quick Access Handler + +Registry.as(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({ + ctor: SymbolsQuickAccessProvider, + prefix: SymbolsQuickAccessProvider.PREFIX, + placeholder: nls.localize('symbolsQuickAccessPlaceholder', "Type the name of a symbol to open."), + contextKey: 'inWorkspaceSymbolsPicker', + helpEntries: [{ description: nls.localize('symbolsQuickAccess', "Go to Symbol in Workspace"), needsEditor: false }] +}); + // Configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ @@ -659,9 +671,9 @@ configurationRegistry.registerConfiguration({ title: nls.localize('searchConfigurationTitle', "Search"), type: 'object', properties: { - 'search.exclude': { + [SEARCH_EXCLUDE_CONFIG]: { type: 'object', - markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), + markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in fulltext searches and quick open. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), default: { '**/node_modules': true, '**/bower_components': true, '**/*.code-search': true }, additionalProperties: { anyOf: [ diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 5604c46149..53c14b90bd 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -27,7 +27,7 @@ import { ISearchConfigurationProperties } from 'vs/workbench/services/search/com import { attachFindReplaceInputBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; -import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/contrib/search/browser/searchActions'; +import { appendKeyBindingLabel, isSearchViewFocused, getSearchView } from 'vs/workbench/contrib/search/browser/searchActions'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { isMacintosh } from 'vs/base/common/platform'; @@ -52,19 +52,9 @@ export interface ISearchWidgetOptions { class ReplaceAllAction extends Action { - private static fgInstance: ReplaceAllAction | null = null; static readonly ID: string = 'search.action.replaceAll'; - static get INSTANCE(): ReplaceAllAction { - if (ReplaceAllAction.fgInstance === null) { - ReplaceAllAction.fgInstance = new ReplaceAllAction(); - } - return ReplaceAllAction.fgInstance; - } - - private _searchWidget: SearchWidget | null = null; - - constructor() { + constructor(private _searchWidget: SearchWidget) { super(ReplaceAllAction.ID, '', 'codicon-replace-all', false); } @@ -417,8 +407,7 @@ export class SearchWidget extends Widget { this._register(this.replaceInput.inputBox.onDidChange(() => this._onReplaceValueChanged.fire())); this._register(this.replaceInput.inputBox.onDidHeightChange(() => this._onDidHeightChange.fire())); - this.replaceAllAction = ReplaceAllAction.INSTANCE; - this.replaceAllAction.searchWidget = this; + this.replaceAllAction = new ReplaceAllAction(this); this.replaceAllAction.label = SearchWidget.REPLACE_ALL_DISABLED_LABEL; this.replaceActionBar = this._register(new ActionBar(this.replaceContainer)); this.replaceActionBar.push([this.replaceAllAction], { icon: true, label: false }); @@ -667,8 +656,12 @@ export function registerContributions() { when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, CONTEXT_FIND_WIDGET_NOT_VISIBLE), primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.Enter, handler: accessor => { - if (isSearchViewFocused(accessor.get(IViewsService))) { - ReplaceAllAction.INSTANCE.run(); + const viewsService = accessor.get(IViewsService); + if (isSearchViewFocused(viewsService)) { + const searchView = getSearchView(viewsService); + if (searchView) { + new ReplaceAllAction(searchView.searchAndReplaceWidget).run(); + } } } }); diff --git a/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts new file mode 100644 index 0000000000..04588fee20 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/symbolsQuickAccess.ts @@ -0,0 +1,229 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; +import { fuzzyScore, createMatches, FuzzyScore } from 'vs/base/common/filters'; +import { stripWildcards } from 'vs/base/common/strings'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ThrottledDelayer } from 'vs/base/common/async'; +import { getWorkspaceSymbols, IWorkspaceSymbol, IWorkspaceSymbolProvider } from 'vs/workbench/contrib/search/common/search'; +import { SymbolKinds, SymbolTag } from 'vs/editor/common/modes'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { Schemas } from 'vs/base/common/network'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { Range } from 'vs/editor/common/core/range'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; +import { IKeyMods, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { createResourceExcludeMatcher } from 'vs/workbench/services/search/common/search'; +import { ResourceMap } from 'vs/base/common/map'; + +interface ISymbolsQuickPickItem extends IPickerQuickAccessItem { + score: FuzzyScore; + symbol: IWorkspaceSymbol; +} + +export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider { + + static PREFIX = '#'; + + private static readonly TYPING_SEARCH_DELAY = 200; // this delay accommodates for the user typing a word and then stops typing to start searching + + private delayer = new ThrottledDelayer(SymbolsQuickAccessProvider.TYPING_SEARCH_DELAY); + + private readonly resourceExcludeMatcher = this._register(createResourceExcludeMatcher(this.instantiationService, this.configurationService)); + + constructor( + @ILabelService private readonly labelService: ILabelService, + @IOpenerService private readonly openerService: IOpenerService, + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(SymbolsQuickAccessProvider.PREFIX); + } + + protected configure(picker: IQuickPick): void { + + // Allow to open symbols in background without closing picker + picker.canAcceptInBackground = true; + } + + private get configuration() { + const editorConfig = this.configurationService.getValue().workbench.editor; + + return { + openEditorPinned: !editorConfig.enablePreviewFromQuickOpen, + openSideBySideDirection: editorConfig.openSideBySideDirection + }; + } + + protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { + return this.delayer.trigger(async () => { + if (token.isCancellationRequested) { + return []; + } + + return this.doGetSymbolPicks(filter, token); + }); + } + + private async doGetSymbolPicks(filter: string, token: CancellationToken): Promise> { + const workspaceSymbols = await getWorkspaceSymbols(filter, token); + if (token.isCancellationRequested) { + return []; + } + + const symbolPicks: Array = []; + + // Normalize filter + const [symbolFilter, containerFilter] = stripWildcards(filter).split(' ') as [string, string | undefined]; + const symbolFilterLow = symbolFilter.toLowerCase(); + const containerFilterLow = containerFilter?.toLowerCase(); + + // Convert to symbol picks and apply filtering + const openSideBySideDirection = this.configuration.openSideBySideDirection; + const symbolsExcludedByResource = new ResourceMap(); + for (const [provider, symbols] of workspaceSymbols) { + for (const symbol of symbols) { + + // Score by symbol label + const symbolLabel = symbol.name; + const symbolScore = fuzzyScore(symbolFilter, symbolFilterLow, 0, symbolLabel, symbolLabel.toLowerCase(), 0, true); + if (!symbolScore) { + continue; + } + + const symbolUri = symbol.location.uri; + let containerLabel: string | undefined = undefined; + if (symbolUri) { + const containerPath = this.labelService.getUriLabel(symbolUri, { relative: true }); + if (symbol.containerName) { + containerLabel = `${symbol.containerName} • ${containerPath}`; + } else { + containerLabel = containerPath; + } + } + + // Score by container if specified + let containerScore: FuzzyScore | undefined = undefined; + if (containerFilter && containerFilterLow) { + if (containerLabel) { + containerScore = fuzzyScore(containerFilter, containerFilterLow, 0, containerLabel, containerLabel.toLowerCase(), 0, true); + } + + if (!containerScore) { + continue; + } + } + + // Filter out symbols that match the global resource filter + if (symbolUri) { + let excludeSymbolByResource = symbolsExcludedByResource.get(symbolUri); + if (typeof excludeSymbolByResource === 'undefined') { + excludeSymbolByResource = this.resourceExcludeMatcher.matches(symbolUri); + symbolsExcludedByResource.set(symbolUri, excludeSymbolByResource); + } + + if (excludeSymbolByResource) { + continue; + } + } + + const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`; + const deprecated = symbol.tags ? symbol.tags.indexOf(SymbolTag.Deprecated) >= 0 : false; + + symbolPicks.push({ + symbol, + score: symbolScore, + label: symbolLabelWithIcon, + ariaLabel: localize('symbolAriaLabel', "{0}, symbols picker", symbolLabel), + highlights: deprecated ? undefined : { + label: createMatches(symbolScore, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */), + description: createMatches(containerScore) + }, + description: containerLabel, + strikethrough: deprecated, + buttons: [ + { + iconClass: openSideBySideDirection === 'right' ? 'codicon-split-horizontal' : 'codicon-split-vertical', + tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom") + } + ], + accept: async (keyMods, event) => this.openSymbol(provider, symbol, token, keyMods, { preserveFocus: event.inBackground }), + trigger: (buttonIndex, keyMods) => { + this.openSymbol(provider, symbol, token, keyMods, { forceOpenSideBySide: true }); + + return TriggerAction.CLOSE_PICKER; + } + }); + } + } + + // Sort picks + symbolPicks.sort((symbolA, symbolB) => this.compareSymbols(symbolA, symbolB)); + + return symbolPicks; + } + + private async openSymbol(provider: IWorkspaceSymbolProvider, symbol: IWorkspaceSymbol, token: CancellationToken, keyMods: IKeyMods, options: { forceOpenSideBySide?: boolean, preserveFocus?: boolean }): Promise { + + // Resolve actual symbol to open for providers that can resolve + let symbolToOpen = symbol; + if (typeof provider.resolveWorkspaceSymbol === 'function' && !symbol.location.range) { + symbolToOpen = await provider.resolveWorkspaceSymbol(symbol, token) || symbol; + + if (token.isCancellationRequested) { + return; + } + } + + // Open HTTP(s) links with opener service + if (symbolToOpen.location.uri.scheme === Schemas.http || symbolToOpen.location.uri.scheme === Schemas.https) { + await this.openerService.open(symbolToOpen.location.uri, { fromUserGesture: true }); + } + + // Otherwise open as editor + else { + await this.editorService.openEditor({ + resource: symbolToOpen.location.uri, + options: { + preserveFocus: options?.preserveFocus, + pinned: keyMods.alt || this.configuration.openEditorPinned, + selection: symbolToOpen.location.range ? Range.collapseToStart(symbolToOpen.location.range) : undefined + } + }, keyMods.ctrlCmd || options?.forceOpenSideBySide ? SIDE_GROUP : ACTIVE_GROUP); + } + } + + private compareSymbols(symbolA: ISymbolsQuickPickItem, symbolB: ISymbolsQuickPickItem): number { + + // By score + if (symbolA.score && symbolB.score) { + if (symbolA.score[0] > symbolB.score[0]) { + return -1; + } else if (symbolA.score[0] < symbolB.score[0]) { + return 1; + } + } + + // By name + const symbolAName = symbolA.symbol.name.toLowerCase(); + const symbolBName = symbolB.symbol.name.toLowerCase(); + const res = symbolAName.localeCompare(symbolBName); + if (res !== 0) { + return res; + } + + // By kind + const symbolAKind = SymbolKinds.toCssClassName(symbolA.symbol.kind); + const symbolBKind = SymbolKinds.toCssClassName(symbolB.symbol.kind); + return symbolAKind.localeCompare(symbolBKind); + } +} diff --git a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index 6e55f8eb1a..bd0dc3d4a4 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -18,6 +18,8 @@ import { IFileMatch } from 'vs/workbench/services/search/common/search'; import { ReplaceAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { FileMatch, FileMatchOrMatch, Match } from 'vs/workbench/contrib/search/common/searchModel'; import { MockObjectTree } from 'vs/workbench/contrib/search/test/browser/mockSearchTree'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('Search Actions', () => { @@ -153,6 +155,7 @@ suite('Search Actions', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index 7612ac77f1..e5cd7428a4 100644 --- a/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -15,6 +15,8 @@ import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { isWindows } from 'vs/base/common/platform'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('Search - Viewlet', () => { let instantiation: TestInstantiationService; @@ -105,6 +107,7 @@ suite('Search - Viewlet', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); 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 ca8c505a7a..b79a578317 100644 --- a/src/vs/workbench/contrib/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -19,6 +19,8 @@ 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'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const nullEvent = new class { id: number = -1; @@ -328,6 +330,7 @@ suite('SearchModel', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } diff --git a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts index e0b4841f7e..cc758bc565 100644 --- a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts @@ -16,6 +16,8 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const lineOneRange = new OneLineRange(1, 0, 1); @@ -352,6 +354,7 @@ suite('SearchResult', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts index 0e521be882..b7f596360c 100644 --- a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { matchesFuzzy } from 'vs/base/common/filters'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts index 9aa372dd08..f1599f4516 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminaQuickAccess.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/common/quickAccess'; +import { IQuickPickSeparator, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; +import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { matchesFuzzy } from 'vs/base/common/filters'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -22,6 +22,12 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider): void { + + // Allow to open terminals in background without closing picker + picker.canAcceptInBackground = true; + } + protected getPicks(filter: string): Array { const terminalPicks: Array = []; @@ -60,9 +66,9 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider { + accept: (keyMod, event) => { this.terminalService.setActiveInstance(terminal); - this.terminalService.showPanel(true); + this.terminalService.showPanel(!event.inBackground); } }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 82b7b06122..cbd7188e18 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -294,8 +294,8 @@ configurationRegistry.registerConfiguration({ type: 'boolean', default: true }, - 'terminal.integrated.allowMenubarMnemonics': { - markdownDescription: nls.localize('terminal.integrated.allowMenubarMnemonics', "Whether to allow menubar mnemonics (eg. alt+f) to trigger the open the menubar. Note that this will cause all alt keystrokes will skip the shell when true."), + 'terminal.integrated.allowMnemonics': { + markdownDescription: nls.localize('terminal.integrated.allowMnemonics', "Whether to allow menubar mnemonics (eg. alt+f) to trigger the open the menubar. Note that this will cause all alt keystrokes will skip the shell when true."), type: 'boolean', default: false }, diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 76d6c9d518..9e5076cc57 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -131,6 +131,14 @@ export interface ITerminalService { findNext(): void; findPrevious(): void; + /** + * Link handlers can be registered here to allow intercepting links clicked in the terminal. + * When a link is clicked, the link will be considered handled when the first interceptor + * resolves with true. It will be considered not handled when _all_ link handlers resolve with + * false, or 3 seconds have elapsed. + */ + addLinkHandler(key: string, callback: TerminalLinkHandlerCallback): IDisposable; + selectDefaultWindowsShell(): Promise; setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; @@ -179,6 +187,18 @@ export enum WindowsShellType { } export type TerminalShellType = WindowsShellType | undefined; +export const LINK_INTERCEPT_THRESHOLD = 3000; + +export interface ITerminalBeforeHandleLinkEvent { + terminal?: ITerminalInstance; + /** The text of the link */ + link: string; + /** Call with whether the link was handled by the interceptor */ + resolve(wasHandled: boolean): void; +} + +export type TerminalLinkHandlerCallback = (e: ITerminalBeforeHandleLinkEvent) => Promise; + export interface ITerminalInstance { /** * The ID of the terminal instance, this is an arbitrary number only used to identify the @@ -240,6 +260,11 @@ export interface ITerminalInstance { */ onExit: Event; + /** + * Attach a listener to intercept and handle link clicks in the terminal. + */ + onBeforeHandleLink: Event; + readonly exitCode: number | undefined; processReady: Promise; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 852a691e52..dca16b626b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -30,7 +30,7 @@ import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGR import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { ITerminalInstanceService, ITerminalInstance, TerminalShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalInstanceService, ITerminalInstance, TerminalShellType, ITerminalBeforeHandleLinkEvent } 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'; @@ -272,6 +272,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { public get onMaximumDimensionsChanged(): Event { return this._onMaximumDimensionsChanged.event; } private readonly _onFocus = new Emitter(); public get onFocus(): Event { return this._onFocus.event; } + private readonly _onBeforeHandleLink = new Emitter(); + public get onBeforeHandleLink(): Event { return this._onBeforeHandleLink.event; } public constructor( private readonly _terminalFocusContextKey: IContextKey, @@ -523,6 +525,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { }); } this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, xterm, this._processManager, this._configHelper); + this._linkHandler.onBeforeHandleLink(e => { + e.terminal = this; + this._onBeforeHandleLink.fire(e); + }); }); this._commandTrackerAddon = new CommandTrackerAddon(); @@ -615,7 +621,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } // Skip processing by xterm.js of keyboard events that match menu bar mnemonics - if (this._configHelper.config.allowMenubarMnemonics && event.altKey) { + if (this._configHelper.config.allowMnemonics && event.altKey) { return false; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index bf72072939..fa5183856b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -16,9 +16,11 @@ import { IFileService } from 'vs/platform/files/common/files'; import { Terminal, ILinkMatcherOptions, IViewportRange } 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'; +import { ITerminalInstanceService, ITerminalBeforeHandleLinkEvent, LINK_INTERCEPT_THRESHOLD } from 'vs/workbench/contrib/terminal/browser/terminal'; import { OperatingSystem, isMacintosh } from 'vs/base/common/platform'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ILogService } from 'vs/platform/log/common/log'; const pathPrefix = '(\\.\\.?|\\~)'; const pathSeparatorClause = '\\/'; @@ -58,7 +60,7 @@ const CUSTOM_LINK_PRIORITY = -1; /** Lowest */ const LOCAL_LINK_PRIORITY = -2; -export type XtermLinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; +export type XtermLinkMatcherHandler = (event: MouseEvent, link: string) => Promise; export type XtermLinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; interface IPath { @@ -66,14 +68,28 @@ interface IPath { normalize(path: string): string; } -export class TerminalLinkHandler { - private readonly _hoverDisposables = new DisposableStore(); +export class TerminalLinkHandler extends DisposableStore { private _widgetManager: TerminalWidgetManager | undefined; private _processCwd: string | undefined; private _gitDiffPreImagePattern: RegExp; private _gitDiffPostImagePattern: RegExp; private readonly _tooltipCallback: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void; private readonly _leaveCallback: () => void; + private _hasBeforeHandleLinkListeners = false; + + protected static _LINK_INTERCEPT_THRESHOLD = LINK_INTERCEPT_THRESHOLD; + public static readonly LINK_INTERCEPT_THRESHOLD = TerminalLinkHandler._LINK_INTERCEPT_THRESHOLD; + + private readonly _onBeforeHandleLink = this.add(new Emitter({ + onFirstListenerAdd: () => this._hasBeforeHandleLinkListeners = true, + onLastListenerRemove: () => this._hasBeforeHandleLinkListeners = false + })); + /** + * Allows intercepting links and handling them outside of the default link handler. When fired + * the listener has a set amount of time to handle the link or the default handler will fire. + * This was designed to only be handled by a single listener. + */ + public get onBeforeHandleLink(): Event { return this._onBeforeHandleLink.event; } constructor( private _xterm: Terminal, @@ -83,8 +99,11 @@ export class TerminalLinkHandler { @IEditorService private readonly _editorService: IEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, - @IFileService private readonly _fileService: IFileService + @IFileService private readonly _fileService: IFileService, + @ILogService private readonly _logService: ILogService ) { + super(); + // Matches '--- a/src/file1', capturing 'src/file1' in group 1 this._gitDiffPreImagePattern = /^--- a\/(\S*)/; // Matches '+++ b/src/file1', capturing 'src/file1' in group 1 @@ -211,19 +230,40 @@ export class TerminalLinkHandler { this._xterm.registerLinkMatcher(this._gitDiffPostImagePattern, wrappedHandler, options); } - public dispose(): void { - this._hoverDisposables.dispose(); - } - - private _wrapLinkHandler(handler: (uri: string) => boolean | void): XtermLinkMatcherHandler { - return (event: MouseEvent, uri: string) => { + protected _wrapLinkHandler(handler: (link: string) => void): XtermLinkMatcherHandler { + return async (event: MouseEvent, link: string) => { // Prevent default electron link handling so Alt+Click mode works normally event.preventDefault(); // Require correct modifier on click if (!this._isLinkActivationModifierDown(event)) { - return false; + return; } - return handler(uri); + + // Allow the link to be intercepted if there are listeners + if (this._hasBeforeHandleLinkListeners) { + const wasHandled = await new Promise(r => { + const timeoutId = setTimeout(() => { + canceled = true; + this._logService.error('An extension intecepted a terminal link but did not return'); + r(false); + }, TerminalLinkHandler.LINK_INTERCEPT_THRESHOLD); + let canceled = false; + const resolve = (handled: boolean) => { + if (!canceled) { + clearTimeout(timeoutId); + r(handled); + } + }; + this._onBeforeHandleLink.fire({ link, resolve }); + }); + if (!wasHandled) { + handler(link); + } + return; + } + + // Just call the handler if there is no before listener + handler(link); }; } @@ -244,18 +284,17 @@ export class TerminalLinkHandler { return this._gitDiffPostImagePattern; } - private _handleLocalLink(link: string): PromiseLike { - return this._resolvePath(link).then(resolvedLink => { - if (!resolvedLink) { - return Promise.resolve(null); - } - const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link); - const selection: ITextEditorSelection = { - startLineNumber: lineColumnInfo.lineNumber, - startColumn: lineColumnInfo.columnNumber - }; - return this._editorService.openEditor({ resource: resolvedLink, options: { pinned: true, selection } }); - }); + private async _handleLocalLink(link: string): Promise { + const resolvedLink = await this._resolvePath(link); + if (!resolvedLink) { + return; + } + const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link); + const selection: ITextEditorSelection = { + startLineNumber: lineColumnInfo.lineNumber, + startColumn: lineColumnInfo.columnNumber + }; + await this._editorService.openEditor({ resource: resolvedLink, options: { pinned: true, selection } }); } private _validateLocalLink(link: string, callback: (isValid: boolean) => void): void { @@ -270,7 +309,7 @@ export class TerminalLinkHandler { this._openerService.open(url, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) }); } - private _isLinkActivationModifierDown(event: MouseEvent): boolean { + protected _isLinkActivationModifierDown(event: MouseEvent): boolean { const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor'); if (editorConf.multiCursorModifier === 'ctrlCmd') { return !!event.altKey; @@ -346,19 +385,19 @@ export class TerminalLinkHandler { return link; } - private _resolvePath(link: string): PromiseLike { + private async _resolvePath(link: string): Promise { if (!this._processManager) { throw new Error('Process manager is required'); } const preprocessedLink = this._preprocessPath(link); if (!preprocessedLink) { - return Promise.resolve(null); + return undefined; } const linkUrl = this.extractLinkUrl(preprocessedLink); if (!linkUrl) { - return Promise.resolve(null); + return undefined; } try { @@ -373,18 +412,20 @@ export class TerminalLinkHandler { uri = URI.file(linkUrl); } - return this._fileService.resolve(uri).then(stat => { + try { + const stat = await this._fileService.resolve(uri); if (stat.isDirectory) { - return null; + return undefined; } return uri; - }).catch(() => { + } + catch (e) { // Does not exist - return null; - }); + return undefined; + } } catch { // Errors in parsing the path - return Promise.resolve(null); + return undefined; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index a9461856de..e717a2a52a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -15,7 +15,7 @@ import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab'; import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; -import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType, TerminalLinkHandlerCallback, LINK_INTERCEPT_THRESHOLD } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; @@ -30,6 +30,7 @@ import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; import { find } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; import { IViewsService } from 'vs/workbench/common/views'; +import { IDisposable } from 'vs/base/common/lifecycle'; interface IExtHostReadyEntry { promise: Promise; @@ -50,6 +51,7 @@ export class TerminalService implements ITerminalService { private _findState: FindReplaceState; private _extHostsReady: { [authority: string]: IExtHostReadyEntry | undefined } = {}; private _activeTabIndex: number; + private _linkHandlers: { [key: string]: TerminalLinkHandlerCallback } = {}; public get activeTabIndex(): number { return this._activeTabIndex; } public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } @@ -411,6 +413,50 @@ export class TerminalService implements ITerminalService { instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance))); instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onInstanceMaximumDimensionsChanged.fire(instance))); instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged)); + instance.addDisposable(instance.onBeforeHandleLink(async e => { + // No link handlers have been registered + const keys = Object.keys(this._linkHandlers); + if (keys.length === 0) { + e.resolve(false); + return; + } + + // Fire each link interceptor and wait for either a true, all false or the cancel time + let resolved = false; + const promises: Promise[] = []; + const timeout = setTimeout(() => { + resolved = true; + e.resolve(false); + }, LINK_INTERCEPT_THRESHOLD); + for (let i = 0; i < keys.length; i++) { + const p = this._linkHandlers[keys[i]](e); + p.then(handled => { + if (!resolved && handled) { + resolved = true; + clearTimeout(timeout); + e.resolve(true); + } + }); + promises.push(p); + } + await Promise.all(promises); + if (!resolved) { + resolved = true; + clearTimeout(timeout); + e.resolve(false); + } + })); + } + + public addLinkHandler(key: string, callback: TerminalLinkHandlerCallback): IDisposable { + this._linkHandlers[key] = callback; + return { + dispose: () => { + if (this._linkHandlers[key] === callback) { + delete this._linkHandlers[key]; + } + } + }; } private _getTabForInstance(instance: ITerminalInstance): ITerminalTab | undefined { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 42895d9e0a..a44a4b97fa 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -107,7 +107,7 @@ export interface ITerminalConfiguration { scrollback: number; commandsToSkipShell: string[]; allowChords: boolean; - allowMenubarMnemonics: boolean; + allowMnemonics: boolean; cwd: string; confirmOnExit: boolean; enableBell: boolean; diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts index 721345764b..3c40aa7b1c 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalLinkHandler.test.ts @@ -5,11 +5,12 @@ import * as assert from 'assert'; import { OperatingSystem } from 'vs/base/common/platform'; -import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; +import { TerminalLinkHandler, LineColumnInfo, XtermLinkMatcherHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import * as strings from 'vs/base/common/strings'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { Event } from 'vs/base/common/event'; import { ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; class TestTerminalLinkHandler extends TerminalLinkHandler { public get localLinkRegex(): RegExp { @@ -24,6 +25,13 @@ class TestTerminalLinkHandler extends TerminalLinkHandler { public preprocessPath(link: string): string | null { return this._preprocessPath(link); } + protected _isLinkActivationModifierDown(event: MouseEvent): boolean { + return true; + } + public wrapLinkHandler(handler: (link: string) => void): XtermLinkMatcherHandler { + TerminalLinkHandler._LINK_INTERCEPT_THRESHOLD = 0; + return this._wrapLinkHandler(handler); + } } class TestXterm { @@ -81,7 +89,7 @@ suite('Workbench - TerminalLinkHandler', () => { const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: '' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -157,7 +165,7 @@ suite('Workbench - TerminalLinkHandler', () => { const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -225,7 +233,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\Me' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); linkHandler.processCwd = 'C:\\base'; assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base\\src\\file1'); @@ -238,7 +246,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\M e' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); linkHandler.processCwd = 'C:\\base dir'; assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base dir\\src\\file1'); @@ -252,7 +260,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); linkHandler.processCwd = '/base'; assert.equal(linkHandler.preprocessPath('./src/file1'), '/base/src/file1'); @@ -265,7 +273,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); assert.equal(linkHandler.preprocessPath('./src/file1'), null); assert.equal(linkHandler.preprocessPath('src/file2'), null); @@ -279,7 +287,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' - } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!, null!); function assertAreGoodMatches(matches: RegExpMatchArray | null) { if (matches) { @@ -302,4 +310,35 @@ suite('Workbench - TerminalLinkHandler', () => { assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null'), false); assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null '), false); }); + + suite('wrapLinkHandler', () => { + const nullMouseEvent: any = Object.freeze({ preventDefault: () => { } }); + + test('should allow intercepting of links with onBeforeHandleLink', async () => { + const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { + os: OperatingSystem.Linux, + userHome: '' + } as any, testConfigHelper, null!, null!, new TestConfigurationService(), new MockTerminalInstanceService(), null!, null!); + linkHandler.onBeforeHandleLink(e => { + if (e.link === 'https://www.microsoft.com') { + intercepted = true; + e.resolve(true); + } + e.resolve(false); + }); + const wrappedHandler = linkHandler.wrapLinkHandler(() => defaultHandled = true); + + let defaultHandled = false; + let intercepted = false; + await wrappedHandler(nullMouseEvent, 'https://www.visualstudio.com'); + assert.equal(intercepted, false); + assert.equal(defaultHandled, true); + + defaultHandled = false; + intercepted = false; + await wrappedHandler(nullMouseEvent, 'https://www.microsoft.com'); + assert.equal(intercepted, true); + assert.equal(defaultHandled, false); + }); + }); }); diff --git a/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts index c6ceddc0db..434fdb8c55 100644 --- a/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/common/terminalColorRegistry.test.ts @@ -21,8 +21,8 @@ function getMockTheme(type: ThemeType): IColorTheme { getColor: (colorId: ColorIdentifier): Color | undefined => themingRegistry.resolveDefaultColor(colorId, theme), defines: () => true, getTokenStyleMetadata: () => undefined, - tokenColorMap: [] - + tokenColorMap: [], + semanticHighlighting: false }; return theme; } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts index d2adb7c5dd..e6e49d344e 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncView.ts @@ -30,8 +30,7 @@ export class UserDataSyncViewContribution implements IWorkbenchContribution { @IUserDataSyncBackupStoreService private readonly userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, ) { const container = this.registerSyncViewContainer(); - // Disable until server returns the correct timestamp - // this.registerBackupView(container, true); + this.registerBackupView(container, true); this.registerBackupView(container, false); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 5834c4980b..ff2d979578 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -109,7 +109,8 @@ export class WebviewEditor extends BaseEditor { return; } - if (this.webview) { + const alreadyOwnsWebview = input instanceof WebviewInput && input.webview === this.webview; + if (this.webview && !alreadyOwnsWebview) { this.webview.release(this); } @@ -125,7 +126,9 @@ export class WebviewEditor extends BaseEditor { input.updateGroup(this.group.id); } - this.claimWebview(input); + if (!alreadyOwnsWebview) { + this.claimWebview(input); + } if (this._dimension) { this.layout(this._dimension); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 1bdaff4ae7..4c0ece4212 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -18,8 +18,9 @@ export class WebviewInput extends EditorInput { private _iconPath?: WebviewIcons; private _group?: GroupIdentifier; - private readonly _webview: Lazy; - private _didSomeoneTakeMyWebview = false; + private _webview: Lazy; + + private _hasTransfered = false; get resource() { return URI.from({ @@ -42,8 +43,8 @@ export class WebviewInput extends EditorInput { dispose() { if (!this.isDisposed()) { - if (!this._didSomeoneTakeMyWebview) { - this._webview?.rawValue?.dispose(); + if (!this._hasTransfered) { + this._webview.rawValue?.dispose(); } } super.dispose(); @@ -107,11 +108,12 @@ export class WebviewInput extends EditorInput { return false; } - protected takeOwnershipOfWebview(): WebviewOverlay | undefined { - if (this._didSomeoneTakeMyWebview) { + protected transfer(other: WebviewInput): WebviewInput | undefined { + if (this._hasTransfered) { return undefined; } - this._didSomeoneTakeMyWebview = true; - return this.webview; + this._hasTransfered = true; + other._webview = this._webview; + return other; } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts index 13e3d87af3..e9d06c68a9 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewWorkbenchService.ts @@ -113,11 +113,25 @@ export class LazilyResolvedWebviewEditorInput extends WebviewInput { super(id, viewType, name, webview, webviewService); } + #resolved = false; + @memoize public async resolve() { - await this._webviewWorkbenchService.resolveWebview(this); + if (!this.#resolved) { + this.#resolved = true; + await this._webviewWorkbenchService.resolveWebview(this); + } return super.resolve(); } + + protected transfer(other: LazilyResolvedWebviewEditorInput): WebviewInput | undefined { + if (!super.transfer(other)) { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + + other.#resolved = this.#resolved; + return other; + } } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index c3c73be63f..03d618c06b 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -251,10 +251,10 @@ export class NativeWindow extends Disposable { const file = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file }); // Represented Filename - this.updateRepresentedFilename(file ? file.fsPath : undefined); + this.updateRepresentedFilename(file?.fsPath); // Custom title menu - this.provideCustomTitleContextMenu(file ? file.fsPath : undefined); + this.provideCustomTitleContextMenu(file?.fsPath); })); } diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index d2f435f56b..25168f61d9 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -10,8 +10,8 @@ import { ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; export const IBackupFileService = createDecorator('backupFileService'); export interface IResolvedBackup { - value: ITextBufferFactory; - meta?: T; + readonly value: ITextBufferFactory; + readonly meta?: T; } /** diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts index 7732692642..adc62d981f 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -147,8 +147,10 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench } } if (extensionKind === 'web') { - // Web extensions are not yet supported to be disabled by kind - return false; + // Web extensions are not yet supported to be disabled by kind. Enable them always on web. + if (this.extensionManagementServerService.localExtensionManagementServer === null) { + return false; + } } } return true; diff --git a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts index 0f1e76675c..092e961e3e 100644 --- a/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test.ts @@ -490,24 +490,32 @@ suite('ExtensionEnablementService Test', () => { assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); }); - test('test web extension on local server is not disabled by kind', async () => { + test('test web extension on local server is disabled by kind', async () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.ok(testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + assert.ok(!testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); }); - test('test web extension on remote server is not disabled by kind', async () => { - instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + test('test web extension on remote server is not disabled by kind when there is no local server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService))); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); - test('test web extension with no server is not disabled by kind', async () => { - instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); + test('test web extension with no server is not disabled by kind when there is no local server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, anExtensionManagementServer('vscode-remote', instantiationService))); + const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.https }) }); + testObject = new TestExtensionEnablementService(instantiationService); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); + }); + + test('test web extension with no server is not disabled by kind when there is no local and remote server', async () => { + instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService(null, null)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: ['web'] }, { location: URI.file(`pub.a`).with({ scheme: Schemas.https }) }); testObject = new TestExtensionEnablementService(instantiationService); assert.ok(testObject.isEnabled(localWorkspaceExtension)); diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 91c65e44fc..072d3a3cd9 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -25,6 +25,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; interface ParsedExtHostArgs { uriTransformerPath?: string; + useHostProxy?: string; } // workaround for https://github.com/microsoft/vscode/issues/85490 @@ -40,7 +41,8 @@ interface ParsedExtHostArgs { const args = minimist(process.argv.slice(2), { string: [ - 'uriTransformerPath' + 'uriTransformerPath', + 'useHostProxy' ] }) as ParsedExtHostArgs; @@ -293,6 +295,7 @@ export async function startExtensionHostProcess(): Promise { const { initData } = renderer; // setup things patchProcess(!!initData.environment.extensionTestsLocationURI); // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708) + initData.environment.useHostProxy = args.useHostProxy !== undefined ? args.useHostProxy !== 'false' : undefined; // host abstraction const hostUtils = new class NodeHost implements IHostUtils { diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 5d34808fa9..0b6d45fd21 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -16,7 +16,7 @@ import { endsWith } from 'vs/base/common/strings'; import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { ProxyAgent } from 'vscode-proxy-agent'; -import { MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTelemetryShape, IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { URI } from 'vs/base/common/uri'; @@ -35,9 +35,10 @@ export function connectProxyResolver( configProvider: ExtHostConfigProvider, extensionService: ExtHostExtensionService, extHostLogService: ILogService, - mainThreadTelemetry: MainThreadTelemetryShape + mainThreadTelemetry: MainThreadTelemetryShape, + initData: IInitData, ) { - const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry); + const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry, initData); const lookup = createPatchedModules(configProvider, resolveProxy); return configureModuleLoading(extensionService, lookup); } @@ -48,7 +49,8 @@ function setupProxyResolution( extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extHostLogService: ILogService, - mainThreadTelemetry: MainThreadTelemetryShape + mainThreadTelemetry: MainThreadTelemetryShape, + initData: IInitData, ) { const env = process.env; @@ -139,12 +141,14 @@ function setupProxyResolution( timeout = setTimeout(logEvent, 10 * 60 * 1000); } + const useHostProxy = initData.environment.useHostProxy; + const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote; useSystemCertificates(extHostLogService, flags.useSystemCertificates, opts, () => { - useProxySettings(flags.useProxySettings, req, opts, url, callback); + useProxySettings(doUseHostProxy, flags.useProxySettings, req, opts, url, callback); }); } - function useProxySettings(useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { + function useProxySettings(useHostProxy: boolean, useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { if (!useProxySettings) { callback('DIRECT'); @@ -192,6 +196,12 @@ function setupProxyResolution( return; } + if (!useHostProxy) { + callback('DIRECT'); + extHostLogService.trace('ProxyResolver#resolveProxy unconfigured', url, 'DIRECT'); + return; + } + const start = Date.now(); extHostWorkspace.resolveProxy(url) // Use full URL to ensure it is an actually used one. .then(proxy => { diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 88849460c8..3141e9b427 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -9,21 +9,19 @@ import { ITextEditorOptions, IResourceEditorInput, TextEditorSelectionRevealType import { IEditorInput, IEditorPane, Extensions as EditorExtensions, EditorInput, IEditorCloseEvent, IEditorInputFactoryRegistry, toResource, IEditorIdentifier, GroupIdentifier, EditorsOrder } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; +import { FileChangesEvent, IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { Selection } from 'vs/editor/common/core/selection'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { Event } from 'vs/base/common/event'; -import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { getExcludes, ISearchConfiguration } from 'vs/workbench/services/search/common/search'; -import { IExpression } from 'vs/base/common/glob'; +import { createResourceExcludeMatcher } from 'vs/workbench/services/search/common/search'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -98,8 +96,8 @@ export class HistoryService extends Disposable implements IHistoryService { private readonly activeEditorListeners = this._register(new DisposableStore()); private lastActiveEditor?: IEditorIdentifier; - private readonly editorHistoryListeners: Map = new Map(); - private readonly editorStackListeners: Map = new Map(); + private readonly editorHistoryListeners = new Map(); + private readonly editorStackListeners = new Map(); constructor( @IEditorService private readonly editorService: EditorServiceImpl, @@ -124,7 +122,7 @@ export class HistoryService extends Disposable implements IHistoryService { this._register(this.editorService.onDidCloseEditor(event => this.onEditorClosed(event))); this._register(this.storageService.onWillSaveState(() => this.saveState())); this._register(this.fileService.onDidFilesChange(event => this.onDidFilesChange(event))); - this._register(this.resourceFilter.onExpressionChange(() => this.removeExcludedFromHistory())); + this._register(this.resourceExcludeMatcher.onExpressionChange(() => this.removeExcludedFromHistory())); this._register(this.editorService.onDidMostRecentlyActiveEditorsChange(() => this.handleEditorEventInRecentEditorsStack())); // if the service is created late enough that an editor is already opened @@ -718,17 +716,7 @@ export class HistoryService extends Disposable implements IHistoryService { private history: Array | undefined = undefined; - private readonly resourceFilter = this._register(this.instantiationService.createInstance( - ResourceGlobMatcher, - (root?: URI) => this.getExcludes(root), - (event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration('search.exclude') - )); - - private getExcludes(root?: URI): IExpression { - const scope = root ? { resource: root } : undefined; - - return getExcludes(scope ? this.configurationService.getValue(scope) : this.configurationService.getValue())!; - } + private readonly resourceExcludeMatcher = this._register(createResourceExcludeMatcher(this.instantiationService, this.configurationService)); private handleEditorEventInHistory(editor?: IEditorPane): void { @@ -764,7 +752,7 @@ export class HistoryService extends Disposable implements IHistoryService { const resourceEditorInput = input as IResourceEditorInput; - return !this.resourceFilter.matches(resourceEditorInput.resource); + return !this.resourceExcludeMatcher.matches(resourceEditorInput.resource); } private removeExcludedFromHistory(): void { diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index f0f6fefc74..9a62182b9d 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -55,6 +55,8 @@ import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/se import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { TestTextResourcePropertiesService, TestContextService, TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; class TestEnvironmentService extends NativeWorkbenchEnvironmentService { @@ -107,6 +109,7 @@ suite('KeybindingsEditing', () => { instantiationService.stub(IFilesConfigurationService, instantiationService.createInstance(FilesConfigurationService)); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); + instantiationService.stub(IThemeService, new TestThemeService()); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); diff --git a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index 7cfebd30f6..4d6f8d7e66 100644 --- a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -28,6 +28,9 @@ export class BrowserLifecycleService extends AbstractLifecycleService { } private onBeforeUnload(): string | null { + const logService = this.logService; + logService.info('[lifecycle] onBeforeUnload triggered'); + let veto = false; // Before Shutdown @@ -36,7 +39,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 in the web')); + logService.error('[lifecycle] Long running onBeforeShutdown currently not supported in the web'); veto = true; } }, @@ -51,7 +54,7 @@ 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 in the web')); + logService.error('[lifecycle] Long running onWillShutdown currently not supported in the web'); }, reason: ShutdownReason.QUIT }); diff --git a/src/vs/workbench/services/progress/browser/progressIndicator.ts b/src/vs/workbench/services/progress/browser/progressIndicator.ts index f5f1129881..8b05bc5107 100644 --- a/src/vs/workbench/services/progress/browser/progressIndicator.ts +++ b/src/vs/workbench/services/progress/browser/progressIndicator.ts @@ -198,7 +198,6 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr progressbar: ProgressBar, scopeId: string, isActive: boolean, - private readonly options: { exclusiveProgressBar?: boolean } | undefined, @IViewletService viewletService: IViewletService, @IPanelService panelService: IPanelService, @IViewsService viewsService: IViewsService @@ -212,9 +211,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr onScopeDeactivated(): void { this.isActive = false; - if (this.options?.exclusiveProgressBar) { - this.progressbar.stop().hide(); - } + this.progressbar.stop().hide(); } onScopeActivated(): void { @@ -314,7 +311,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr done: () => { this.progressState = ProgressIndicatorState.Done; - if (this.isActive || this.options?.exclusiveProgressBar) { + if (this.isActive) { this.progressbar.stop().hide(); } } @@ -345,7 +342,7 @@ export class CompositeProgressIndicator extends CompositeScope implements IProgr // The while promise is either null or equal the promise we last hooked on this.progressState = ProgressIndicatorState.None; - if (this.isActive || this.options?.exclusiveProgressBar) { + if (this.isActive) { this.progressbar.stop().hide(); } } diff --git a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts index c68f6fec71..4a3b664131 100644 --- a/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/browser/progressIndicator.test.ts @@ -129,7 +129,7 @@ suite('Progress Indicator', () => { let viewletService = new TestViewletService(); let panelService = new TestPanelService(); let viewsService = new TestViewsService(); - let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, undefined, viewletService, panelService, viewsService); + let service = new CompositeProgressIndicator((testProgressBar), 'test.scopeId', true, viewletService, panelService, viewsService); // Active: Show (Infinite) let fn = service.show(true); diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 76c3e0f191..ccd4f9295c 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -11,16 +11,20 @@ import * as objects from 'vs/base/common/objects'; import * as extpath from 'vs/base/common/extpath'; import { fuzzyContains, getNLines } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IFilesConfiguration } from 'vs/platform/files/common/files'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IFilesConfiguration, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { Event } from 'vs/base/common/event'; import { relative } from 'vs/base/common/path'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; export const VIEWLET_ID = 'workbench.view.search'; export const PANEL_ID = 'workbench.panel.search'; export const VIEW_ID = 'workbench.view.search'; +export const SEARCH_EXCLUDE_CONFIG = 'search.exclude'; + export const ISearchService = createDecorator('searchService'); /** @@ -372,6 +376,14 @@ export function getExcludes(configuration: ISearchConfiguration, includeSearchEx return allExcludes; } +export function createResourceExcludeMatcher(instantiationService: IInstantiationService, configurationService: IConfigurationService): ResourceGlobMatcher { + return instantiationService.createInstance( + ResourceGlobMatcher, + root => getExcludes(root ? configurationService.getValue({ resource: root }) : configurationService.getValue()) || Object.create(null), + event => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration(SEARCH_EXCLUDE_CONFIG) + ); +} + export function pathIncludedInQuery(queryProps: ICommonQueryProps, fsPath: string): boolean { if (queryProps.excludePattern && glob.match(queryProps.excludePattern, fsPath)) { return false; diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index c094dfaac1..7e300288a5 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -44,6 +44,8 @@ const tokenGroupToScopesMap = { export type TokenStyleDefinition = TokenStylingRule | ProbeScope[] | TokenStyleValue; export type TokenStyleDefinitions = { [P in keyof TokenStyleData]?: TokenStyleDefinition | undefined }; +export type TextMateThemingRuleDefinitions = { [P in keyof TokenStyleData]?: ITextMateThemingRule | undefined; } & { scope?: ProbeScope; }; + const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; export class ColorThemeData implements IWorkbenchColorTheme { @@ -57,6 +59,9 @@ export class ColorThemeData implements IWorkbenchColorTheme { watch?: boolean; extensionData?: ExtensionData; + private themeSemanticHighlighting: boolean | undefined; + private customSemanticHighlighting: boolean | undefined; + private themeTokenColors: ITextMateThemingRule[] = []; private customTokenColors: ITextMateThemingRule[] = []; private colorMap: IColorMap = {}; @@ -78,6 +83,10 @@ export class ColorThemeData implements IWorkbenchColorTheme { this.isLoaded = false; } + get semanticHighlighting(): boolean { + return this.customSemanticHighlighting !== undefined ? this.customSemanticHighlighting : !!this.themeSemanticHighlighting; + } + get tokenColors(): ITextMateThemingRule[] { if (!this.textMateThemingRules) { const result: ITextMateThemingRule[] = []; @@ -271,7 +280,8 @@ export class ColorThemeData implements IWorkbenchColorTheme { return colorRegistry.resolveDefaultColor(colorId, this); } - public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { + + public resolveScopes(scopes: ProbeScope[], definitions?: TextMateThemingRuleDefinitions): TokenStyle | undefined { if (!this.themeTokenScopeMatchers) { this.themeTokenScopeMatchers = this.themeTokenColors.map(getScopeMatcher); @@ -285,19 +295,24 @@ export class ColorThemeData implements IWorkbenchColorTheme { let fontStyle: string | undefined = undefined; let foregroundScore = -1; let fontStyleScore = -1; + let fontStyleThemingRule: ITextMateThemingRule | undefined = undefined; + let foregroundThemingRule: ITextMateThemingRule | undefined = undefined; - function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITextMateThemingRule[]) { + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], themingRules: ITextMateThemingRule[]) { for (let i = 0; i < scopeMatchers.length; i++) { const score = scopeMatchers[i](scope); if (score >= 0) { - const settings = tokenColors[i].settings; + const themingRule = themingRules[i]; + const settings = themingRules[i].settings; if (score >= foregroundScore && settings.foreground) { foreground = settings.foreground; foregroundScore = score; + foregroundThemingRule = themingRule; } if (score >= fontStyleScore && types.isString(settings.fontStyle)) { fontStyle = settings.fontStyle; fontStyleScore = score; + fontStyleThemingRule = themingRule; } } } @@ -305,6 +320,12 @@ export class ColorThemeData implements IWorkbenchColorTheme { findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); if (foreground !== undefined || fontStyle !== undefined) { + if (definitions) { + definitions.foreground = foregroundThemingRule; + definitions.bold = definitions.italic = definitions.underline = fontStyleThemingRule; + definitions.scope = scope; + } + return TokenStyle.fromSettings(foreground, fontStyle); } } @@ -346,6 +367,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) { this.customTokenColors = []; + this.customSemanticHighlighting = undefined; // first add the non-theme specific settings this.addCustomTokenColors(customTokenColors); @@ -397,6 +419,9 @@ export class ColorThemeData implements IWorkbenchColorTheme { } } } + if (customTokenColors.semanticHighlighting !== undefined) { + this.customSemanticHighlighting = customTokenColors.semanticHighlighting; + } } public ensureLoaded(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise { @@ -417,13 +442,15 @@ export class ColorThemeData implements IWorkbenchColorTheme { const result = { colors: {}, textMateRules: [], - stylingRules: undefined + stylingRules: undefined, + semanticHighlighting: false }; return _loadColorTheme(extensionResourceLoaderService, this.location, result).then(_ => { this.isLoaded = true; this.tokenStylingRules = result.stylingRules; this.colorMap = result.colors; this.themeTokenColors = result.textMateRules; + this.themeSemanticHighlighting = result.semanticHighlighting; }); } @@ -447,6 +474,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { selector: this.id.split(' ').join('.'), // to not break old clients themeTokenColors: this.themeTokenColors, extensionData: this.extensionData, + themeSemanticHighlighting: this.themeSemanticHighlighting, colorMap: colorMapData, watch: this.watch }); @@ -454,7 +482,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { } hasEqualData(other: ColorThemeData) { - return objects.equals(this.colorMap, other.colorMap) && objects.equals(this.themeTokenColors, other.themeTokenColors); + return objects.equals(this.colorMap, other.colorMap) && objects.equals(this.themeTokenColors, other.themeTokenColors) && this.themeSemanticHighlighting === other.themeSemanticHighlighting; } get baseTheme(): string { @@ -504,7 +532,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { } break; case 'themeTokenColors': - case 'id': case 'label': case 'settingsId': case 'extensionData': case 'watch': + case 'id': case 'label': case 'settingsId': case 'extensionData': case 'watch': case 'themeSemanticHighlighting': (theme as any)[key] = data[key]; break; } @@ -548,7 +576,7 @@ function toCSSSelector(extensionId: string, path: string) { return str; } -function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[] | undefined }): Promise { +function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[] | undefined, semanticHighlighting: boolean }): Promise { if (resources.extname(themeLocation) === '.json') { return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => { let errors: Json.ParseError[] = []; @@ -567,6 +595,7 @@ function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoade convertSettings(contentValue.settings, result); return null; } + result.semanticHighlighting = result.semanticHighlighting || contentValue.semanticHighlighting; let colors = contentValue.colors; if (colors) { if (typeof colors !== 'object') { @@ -591,10 +620,10 @@ function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoade return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.tokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'tokenColors' should be either an array specifying colors or a path to a TextMate theme file", themeLocation.toString()))); } } - let tokenStylingRules = contentValue.tokenStylingRules; - if (tokenStylingRules && typeof tokenStylingRules === 'object') { - result.stylingRules = readCustomTokenStyleRules(tokenStylingRules, result.stylingRules); - } + // let tokenStylingRules = contentValue.tokenStylingRules; + // if (tokenStylingRules && typeof tokenStylingRules === 'object') { + // result.stylingRules = readCustomTokenStyleRules(tokenStylingRules, result.stylingRules); + // } return null; }); }); diff --git a/src/vs/workbench/services/themes/common/colorThemeSchema.ts b/src/vs/workbench/services/themes/common/colorThemeSchema.ts index 8ca9ce18ed..4e770182c5 100644 --- a/src/vs/workbench/services/themes/common/colorThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/colorThemeSchema.ts @@ -222,6 +222,10 @@ const colorThemeSchema: IJSONSchema = { $ref: textmateColorsSchemaId } ] + }, + semanticHighlighting: { + type: 'boolean', + description: nls.localize('schema.supportsSemanticHighlighting', 'Whether semantic highlighting should be enabled for this theme.') } } }; diff --git a/src/vs/workbench/services/themes/common/themeConfiguration.ts b/src/vs/workbench/services/themes/common/themeConfiguration.ts index f682b6afc3..0e29069838 100644 --- a/src/vs/workbench/services/themes/common/themeConfiguration.ts +++ b/src/vs/workbench/services/themes/common/themeConfiguration.ts @@ -132,6 +132,10 @@ const tokenColorSchema: IJSONSchema = { textMateRules: { description: nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).'), $ref: textmateColorsSchemaId + }, + semanticHighlighting: { + description: nls.localize('editorColors.semanticHighlighting', 'Whether semantic highlighting should be enabled for this theme.'), + type: 'boolean' } } }; @@ -154,6 +158,7 @@ const tokenColorCustomizationConfiguration: IConfigurationNode = { [ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL]: experimentalTokenStylingCustomizationSchema } }; + configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); export function updateColorThemeConfigurationSchemas(themes: IWorkbenchColorTheme[]) { diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index d48db67132..0cadd30902 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -97,7 +97,7 @@ export interface IColorCustomizations { } export interface ITokenColorCustomizations { - [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[]; + [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[] | boolean; comments?: string | ITokenColorizationSetting; strings?: string | ITokenColorizationSetting; numbers?: string | ITokenColorizationSetting; @@ -106,6 +106,7 @@ export interface ITokenColorCustomizations { functions?: string | ITokenColorizationSetting; variables?: string | ITokenColorizationSetting; textMateRules?: ITextMateThemingRule[]; + semanticHighlighting?: boolean; } export interface IExperimentalTokenStyleCustomizations { diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts deleted file mode 100644 index 0de6a39fd5..0000000000 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ /dev/null @@ -1,111 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { SyncStatus, ISettingsSyncService, IConflictSetting, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Emitter, Event } from 'vs/base/common/event'; -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -export class SettingsSyncService extends Disposable implements ISettingsSyncService { - - _serviceBrand: undefined; - - private readonly channel: IChannel; - - readonly resourceKey = 'settings'; - readonly resource = SyncResource.Settings; - - private _status: SyncStatus = SyncStatus.Uninitialized; - get status(): SyncStatus { return this._status; } - private _onDidChangeStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; - - private _conflicts: IConflictSetting[] = []; - get conflicts(): IConflictSetting[] { return this._conflicts; } - private _onDidChangeConflicts: Emitter = this._register(new Emitter()); - readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; - - get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } - - constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService - ) { - super(); - this.channel = sharedProcessService.getChannel('settingsSync'); - this.channel.call('_getInitialStatus').then(status => { - this.updateStatus(status); - this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); - }); - this.channel.call('_getInitialConflicts').then(conflicts => { - if (conflicts.length) { - this.updateConflicts(conflicts); - } - this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); - }); - } - - pull(): Promise { - return this.channel.call('pull'); - } - - push(): Promise { - return this.channel.call('push'); - } - - sync(): Promise { - return this.channel.call('sync'); - } - - stop(): Promise { - return this.channel.call('stop'); - } - - resetLocal(): Promise { - return this.channel.call('resetLocal'); - } - - hasPreviouslySynced(): Promise { - return this.channel.call('hasPreviouslySynced'); - } - - hasLocalData(): Promise { - return this.channel.call('hasLocalData'); - } - - accept(content: string): Promise { - return this.channel.call('accept', [content]); - } - - resolveSettingsConflicts(conflicts: { key: string, value: any | undefined }[]): Promise { - return this.channel.call('resolveConflicts', [conflicts]); - } - - getRemoteContent(ref?: string, fragment?: string): Promise { - return this.channel.call('getRemoteContent', [ref, fragment]); - } - - getLocalBackupContent(ref?: string, fragment?: string): Promise { - return this.channel.call('getLocalBackupContent', [ref, fragment]); - } - - getRemoteContentFromPreview(): Promise { - return this.channel.call('getRemoteContentFromPreview', []); - } - - private async updateStatus(status: SyncStatus): Promise { - this._status = status; - this._onDidChangeStatus.fire(status); - } - - private async updateConflicts(conflicts: IConflictSetting[]): Promise { - this._conflicts = conflicts; - this._onDidChangeConflicts.fire(conflicts); - } - -} - -registerSingleton(ISettingsSyncService, SettingsSyncService); diff --git a/src/vs/workbench/test/browser/parts/editor/editor.test.ts b/src/vs/workbench/test/browser/parts/editor/editor.test.ts index a1507d42ba..dfac3f223e 100644 --- a/src/vs/workbench/test/browser/parts/editor/editor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editor.test.ts @@ -35,6 +35,8 @@ suite('Workbench editor', () => { assert.equal(toResource(untitled)!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), untitled.resource.toString()); + assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), untitled.resource.toString()); + assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString()); assert.ok(!toResource(untitled, { filterByScheme: Schemas.file })); @@ -43,6 +45,8 @@ suite('Workbench editor', () => { assert.equal(toResource(file)!.toString(), file.resource.toString()); assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), file.resource.toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), file.resource.toString()); assert.equal(toResource(file, { filterByScheme: Schemas.file })!.toString(), file.resource.toString()); assert.equal(toResource(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); assert.ok(!toResource(file, { filterByScheme: Schemas.untitled })); @@ -52,8 +56,20 @@ suite('Workbench editor', () => { assert.ok(!toResource(diffEditorInput)); assert.ok(!toResource(diffEditorInput, { filterByScheme: Schemas.file })); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.resource.toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); + + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), untitled.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS, filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString()); + + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.file }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.untitled }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); }); }); diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index 2af7160550..46c101c058 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -24,6 +24,8 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { @@ -84,6 +86,7 @@ suite('Workbench editor model', () => { instantiationService.stub(IDialogService, dialogService); instantiationService.stub(INotificationService, notificationService); instantiationService.stub(IUndoRedoService, undoRedoService); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts index 342fa83d1c..b82ffffc84 100644 --- a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts @@ -22,6 +22,8 @@ import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommand import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; suite('Editor - Range decorations', () => { @@ -157,6 +159,7 @@ suite('Editor - Range decorations', () => { function stubModelService(instantiationService: TestInstantiationService): IModelService { instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IThemeService, new TestThemeService()); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/test/browser/quickAccess.test.ts b/src/vs/workbench/test/browser/quickAccess.test.ts index d717e04f96..21e1b5daf0 100644 --- a/src/vs/workbench/test/browser/quickAccess.test.ts +++ b/src/vs/workbench/test/browser/quickAccess.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IQuickAccessRegistry, Extensions, IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess'; +import { IQuickAccessRegistry, Extensions, IQuickAccessProvider, QuickAccessRegistry } from 'vs/platform/quickinput/common/quickAccess'; import { IQuickPick, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -18,6 +18,10 @@ suite('QuickAccess', () => { let instantiationService: IInstantiationService; let accessor: TestServiceAccessor; + let providerDefaultCalled = false; + let providerDefaultCanceled = false; + let providerDefaultDisposed = false; + let provider1Called = false; let provider1Canceled = false; let provider1Disposed = false; @@ -30,9 +34,21 @@ suite('QuickAccess', () => { let provider3Canceled = false; let provider3Disposed = false; - let provider4Called = false; - let provider4Canceled = false; - let provider4Disposed = false; + class TestProviderDefault implements IQuickAccessProvider { + + constructor(@IQuickInputService private readonly quickInputService: IQuickInputService, disposables: DisposableStore) { } + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + assert.ok(picker); + providerDefaultCalled = true; + token.onCancellationRequested(() => providerDefaultCanceled = true); + + // bring up provider #3 + setTimeout(() => this.quickInputService.quickAccess.show(providerDescriptor3.prefix)); + + return toDisposable(() => providerDefaultDisposed = true); + } + } class TestProvider1 implements IQuickAccessProvider { provide(picker: IQuickPick, token: CancellationToken): IDisposable { @@ -55,39 +71,22 @@ suite('QuickAccess', () => { } class TestProvider3 implements IQuickAccessProvider { - - constructor(@IQuickInputService private readonly quickInputService: IQuickInputService, disposables: DisposableStore) { } - provide(picker: IQuickPick, token: CancellationToken): IDisposable { assert.ok(picker); provider3Called = true; token.onCancellationRequested(() => provider3Canceled = true); - // bring up provider #4 - setTimeout(() => this.quickInputService.quickAccess.show(providerDescriptor4.prefix)); + // hide without picking + setTimeout(() => picker.hide()); return toDisposable(() => provider3Disposed = true); } } - class TestProvider4 implements IQuickAccessProvider { - provide(picker: IQuickPick, token: CancellationToken): IDisposable { - assert.ok(picker); - provider4Called = true; - token.onCancellationRequested(() => provider4Canceled = true); - - // hide without picking - setTimeout(() => picker.hide()); - - return toDisposable(() => provider4Disposed = true); - } - } - - const defaultProviderDescriptor = { ctor: TestProvider1, prefix: '', helpEntries: [] }; + const providerDescriptorDefault = { ctor: TestProviderDefault, prefix: '', helpEntries: [] }; const providerDescriptor1 = { ctor: TestProvider1, prefix: 'test', helpEntries: [] }; const providerDescriptor2 = { ctor: TestProvider2, prefix: 'test something', helpEntries: [] }; - const providerDescriptor3 = { ctor: TestProvider3, prefix: 'default', helpEntries: [] }; - const providerDescriptor4 = { ctor: TestProvider4, prefix: 'changed', helpEntries: [] }; + const providerDescriptor3 = { ctor: TestProvider3, prefix: 'changed', helpEntries: [] }; setup(() => { instantiationService = workbenchInstantiationService(); @@ -96,91 +95,101 @@ suite('QuickAccess', () => { test('registry', () => { const registry = (Registry.as(Extensions.Quickaccess)); - registry.defaultProvider = defaultProviderDescriptor; + const restore = (registry as QuickAccessRegistry).clear(); - const initialSize = registry.getQuickAccessProviders().length; + assert.ok(!registry.getQuickAccessProvider('test')); - const disposable = registry.registerQuickAccessProvider(providerDescriptor1); + const disposables = new DisposableStore(); + disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault)); + assert(registry.getQuickAccessProvider('') === providerDescriptorDefault); + assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault); + + const disposable = disposables.add(registry.registerQuickAccessProvider(providerDescriptor1)); assert(registry.getQuickAccessProvider('test') === providerDescriptor1); const providers = registry.getQuickAccessProviders(); assert(providers.some(provider => provider.prefix === 'test')); disposable.dispose(); + assert(registry.getQuickAccessProvider('test') === providerDescriptorDefault); + + disposables.dispose(); assert.ok(!registry.getQuickAccessProvider('test')); - assert.equal(registry.getQuickAccessProviders().length - initialSize, 0); + + restore(); }); test('provider', async () => { const registry = (Registry.as(Extensions.Quickaccess)); - const defaultProvider = registry.defaultProvider; + const restore = (registry as QuickAccessRegistry).clear(); const disposables = new DisposableStore(); + disposables.add(registry.registerQuickAccessProvider(providerDescriptorDefault)); disposables.add(registry.registerQuickAccessProvider(providerDescriptor1)); disposables.add(registry.registerQuickAccessProvider(providerDescriptor2)); - disposables.add(registry.registerQuickAccessProvider(providerDescriptor4)); - registry.defaultProvider = providerDescriptor3; + disposables.add(registry.registerQuickAccessProvider(providerDescriptor3)); accessor.quickInputService.quickAccess.show('test'); + assert.equal(providerDefaultCalled, false); assert.equal(provider1Called, true); assert.equal(provider2Called, false); assert.equal(provider3Called, false); - assert.equal(provider4Called, false); + assert.equal(providerDefaultCanceled, false); assert.equal(provider1Canceled, false); assert.equal(provider2Canceled, false); assert.equal(provider3Canceled, false); - assert.equal(provider4Canceled, false); + assert.equal(providerDefaultDisposed, false); assert.equal(provider1Disposed, false); assert.equal(provider2Disposed, false); assert.equal(provider3Disposed, false); - assert.equal(provider4Disposed, false); provider1Called = false; accessor.quickInputService.quickAccess.show('test something'); + assert.equal(providerDefaultCalled, false); assert.equal(provider1Called, false); assert.equal(provider2Called, true); assert.equal(provider3Called, false); - assert.equal(provider4Called, false); + assert.equal(providerDefaultCanceled, false); assert.equal(provider1Canceled, true); assert.equal(provider2Canceled, false); assert.equal(provider3Canceled, false); - assert.equal(provider4Canceled, false); + assert.equal(providerDefaultDisposed, false); assert.equal(provider1Disposed, true); assert.equal(provider2Disposed, false); assert.equal(provider3Disposed, false); - assert.equal(provider4Disposed, false); provider2Called = false; provider1Canceled = false; provider1Disposed = false; accessor.quickInputService.quickAccess.show('usedefault'); + assert.equal(providerDefaultCalled, true); assert.equal(provider1Called, false); assert.equal(provider2Called, false); - assert.equal(provider3Called, true); - assert.equal(provider4Called, false); + assert.equal(provider3Called, false); + assert.equal(providerDefaultCanceled, false); assert.equal(provider1Canceled, false); assert.equal(provider2Canceled, true); assert.equal(provider3Canceled, false); - assert.equal(provider4Canceled, false); + assert.equal(providerDefaultDisposed, false); assert.equal(provider1Disposed, false); assert.equal(provider2Disposed, true); assert.equal(provider3Disposed, false); - assert.equal(provider4Disposed, false); + + await timeout(1); + + assert.equal(providerDefaultCanceled, true); + assert.equal(providerDefaultDisposed, true); + assert.equal(provider3Called, true); await timeout(1); assert.equal(provider3Canceled, true); assert.equal(provider3Disposed, true); - assert.equal(provider4Called, true); - - await timeout(1); - - assert.equal(provider4Canceled, true); - assert.equal(provider4Disposed, true); disposables.dispose(); - registry.defaultProvider = defaultProvider; + + restore(); }); }); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 67aacf6300..d9159fde73 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -141,6 +141,8 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(IHistoryService, new TestHistoryService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService)); instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); + const themeService = new TestThemeService(); + instantiationService.stub(IThemeService, themeService); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); instantiationService.stub(IFileService, new TestFileService()); instantiationService.stub(IBackupFileService, new TestBackupFileService()); @@ -156,8 +158,6 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i instantiationService.stub(ITextFileService, overrides?.textFileService ? overrides.textFileService(instantiationService) : instantiationService.createInstance(TestTextFileService)); instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); - const themeService = new TestThemeService(); - instantiationService.stub(IThemeService, themeService); instantiationService.stub(ILogService, new NullLogService()); const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); instantiationService.stub(IEditorGroupsService, editorGroupService); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index fd045a5f82..deba2733c0 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -50,7 +50,6 @@ import 'vs/workbench/services/url/electron-browser/urlService'; import 'vs/workbench/services/workspaces/electron-browser/workspacesService'; import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'; -import 'vs/workbench/services/userDataSync/electron-browser/settingsSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncStoreService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncBackupStoreService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index cc7be33a55..4117050ebd 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -62,13 +62,12 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; -import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, ISettingsSyncService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { AuthenticationService, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; -import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; import { UserDataAutoSyncService } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSyncService'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; @@ -87,7 +86,6 @@ registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService); registerSingleton(IAuthenticationTokenService, AuthenticationTokenService); registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService); -registerSingleton(ISettingsSyncService, SettingsSynchroniser); registerSingleton(IUserDataSyncService, UserDataSyncService); registerSingleton(ITitleService, TitlebarPart); diff --git a/yarn.lock b/yarn.lock index bd9a20bbf5..91188b2afc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -626,9 +626,9 @@ acorn-jsx@^5.1.0: integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== acorn@^5.0.0, acorn@^5.6.2: - version "5.7.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" - integrity sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ== + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== acorn@^6.0.2: version "6.0.7" @@ -9476,16 +9476,16 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@3.9.0-dev.20200304: - version "3.9.0-dev.20200304" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200304.tgz#3cc35357eff29dc5604b4fa56d6597e13daf86ed" - integrity sha512-eUip/GgJmjp4qtHiJDxVhE5SDDiPzBUg7KBAFUgb7HgL/tv10JAHej7fnS1i+7xrq1eDtbkJyPaYOVnhL9db7Q== - typescript@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= +typescript@^3.9.0-dev.20200313: + version "3.9.0-dev.20200313" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200313.tgz#f66aeb2c08268f2b1fc6d1d96e15554c6e7ed29b" + integrity sha512-85/IJPm1nEUbQDxK3aN+svIy4X3kPcAipihB3704NY1HXncJ1daNLJW1OktOacb8tD/URpIGs9nMgbUrKvglGg== + uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192"