From a64304602e39b89703e63eea660bd3c037142206 Mon Sep 17 00:00:00 2001 From: ADS Merger Date: Sun, 29 Mar 2020 01:29:32 +0000 Subject: [PATCH] Merge from vscode 1ec43773e37997841c5af42b33ddb180e9735bf2 --- .github/ISSUE_TEMPLATE/bug_report.md | 1 + .github/locker.yml | 6 - .github/new_release.yml | 6 - .../workflows/test-plan-item-validator.yml | 21 + build/gulpfile.vscode.js | 1 - build/lib/i18n.resources.json | 4 - build/lib/optimize.js | 42 - build/lib/optimize.ts | 49 - build/package.json | 2 +- build/yarn.lock | 8 +- .../github-authentication/src/extension.ts | 4 +- .../github-authentication/src/github.ts | 4 +- .../github-authentication/src/githubServer.ts | 42 + .../merge-conflict/src/documentTracker.ts | 2 +- .../theme-red/themes/Red-color-theme.json | 2 +- .../themes/solarized-light-color-theme.json | 5 +- package.json | 2 +- .../browser/queryHistoryActionProvider.ts | 4 +- .../tasks/browser/tasksActionProvider.ts | 4 +- .../browser/recentConnectionTreeController.ts | 4 +- .../browser/serverTreeActionProvider.ts | 4 +- src/tsconfig.monaco.json | 2 - src/vs/base/browser/ui/actionbar/actionbar.ts | 63 +- .../ui/breadcrumbs/breadcrumbsWidget.ts | 11 +- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 4 +- .../base/browser/ui/iconLabel/iconlabel.css | 4 - src/vs/base/browser/ui/menu/menubar.ts | 8 +- .../browser/ui/scrollbar/abstractScrollbar.ts | 10 + .../ui/scrollbar/horizontalScrollbar.ts | 4 + .../browser/ui/scrollbar/scrollableElement.ts | 22 +- .../ui/scrollbar/scrollableElementOptions.ts | 5 +- .../browser/ui/scrollbar/scrollbarState.ts | 6 +- .../browser/ui/scrollbar/verticalScrollbar.ts | 4 + src/vs/base/browser/ui/splitview/paneview.ts | 6 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 2 +- src/vs/base/browser/ui/tree/treeDefaults.ts | 6 +- src/vs/base/common/amd.ts | 9 - src/vs/base/common/codicon.ts | 2 +- src/vs/base/common/codicons.ts | 6 + src/vs/base/common/fuzzyScorer.ts | 94 +- .../browser/media/arrow-left-dark.svg | 12 - .../browser/media/arrow-left-light.svg | 12 - .../parts/quickinput/browser/quickInput.ts | 4 +- .../quickinput/browser/quickInputList.ts | 16 +- .../parts/quickopen/browser/quickOpenModel.ts | 578 ------- .../quickopen/browser/quickOpenViewer.ts | 142 -- .../quickopen/browser/quickOpenWidget.ts | 1041 ------------ .../parts/quickopen/browser/quickopen.css | 169 -- .../base/parts/quickopen/common/quickOpen.ts | 95 -- .../quickopen/test/browser/quickopen.test.ts | 49 - src/vs/base/parts/storage/common/storage.ts | 10 +- src/vs/base/parts/storage/node/storage.ts | 5 +- .../test/browser/actionbar.test.ts} | 7 +- src/vs/base/test/common/codicon.test.ts | 12 + src/vs/base/test/common/fuzzyScorer.test.ts | 73 +- src/vs/code/electron-main/app.ts | 2 +- .../browser/controller/textAreaHandler.ts | 3 + src/vs/editor/browser/editorBrowser.ts | 1 + .../browser/services/codeEditorServiceImpl.ts | 46 +- src/vs/editor/common/model/editStack.ts | 23 +- .../common/services/modelServiceImpl.ts | 33 +- .../documentSymbols/media/outlineTree.css | 4 - .../documentSymbols.ts} | 1 - .../editor/contrib/indentation/indentation.ts | 2 +- .../editorNavigationQuickAccess.ts | 2 +- .../quickAccess/gotoSymbolQuickAccess.ts | 37 +- .../wordHighlighter/wordHighlighter.ts | 13 +- .../standaloneQuickInputServiceImpl.ts | 4 - .../standalone/browser/standalone-tokens.css | 20 - .../browser/standaloneThemeServiceImpl.ts | 2 +- .../test/browser/standaloneLanguages.test.ts | 2 +- .../test/browser/controller/cursor.test.ts | 20 + .../services/decorationRenderOptions.test.ts | 182 +- src/vs/platform/actions/common/actions.ts | 2 +- .../configuration/common/configuration.ts | 8 +- .../common/configurationService.ts | 1 + .../test/node/configurationService.test.ts | 9 +- .../environment/common/environment.ts | 2 +- src/vs/platform/environment/node/argv.ts | 2 +- src/vs/platform/files/common/files.ts | 3 + .../files/node/diskFileSystemProvider.ts | 3 + .../electron-browser/diskFileService.test.ts | 18 + src/vs/platform/product/common/product.ts | 4 +- src/vs/platform/quickOpen/common/quickOpen.ts | 60 - .../quickinput/browser/pickerQuickAccess.ts | 48 +- .../platform/quickinput/browser/quickInput.ts | 6 +- .../platform/quickinput/common/quickInput.ts | 3 - .../storage/browser/storageService.ts | 6 +- src/vs/platform/storage/common/storage.ts | 8 +- src/vs/platform/storage/node/storageIpc.ts | 12 +- .../platform/storage/node/storageService.ts | 5 + src/vs/platform/theme/common/styler.ts | 6 +- src/vs/platform/theme/common/themeService.ts | 2 +- .../common/tokenClassificationRegistry.ts | 161 +- .../theme/test/common/testThemeService.ts | 2 +- src/vs/vscode.proposed.d.ts | 184 +- .../api/browser/mainThreadAuthentication.ts | 104 +- .../api/browser/mainThreadNotebook.ts | 71 +- .../api/browser/mainThreadSaveParticipant.ts | 6 +- .../api/browser/mainThreadTerminalService.ts | 24 +- .../api/browser/mainThreadWebview.ts | 40 +- .../workbench/api/common/extHost.api.impl.ts | 28 +- .../workbench/api/common/extHost.protocol.ts | 15 +- .../api/common/extHostAuthentication.ts | 8 +- .../workbench/api/common/extHostNotebook.ts | 353 ++-- .../api/common/extHostTerminalService.ts | 79 +- src/vs/workbench/api/common/extHostTypes.ts | 41 +- src/vs/workbench/api/common/extHostWebview.ts | 121 +- .../api/common/menusExtensionPoint.ts | 2 + .../api/node/extHostTerminalService.ts | 59 +- src/vs/workbench/browser/actions.ts | 196 --- .../browser/actions/quickAccessActions.ts | 251 +++ .../browser/actions/windowActions.ts | 20 +- src/vs/workbench/browser/contextkeys.ts | 7 +- src/vs/workbench/browser/media/style.css | 11 +- .../parts/activitybar/activitybarPart.ts | 20 +- .../workbench/browser/parts/compositePart.ts | 3 +- .../browser/parts/editor/breadcrumbs.ts | 3 +- .../parts/editor/breadcrumbsControl.ts | 32 +- .../parts/editor/editor.contribution.ts | 132 +- .../workbench/browser/parts/editor/editor.ts | 1 + .../browser/parts/editor/editorActions.ts | 151 +- .../browser/parts/editor/editorCommands.ts | 11 +- .../browser/parts/editor/editorControl.ts | 8 +- .../browser/parts/editor/editorPicker.ts | 265 --- .../browser/parts/editor/editorQuickAccess.ts | 4 - .../browser/parts/editor/editorStatus.ts | 4 +- .../parts/editor/media/editorpicker.css | 8 - .../parts/editor/noTabsTitleControl.ts | 8 +- .../browser/parts/editor/tabsTitleControl.ts | 39 +- .../browser/parts/editor/titleControl.ts | 9 +- .../parts/quickopen/media/quickopen.css | 15 - .../parts/quickopen/quickOpenActions.ts | 137 -- .../parts/quickopen/quickOpenController.ts | 910 ---------- .../browser/parts/quickopen/quickopen.ts | 205 --- .../browser/parts/titlebar/menubarControl.ts | 62 +- .../browser/parts/views/media/views.css | 22 - .../browser/parts/views/viewPaneContainer.ts | 3 +- .../browser/parts/views/viewsViewlet.ts | 33 +- src/vs/workbench/browser/quickaccess.ts | 41 + src/vs/workbench/browser/quickopen.ts | 339 ---- src/vs/workbench/browser/style.ts | 7 - .../browser/workbench.contribution.ts | 15 +- src/vs/workbench/browser/workbench.ts | 2 - src/vs/workbench/common/editor.ts | 1 + .../backup/electron-browser/backupTracker.ts | 20 +- .../inspectEditorTokens.ts | 17 +- .../quickaccess/gotoLineQuickAccess.ts | 33 +- .../quickaccess/gotoSymbolQuickAccess.ts | 37 +- .../codeEditor/browser/saveParticipants.ts | 70 +- .../codeEditor/browser/semanticTokensHelp.ts | 24 +- .../contrib/comments/browser/commentsView.ts | 2 +- .../customEditor/browser/customEditorInput.ts | 48 +- .../browser/customEditorInputFactory.ts | 10 +- .../customEditor/browser/customEditors.ts | 35 +- .../contrib/debug/browser/callStackView.ts | 4 +- .../debug/browser/debug.contribution.ts | 13 - .../contrib/debug/browser/debugActions.ts | 7 +- .../contrib/debug/browser/debugQuickOpen.ts | 145 -- .../contrib/debug/browser/debugToolBar.ts | 1 + .../contrib/debug/browser/rawDebugSession.ts | 2 +- .../workbench/contrib/debug/browser/repl.ts | 3 +- .../workbench/contrib/debug/node/terminals.ts | 2 +- .../debug/test/common/debugUtils.test.ts | 1 + .../browser/actions/showEmmetCommands.ts | 9 +- .../experimentService.test.ts | 3 +- .../browser/extensions.contribution.ts | 25 - .../extensions/browser/extensionsActions.ts | 1 + .../extensions/browser/extensionsList.ts | 31 +- .../extensions/browser/extensionsQuickOpen.ts | 139 -- .../extensions/browser/extensionsViewlet.ts | 9 +- .../extensions/browser/extensionsViews.ts | 10 +- .../browser/media/extensionsViewlet.css | 9 + .../externalTerminal/node/TerminalHelper.scpt | Bin 15672 -> 15244 bytes .../externalTerminal/node/iTermHelper.scpt | Bin 7330 -> 7380 bytes .../contrib/feedback/browser/feedback.ts | 8 + .../feedback/browser/feedbackStatusbarItem.ts | 13 +- .../contrib/files/browser/fileActions.ts | 15 +- .../contrib/logs/common/logs.contribution.ts | 2 +- .../contrib/notebook/browser/constants.ts | 8 +- .../browser/contrib/notebookActions.ts | 4 +- .../notebook/browser/notebook.contribution.ts | 5 - .../contrib/notebook/browser/notebook.css | 2 +- .../notebook/browser/notebookBrowser.ts | 3 +- .../notebook/browser/notebookEditor.ts | 74 +- .../notebook/browser/notebookEditorInput.ts | 23 +- .../notebook/browser/notebookService.ts | 43 +- .../notebook/browser/view/notebookCellList.ts | 129 +- .../view/renderers/backLayerWebView.ts | 18 +- .../browser/view/renderers/cellRenderer.ts | 16 +- .../browser/view/renderers/codeCell.ts | 20 +- .../browser/view/renderers/markdownCell.ts | 150 +- .../browser/viewModel/baseCellViewModel.ts | 4 +- .../browser/viewModel/codeCellViewModel.ts | 33 +- .../viewModel/markdownCellViewModel.ts | 11 +- .../browser/viewModel/notebookViewModel.ts | 31 +- .../common/model/notebookTextModel.ts | 131 +- .../contrib/notebook/common/notebookCommon.ts | 57 +- .../notebook/test/notebookViewModel.test.ts | 2 +- .../notebook/test/testNotebookEditor.ts | 8 +- .../contrib/outline/browser/outlinePane.css | 11 - .../output/browser/output.contribution.ts | 1 + .../contrib/output/browser/outputView.ts | 26 +- .../preferences/browser/media/preferences.css | 20 - .../browser/media/settingsEditor2.css | 17 +- .../browser/preferences.contribution.ts | 1508 +++++++++-------- .../preferences/browser/preferencesActions.ts | 217 +-- .../preferences/browser/settingsEditor2.ts | 92 +- .../preferences/browser/settingsTree.ts | 40 +- .../browser/commandsQuickAccess.ts | 53 +- .../browser/quickAccess.contribution.ts | 128 +- .../quickaccess/browser/viewQuickAccess.ts | 50 +- .../quickopen/browser/commandsHandler.ts | 643 ------- .../quickopen/browser/gotoLineHandler.ts | 343 ---- .../quickopen/browser/gotoSymbolHandler.ts | 559 ------ .../contrib/quickopen/browser/helpHandler.ts | 138 -- .../browser/quickopen.contribution.ts | 194 --- .../quickopen/browser/viewPickerHandler.ts | 249 --- .../contrib/remote/browser/remote.ts | 3 +- .../contrib/scm/browser/repositoryPane.ts | 1 + .../search/browser/anythingQuickAccess.ts | 145 +- .../search/browser/openAnythingHandler.ts | 249 --- .../contrib/search/browser/openFileHandler.ts | 343 ---- .../search/browser/openSymbolHandler.ts | 259 --- .../search/browser/search.contribution.ts | 55 +- .../search/browser/symbolsQuickAccess.ts | 27 +- .../contrib/search/common/searchModel.ts | 2 +- .../tasks/browser/abstractTaskService.ts | 327 ++-- .../tasks/browser/providerProgressManager.ts | 61 - .../contrib/tasks/browser/quickOpen.ts | 232 --- .../tasks/browser/task.contribution.ts | 22 +- .../contrib/tasks/browser/taskQuickOpen.ts | 66 - .../contrib/tasks/browser/taskQuickPick.ts | 257 +++ .../contrib/tasks/browser/tasksQuickAccess.ts | 142 +- .../tasks/browser/terminalTaskSystem.ts | 49 +- .../contrib/tasks/common/taskConfiguration.ts | 35 +- .../contrib/tasks/common/taskService.ts | 5 +- .../contrib/tasks/common/taskSystem.ts | 2 +- .../workbench/contrib/tasks/common/tasks.ts | 45 +- .../terminal/browser/media/terminal.css | 5 + .../terminal/browser/terminal.contribution.ts | 32 +- .../terminal/browser/terminalActions.ts | 90 +- .../terminal/browser/terminalInstance.ts | 13 +- .../browser/terminalProcessManager.ts | 43 +- .../terminal/browser/terminalQuickOpen.ts | 137 -- .../terminal/browser/terminalService.ts | 4 +- .../contrib/terminal/browser/terminalTab.ts | 26 +- .../contrib/terminal/browser/terminalView.ts | 8 +- .../environmentVariable.contribution.ts | 10 + .../terminal/common/environmentVariable.ts | 99 ++ .../common/environmentVariableCollection.ts | 146 ++ .../common/environmentVariableService.ts | 120 ++ .../common/environmentVariableShared.ts | 18 + .../terminal/common/terminalColorRegistry.ts | 8 +- .../environmentVariableCollection.test.ts | 321 ++++ .../common/environmentVariableService.test.ts | 116 ++ .../common/environmentVariableShared.test.ts | 38 + .../userDataSync/browser/userDataSync.ts | 20 +- .../contrib/watermark/browser/watermark.ts | 7 +- .../contrib/webview/browser/pre/index.html | 2 +- .../electron-browser/webviewElement.ts | 16 +- .../welcome/overlay/browser/welcomeOverlay.ts | 2 +- .../services/backup/common/backup.ts | 7 +- .../backup/common/backupFileService.ts | 16 + .../backupFileService.test.ts | 28 + .../configuration/browser/configuration.ts | 34 +- .../browser/configurationService.ts | 49 +- .../configurationService.test.ts | 53 +- .../configurationResolverService.test.ts | 4 - .../services/extensions/node/proxyResolver.ts | 20 +- .../quickinput/browser/quickInputService.ts | 20 +- .../services/search/common/searchExtTypes.ts | 4 +- .../services/search/node/fileSearch.ts | 2 +- .../services/search/node/rawSearchService.ts | 4 +- .../common/textFileEditorModelManager.ts | 4 +- .../common/textFileSaveParticipant.ts | 10 +- .../services/textfile/common/textfiles.ts | 7 +- .../themes/browser/fileIconThemeData.ts | 6 +- .../services/themes/common/colorThemeData.ts | 223 +-- .../themes/common/colorThemeSchema.ts | 6 + .../tokenClassificationExtensionPoint.ts | 7 +- .../tokenStyleResolving.test.ts | 80 +- .../common/workingCopyFileService.ts | 56 +- .../browser/workingCopyFileService.test.ts | 26 + .../test/common/workingCopyService.test.ts | 95 +- .../browser/api/extHostApiCommands.test.ts | 2 +- .../api/extHostLanguageFeatures.test.ts | 2 +- .../workbench/test/browser/quickopen.test.ts | 81 - .../test/browser/workbenchTestServices.ts | 7 +- .../test/common/workbenchTestServices.ts | 3 + .../quickopen.perf.integrationTest.ts | 203 --- src/vs/workbench/workbench.common.main.ts | 11 +- src/vs/workbench/workbench.web.api.ts | 10 +- test/automation/src/application.ts | 2 +- test/automation/src/code.ts | 2 +- test/automation/src/index.ts | 2 +- .../src/{quickopen.ts => quickaccess.ts} | 10 +- test/automation/src/settings.ts | 6 +- test/automation/src/sql/profiler.ts | 4 +- test/automation/src/terminal.ts | 6 +- test/automation/src/workbench.ts | 16 +- test/integration/browser/src/index.ts | 2 +- test/smoke/README.md | 2 +- test/smoke/src/areas/editor/editor.test.ts | 6 +- .../src/areas/extensions/extensions.test.ts | 2 +- .../src/areas/languages/languages.test.ts | 8 +- .../src/areas/multiroot/multiroot.test.ts | 2 +- .../src/areas/preferences/preferences.test.ts | 5 +- test/smoke/src/areas/search/search.test.ts | 10 +- .../src/areas/statusbar/statusbar.test.ts | 10 +- .../src/areas/workbench/data-loss.test.ts | 2 +- .../areas/workbench/data-migration.test.ts | 10 +- .../src/areas/workbench/localization.test.ts | 5 +- test/smoke/src/main.ts | 4 +- .../src/sql/queryEditor/queryEditor.test.ts | 2 +- yarn.lock | 8 +- 316 files changed, 6524 insertions(+), 11687 deletions(-) delete mode 100644 .github/locker.yml delete mode 100644 .github/new_release.yml create mode 100644 .github/workflows/test-plan-item-validator.yml delete mode 100644 src/vs/base/parts/quickinput/browser/media/arrow-left-dark.svg delete mode 100644 src/vs/base/parts/quickinput/browser/media/arrow-left-light.svg delete mode 100644 src/vs/base/parts/quickopen/browser/quickOpenModel.ts delete mode 100644 src/vs/base/parts/quickopen/browser/quickOpenViewer.ts delete mode 100644 src/vs/base/parts/quickopen/browser/quickOpenWidget.ts delete mode 100644 src/vs/base/parts/quickopen/browser/quickopen.css delete mode 100644 src/vs/base/parts/quickopen/common/quickOpen.ts delete mode 100644 src/vs/base/parts/quickopen/test/browser/quickopen.test.ts rename src/vs/{workbench/test/browser/actionRegistry.test.ts => base/test/browser/actionbar.test.ts} (78%) rename src/vs/editor/contrib/{quickOpen/quickOpen.ts => gotoSymbol/documentSymbols.ts} (99%) delete mode 100644 src/vs/platform/quickOpen/common/quickOpen.ts delete mode 100644 src/vs/workbench/browser/actions.ts create mode 100644 src/vs/workbench/browser/actions/quickAccessActions.ts delete mode 100644 src/vs/workbench/browser/parts/editor/editorPicker.ts delete mode 100644 src/vs/workbench/browser/parts/editor/media/editorpicker.css delete mode 100644 src/vs/workbench/browser/parts/quickopen/media/quickopen.css delete mode 100644 src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts delete mode 100644 src/vs/workbench/browser/parts/quickopen/quickOpenController.ts delete mode 100644 src/vs/workbench/browser/parts/quickopen/quickopen.ts create mode 100644 src/vs/workbench/browser/quickaccess.ts delete mode 100644 src/vs/workbench/browser/quickopen.ts delete mode 100644 src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts delete mode 100644 src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts delete mode 100644 src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts delete mode 100644 src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts delete mode 100644 src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts delete mode 100644 src/vs/workbench/contrib/quickopen/browser/helpHandler.ts delete mode 100644 src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts delete mode 100644 src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts delete mode 100644 src/vs/workbench/contrib/search/browser/openAnythingHandler.ts delete mode 100644 src/vs/workbench/contrib/search/browser/openFileHandler.ts delete mode 100644 src/vs/workbench/contrib/search/browser/openSymbolHandler.ts delete mode 100644 src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts delete mode 100644 src/vs/workbench/contrib/tasks/browser/quickOpen.ts delete mode 100644 src/vs/workbench/contrib/tasks/browser/taskQuickOpen.ts create mode 100644 src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts delete mode 100644 src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts create mode 100644 src/vs/workbench/contrib/terminal/common/environmentVariable.contribution.ts create mode 100644 src/vs/workbench/contrib/terminal/common/environmentVariable.ts create mode 100644 src/vs/workbench/contrib/terminal/common/environmentVariableCollection.ts create mode 100644 src/vs/workbench/contrib/terminal/common/environmentVariableService.ts create mode 100644 src/vs/workbench/contrib/terminal/common/environmentVariableShared.ts create mode 100644 src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts create mode 100644 src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts create mode 100644 src/vs/workbench/contrib/terminal/test/common/environmentVariableShared.test.ts delete mode 100644 src/vs/workbench/test/browser/quickopen.test.ts delete mode 100644 src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts rename test/automation/src/{quickopen.ts => quickaccess.ts} (90%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ccd1b65ffc..bd53beca18 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,6 +6,7 @@ labels: '' assignees: '' --- + diff --git a/.github/locker.yml b/.github/locker.yml deleted file mode 100644 index 5f0ded1f36..0000000000 --- a/.github/locker.yml +++ /dev/null @@ -1,6 +0,0 @@ -{ - daysAfterClose: 45, - daysSinceLastUpdate: 3, - ignoredLabels: ['A11y_ADS_OctTestPass', 'A11y_ADS_Schema_Dacpac_Backup', 'A11y_AzureDataStudio', 'A11yExclusion', 'A11yMAS', 'A11yResolved: Will Not Fix', 'A11yTCS'], - perform: true -} diff --git a/.github/new_release.yml b/.github/new_release.yml deleted file mode 100644 index de8617c981..0000000000 --- a/.github/new_release.yml +++ /dev/null @@ -1,6 +0,0 @@ -{ - newReleaseLabel: 'new-release', - newReleaseColor: '006b75', - daysAfterRelease: 5, - perform: true -} diff --git a/.github/workflows/test-plan-item-validator.yml b/.github/workflows/test-plan-item-validator.yml new file mode 100644 index 0000000000..bca963f534 --- /dev/null +++ b/.github/workflows/test-plan-item-validator.yml @@ -0,0 +1,21 @@ +name: Test Plan Item Validator +on: + issues: + types: [edited, labeled] + +jobs: + main: + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'JacksonKearl/vscode-triage-github-actions' + ref: v15 + - name: Run Test Plan Item Validator + uses: ./test-plan-item-validator + with: + token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} + label: testplan-item + invalidLabel: invalid-testplan-item + comment: Invalid test plan item. See errors below and the [test plan item spec](https://github.com/microsoft/vscode/wiki/Writing-Test-Plan-Items) for more information. This comment will go away when the issues are resolved. diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 8f739c92c0..af59df1239 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -123,7 +123,6 @@ const optimizeVSCodeTask = task.define('optimize-vscode', task.series( resources: vscodeResources, loaderConfig: common.loaderConfig(nodeModules), out: 'out-vscode', - inlineAmdImages: true, bundleInfo: undefined }) )); diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 835cee2d26..dd2b3cd1d6 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -126,10 +126,6 @@ "name": "vs/workbench/contrib/quickaccess", "project": "vscode-workbench" }, - { - "name": "vs/workbench/contrib/quickopen", - "project": "vscode-workbench" - }, { "name": "vs/workbench/contrib/userData", "project": "vscode-workbench" diff --git a/build/lib/optimize.js b/build/lib/optimize.js index 11a768f361..a4f399031e 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.minifyTask = exports.optimizeTask = exports.loaderConfig = void 0; const es = require("event-stream"); -const fs = require("fs"); const gulp = require("gulp"); const concat = require("gulp-concat"); const minifyCSS = require("gulp-cssnano"); @@ -136,14 +135,6 @@ function optimizeTask(opts) { if (err || !result) { return bundlesStream.emit('error', JSON.stringify(err)); } - if (opts.inlineAmdImages) { - try { - result = inlineAmdImages(src, result); - } - catch (err) { - return bundlesStream.emit('error', JSON.stringify(err)); - } - } toBundleStream(src, bundledFileHeader, result.files).pipe(bundlesStream); // Remove css inlined resources const filteredResources = resources.slice(); @@ -179,39 +170,6 @@ function optimizeTask(opts) { }; } exports.optimizeTask = optimizeTask; -function inlineAmdImages(src, result) { - for (const outputFile of result.files) { - for (const sourceFile of outputFile.sources) { - if (sourceFile.path && /\.js$/.test(sourceFile.path)) { - sourceFile.contents = sourceFile.contents.replace(/\([^.]+\.registerAndGetAmdImageURL\(([^)]+)\)\)/g, (_, m0) => { - let imagePath = m0; - // remove `` or '' - if ((imagePath.charAt(0) === '`' && imagePath.charAt(imagePath.length - 1) === '`') - || (imagePath.charAt(0) === '\'' && imagePath.charAt(imagePath.length - 1) === '\'')) { - imagePath = imagePath.substr(1, imagePath.length - 2); - } - if (!/\.(png|svg)$/.test(imagePath)) { - console.log(`original: ${_}`); - return _; - } - const repoLocation = path.join(src, imagePath); - const absoluteLocation = path.join(REPO_ROOT_PATH, repoLocation); - if (!fs.existsSync(absoluteLocation)) { - const message = `Invalid amd image url in file ${sourceFile.path}: ${imagePath}`; - console.log(message); - throw new Error(message); - } - const fileContents = fs.readFileSync(absoluteLocation); - const mime = /\.svg$/.test(imagePath) ? 'image/svg+xml' : 'image/png'; - // Mark the file as inlined so we don't ship it by itself - result.cssInlinedResources.push(repoLocation); - return `("data:${mime};base64,${fileContents.toString('base64')}")`; - }); - } - } - } - return result; -} /** * Wrap around uglify and allow the preserveComments function * to have a file "context" to include our copyright only once per file. diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 1f8af4c62a..f58283f461 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -6,7 +6,6 @@ 'use strict'; import * as es from 'event-stream'; -import * as fs from 'fs'; import * as gulp from 'gulp'; import * as concat from 'gulp-concat'; import * as minifyCSS from 'gulp-cssnano'; @@ -162,10 +161,6 @@ export interface IOptimizeTaskOpts { * (emit bundleInfo.json file) */ bundleInfo: boolean; - /** - * replace calls to `registerAndGetAmdImageURL` with data uris - */ - inlineAmdImages: boolean; /** * (out folder name) */ @@ -199,14 +194,6 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr bundle.bundle(entryPoints, loaderConfig, function (err, result) { if (err || !result) { return bundlesStream.emit('error', JSON.stringify(err)); } - if (opts.inlineAmdImages) { - try { - result = inlineAmdImages(src, result); - } catch (err) { - return bundlesStream.emit('error', JSON.stringify(err)); - } - } - toBundleStream(src, bundledFileHeader, result.files).pipe(bundlesStream); // Remove css inlined resources @@ -251,42 +238,6 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr }; } -function inlineAmdImages(src: string, result: bundle.IBundleResult): bundle.IBundleResult { - for (const outputFile of result.files) { - for (const sourceFile of outputFile.sources) { - if (sourceFile.path && /\.js$/.test(sourceFile.path)) { - sourceFile.contents = sourceFile.contents.replace(/\([^.]+\.registerAndGetAmdImageURL\(([^)]+)\)\)/g, (_, m0) => { - let imagePath = m0; - // remove `` or '' - if ((imagePath.charAt(0) === '`' && imagePath.charAt(imagePath.length - 1) === '`') - || (imagePath.charAt(0) === '\'' && imagePath.charAt(imagePath.length - 1) === '\'')) { - imagePath = imagePath.substr(1, imagePath.length - 2); - } - if (!/\.(png|svg)$/.test(imagePath)) { - console.log(`original: ${_}`); - return _; - } - const repoLocation = path.join(src, imagePath); - const absoluteLocation = path.join(REPO_ROOT_PATH, repoLocation); - if (!fs.existsSync(absoluteLocation)) { - const message = `Invalid amd image url in file ${sourceFile.path}: ${imagePath}`; - console.log(message); - throw new Error(message); - } - const fileContents = fs.readFileSync(absoluteLocation); - const mime = /\.svg$/.test(imagePath) ? 'image/svg+xml' : 'image/png'; - - // Mark the file as inlined so we don't ship it by itself - result.cssInlinedResources.push(repoLocation); - - return `("data:${mime};base64,${fileContents.toString('base64')}")`; - }); - } - } - } - return result; -} - declare class FileWithCopyright extends VinylFile { public __hasOurCopyright: boolean; } diff --git a/build/package.json b/build/package.json index 0391611bf5..919f97a342 100644 --- a/build/package.json +++ b/build/package.json @@ -49,7 +49,7 @@ "rollup-plugin-node-resolve": "^5.2.0", "service-downloader": "0.2.1", "terser": "4.3.8", - "typescript": "^3.9.0-dev.20200316", + "typescript": "^3.9.0-dev.20200327", "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 bf871725cc..96ceca1441 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3600,10 +3600,10 @@ typescript@^3.0.1: 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.20200316: - version "3.9.0-dev.20200316" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200316.tgz#7cbb2fa2eedf58eea27b3250ab38674790ccf999" - integrity sha512-MM67isAuvHM4hwfHR4K9NikB7MFD9RjISB5cXhtKmjkpMFO0QNzFmFq061VmsJqoRVpG9N2KE+cm6BJ9dIjrtQ== +typescript@^3.9.0-dev.20200327: + version "3.9.0-dev.20200327" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200327.tgz#52179aae816587f772a0526e91143760f2bee42f" + integrity sha512-/TWD/zPvhAcN2Toqx2NBQ+oDVGVj4iqupjWcUAwL45TfcODeHpzszneABR1b/EjHbtUObtLH40vy5Z6rdVvKzg== typical@^4.0.0: version "4.0.0" diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts index 0ec2c15cbc..3c41a77f21 100644 --- a/extensions/github-authentication/src/extension.ts +++ b/extensions/github-authentication/src/extension.ts @@ -24,6 +24,7 @@ export async function activate(context: vscode.ExtensionContext) { try { const session = await loginService.login(scopeList.join(' ')); Logger.info('Login success!'); + onDidChangeSessions.fire({ added: [session.id], removed: [], changed: [] }); return session; } catch (e) { vscode.window.showErrorMessage(`Sign in failed: ${e}`); @@ -32,7 +33,8 @@ export async function activate(context: vscode.ExtensionContext) { } }, logout: async (id: string) => { - return loginService.logout(id); + await loginService.logout(id); + onDidChangeSessions.fire({ added: [], removed: [id], changed: [] }); } }); diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index bb9a590348..002e4b6d98 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -142,7 +142,9 @@ export class GitHubAuthenticationProvider { public async logout(id: string) { const sessionIndex = this._sessions.findIndex(session => session.id === id); if (sessionIndex > -1) { - this._sessions.splice(sessionIndex, 1); + const session = this._sessions.splice(sessionIndex, 1)[0]; + const token = await session.getAccessToken(); + await this._githubServer.revokeToken(token); } this.storeSessions(); diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index efceb637a8..8eb54dcb30 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -156,4 +156,46 @@ export class GitHubServer { }); }); } + + public async revokeToken(token: string): Promise { + return new Promise(async (resolve, reject) => { + const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate`)); + const clientDetails = ClientRegistrar.getClientDetails(callbackUri); + const detailsString = `${clientDetails.id}:${clientDetails.secret}`; + + const payload = JSON.stringify({ access_token: token }); + + Logger.info('Revoking token...'); + const post = https.request({ + host: 'api.github.com', + path: `/applications/${clientDetails.id}/token`, + method: 'DELETE', + headers: { + Authorization: `Basic ${Buffer.from(detailsString).toString('base64')}`, + 'User-Agent': 'Visual-Studio-Code', + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(payload) + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 204) { + Logger.info('Revoked token!'); + resolve(); + } else { + reject(new Error(result.statusMessage)); + } + }); + }); + + post.write(payload); + post.end(); + post.on('error', err => { + reject(err); + }); + }); + } } diff --git a/extensions/merge-conflict/src/documentTracker.ts b/extensions/merge-conflict/src/documentTracker.ts index 9f01ee7375..d8eef96aee 100644 --- a/extensions/merge-conflict/src/documentTracker.ts +++ b/extensions/merge-conflict/src/documentTracker.ts @@ -49,7 +49,7 @@ class OriginDocumentMergeConflictTracker implements interfaces.IDocumentMergeCon export default class DocumentMergeConflictTracker implements vscode.Disposable, interfaces.IDocumentMergeConflictTrackerService { private cache: Map = new Map(); - private delayExpireTime: number = 250; + private delayExpireTime: number = 0; getConflicts(document: vscode.TextDocument, origin: string): PromiseLike { // Attempt from cache diff --git a/extensions/theme-red/themes/Red-color-theme.json b/extensions/theme-red/themes/Red-color-theme.json index 8ebbf48c22..8964f40a09 100644 --- a/extensions/theme-red/themes/Red-color-theme.json +++ b/extensions/theme-red/themes/Red-color-theme.json @@ -192,7 +192,7 @@ }, { "name": "Support.constant", - "scope": "support.constant", + "scope": [ "support.constant", "support.variable"], "settings": { "fontStyle": "", "foreground": "#eb939aff" 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 2c1f501d85..21f530d00a 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -182,7 +182,10 @@ }, { "name": "Library constant", - "scope": "support.constant", + "scope": [ + "support.constant", + "support.variable" + ], "settings": {} }, { diff --git a/package.json b/package.json index a07c115a3e..79df1d81f0 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.20200316", + "typescript": "^3.9.0-dev.20200327", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActionProvider.ts b/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActionProvider.ts index 399a4ff40b..d00b823141 100644 --- a/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActionProvider.ts +++ b/src/sql/workbench/contrib/queryHistory/browser/queryHistoryActionProvider.ts @@ -5,7 +5,6 @@ import { DeleteAction, OpenQueryAction, RunQueryAction, ClearHistoryAction, ToggleQueryHistoryCaptureAction } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryActions'; import { ITree } from 'vs/base/parts/tree/browser/tree'; -import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { IAction } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { QueryHistoryNode } from 'sql/workbench/contrib/queryHistory/browser/queryHistoryNode'; @@ -13,7 +12,7 @@ import { QueryHistoryNode } from 'sql/workbench/contrib/queryHistory/browser/que /** * Provides query history actions */ -export class QueryHistoryActionProvider extends ContributableActionProvider { +export class QueryHistoryActionProvider { private _actions: { openQueryAction: IAction, @@ -26,7 +25,6 @@ export class QueryHistoryActionProvider extends ContributableActionProvider { constructor( @IInstantiationService instantiationService: IInstantiationService ) { - super(); this._actions = { openQueryAction: instantiationService.createInstance(OpenQueryAction, OpenQueryAction.ID, OpenQueryAction.LABEL), runQueryAction: instantiationService.createInstance(RunQueryAction, RunQueryAction.ID, RunQueryAction.LABEL), diff --git a/src/sql/workbench/contrib/tasks/browser/tasksActionProvider.ts b/src/sql/workbench/contrib/tasks/browser/tasksActionProvider.ts index b855a20295..88f9489798 100644 --- a/src/sql/workbench/contrib/tasks/browser/tasksActionProvider.ts +++ b/src/sql/workbench/contrib/tasks/browser/tasksActionProvider.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { ITree } from 'vs/base/parts/tree/browser/tree'; -import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { IAction } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TaskNode, TaskStatus, TaskExecutionMode } from 'sql/workbench/services/tasks/common/tasksNode'; @@ -13,12 +12,11 @@ import { CancelAction, ScriptAction } from 'sql/workbench/contrib/tasks/common/t /** * Provides actions for the history tasks */ -export class TaskHistoryActionProvider extends ContributableActionProvider { +export class TaskHistoryActionProvider { constructor( @IInstantiationService private _instantiationService: IInstantiationService ) { - super(); } public hasActions(tree: ITree, element: any): boolean { diff --git a/src/sql/workbench/services/connection/browser/recentConnectionTreeController.ts b/src/sql/workbench/services/connection/browser/recentConnectionTreeController.ts index 06654684e8..8f336d739b 100644 --- a/src/sql/workbench/services/connection/browser/recentConnectionTreeController.ts +++ b/src/sql/workbench/services/connection/browser/recentConnectionTreeController.ts @@ -8,7 +8,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ITree } from 'vs/base/parts/tree/browser/tree'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ClearSingleRecentConnectionAction } from 'sql/workbench/services/connection/browser/connectionActions'; -import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; @@ -17,14 +16,13 @@ import { Event, Emitter } from 'vs/base/common/event'; import mouse = require('vs/base/browser/mouseEvent'); import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; -export class RecentConnectionActionsProvider extends ContributableActionProvider { +export class RecentConnectionActionsProvider { private _onRecentConnectionRemoved = new Emitter(); public onRecentConnectionRemoved: Event = this._onRecentConnectionRemoved.event; constructor( @IInstantiationService private _instantiationService: IInstantiationService ) { - super(); } private getRecentConnectionActions(tree: ITree, element: any): IAction[] { diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts index 82777d130b..160f59f225 100644 --- a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts +++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { ITree } from 'vs/base/parts/tree/browser/tree'; -import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { IAction } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -31,7 +30,7 @@ import { firstIndex, find } from 'vs/base/common/arrays'; /** * Provides actions for the server tree elements */ -export class ServerTreeActionProvider extends ContributableActionProvider { +export class ServerTreeActionProvider { constructor( @IInstantiationService private _instantiationService: IInstantiationService, @@ -40,7 +39,6 @@ export class ServerTreeActionProvider extends ContributableActionProvider { @IMenuService private menuService: IMenuService, @IContextKeyService private _contextKeyService: IContextKeyService ) { - super(); } public hasActions(tree: ITree, element: any): boolean { diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 1e169f96b3..825a83761f 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -22,8 +22,6 @@ "vs/editor/*", "vs/base/common/*", "vs/base/browser/*", - "vs/base/parts/tree/*", - "vs/base/parts/quickopen/*", "vs/platform/*/common/*", "vs/platform/*/browser/*" ], diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 115b053e77..b88982703d 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -134,6 +134,18 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } })); + if (platform.isMacintosh) { + // macOS: allow to trigger the button when holding Ctrl+key and pressing the + // main mouse button. This is for scenarios where e.g. some interaction forces + // the Ctrl+key to be pressed and hold but the user still wants to interact + // with the actions (for example quick access in quick navigation mode). + this._register(DOM.addDisposableListener(element, DOM.EventType.CONTEXT_MENU, e => { + if (e.button === 0 && e.ctrlKey === true) { + this.onClick(e); + } + })); + } + this._register(DOM.addDisposableListener(element, DOM.EventType.CLICK, e => { DOM.EventHelper.stop(e, true); // See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard @@ -633,8 +645,7 @@ export class ActionBar extends Disposable implements IActionRunner { // Prevent native context menu on actions this._register(DOM.addDisposableListener(actionViewItemElement, DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => { - e.preventDefault(); - e.stopPropagation(); + DOM.EventHelper.stop(e, true); })); let item: IActionViewItem | undefined; @@ -873,3 +884,51 @@ export class SelectActionViewItem extends BaseActionViewItem { this.selectBox.render(container); } } + +export function prepareActions(actions: IAction[]): IAction[] { + if (!actions.length) { + return actions; + } + + // Clean up leading separators + let firstIndexOfAction = -1; + for (let i = 0; i < actions.length; i++) { + if (actions[i].id === Separator.ID) { + continue; + } + + firstIndexOfAction = i; + break; + } + + if (firstIndexOfAction === -1) { + return []; + } + + actions = actions.slice(firstIndexOfAction); + + // Clean up trailing separators + for (let h = actions.length - 1; h >= 0; h--) { + const isSeparator = actions[h].id === Separator.ID; + if (isSeparator) { + actions.splice(h, 1); + } else { + break; + } + } + + // Clean up separator duplicates + let foundAction = false; + for (let k = actions.length - 1; k >= 0; k--) { + const isSeparator = actions[k].id === Separator.ID; + if (isSeparator && !foundAction) { + actions.splice(k, 1); + } else if (!isSeparator) { + foundAction = true; + } else if (isSeparator) { + foundAction = false; + } + } + + return actions; +} diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index ce2d9bf1c5..f8828fa3f0 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -81,7 +81,8 @@ export class BreadcrumbsWidget { private _dimension: dom.Dimension | undefined; constructor( - container: HTMLElement + container: HTMLElement, + horizontalScrollbarSize: number, ) { this._domNode = document.createElement('div'); this._domNode.className = 'monaco-breadcrumbs'; @@ -90,7 +91,7 @@ export class BreadcrumbsWidget { this._scrollable = new DomScrollableElement(this._domNode, { vertical: ScrollbarVisibility.Hidden, horizontal: ScrollbarVisibility.Auto, - horizontalScrollbarSize: 3, + horizontalScrollbarSize, useShadows: false, scrollYToX: true }); @@ -106,6 +107,12 @@ export class BreadcrumbsWidget { this._disposables.add(focusTracker.onDidFocus(_ => this._onDidChangeFocus.fire(true))); } + setHorizontalScrollbarSize(size: number) { + this._scrollable.updateOptions({ + horizontalScrollbarSize: size + }); + } + dispose(): void { this._disposables.dispose(); dispose(this._pendingLayout); diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index c42ca480c3..9a8768a297 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -202,7 +202,7 @@ class Label { const l = label[i]; const id = options?.domId && `${options?.domId}_${i}`; - dom.append(this.container, dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i }, l)); + dom.append(this.container, dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }, l)); if (i < label.length - 1) { dom.append(this.container, dom.$('span.label-separator', undefined, options?.separator || '/')); @@ -270,7 +270,7 @@ class LabelWithHighlights { const m = matches ? matches[i] : undefined; const id = options?.domId && `${options?.domId}_${i}`; - const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i }); + const name = dom.$('a.label-name', { id, 'data-icon-label-count': label.length, 'data-icon-label-index': i, 'role': 'treeitem' }); const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportCodicons); highlightedLabel.set(l, m, options?.title, options?.labelEscapeNewLines); diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index d833beb9de..3ba7eb28d0 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -74,16 +74,12 @@ } /* make sure selection color wins when a label is being selected */ -.monaco-tree.focused .selected .monaco-icon-label, /* tree */ -.monaco-tree.focused .selected .monaco-icon-label::after, .monaco-list:focus .selected .monaco-icon-label, /* list */ .monaco-list:focus .selected .monaco-icon-label::after { color: inherit !important; } -.monaco-tree-row.focused.selected .label-description, -.monaco-tree-row.selected .label-description, .monaco-list-row.focused.selected .label-description, .monaco-list-row.selected .label-description { opacity: .8; diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 4740afe544..55171fc9d6 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -32,6 +32,7 @@ export interface IMenuBarOptions { getKeybinding?: (action: IAction) => ResolvedKeybinding | undefined; alwaysOnMnemonics?: boolean; compactMode?: Direction; + getCompactMenuActions?: () => IAction[] } export interface MenuBarMenu { @@ -91,7 +92,7 @@ export class MenuBar extends Disposable { private menuStyle: IMenuStyles | undefined; private overflowLayoutScheduled: IDisposable | undefined = undefined; - constructor(private container: HTMLElement, private options: IMenuBarOptions = {}, private compactMenuActions?: IAction[]) { + constructor(private container: HTMLElement, private options: IMenuBarOptions = {}) { super(); this.container.setAttribute('role', 'menubar'); @@ -492,9 +493,10 @@ export class MenuBar extends Disposable { this.overflowMenu.buttonElement.style.visibility = 'visible'; } - if (this.compactMenuActions && this.compactMenuActions.length) { + const compactMenuActions = this.options.getCompactMenuActions?.(); + if (compactMenuActions && compactMenuActions.length) { this.overflowMenu.actions.push(new Separator()); - this.overflowMenu.actions.push(...this.compactMenuActions); + this.overflowMenu.actions.push(...compactMenuActions); } } else { DOM.removeNode(this.overflowMenu.buttonElement); diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts index 3575bad090..8398e34c1f 100644 --- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts @@ -259,6 +259,15 @@ export abstract class AbstractScrollbar extends Widget { this._scrollable.setScrollPositionNow(desiredScrollPosition); } + public updateScrollbarSize(scrollbarSize: number): void { + this._updateScrollbarSize(scrollbarSize); + this._scrollbarState.setScrollbarSize(scrollbarSize); + this._shouldRender = true; + if (!this._lazyRender) { + this.render(); + } + } + // ----------------- Overwrite these protected abstract _renderDomNode(largeSize: number, smallSize: number): void; @@ -267,6 +276,7 @@ export abstract class AbstractScrollbar extends Widget { protected abstract _mouseDownRelativePosition(offsetX: number, offsetY: number): number; protected abstract _sliderMousePosition(e: ISimplifiedMouseEvent): number; protected abstract _sliderOrthogonalMousePosition(e: ISimplifiedMouseEvent): number; + protected abstract _updateScrollbarSize(size: number): void; public abstract writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void; } diff --git a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts index cc38e67f17..074733483c 100644 --- a/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts @@ -92,6 +92,10 @@ export class HorizontalScrollbar extends AbstractScrollbar { return e.posy; } + protected _updateScrollbarSize(size: number): void { + this.slider.setHeight(size); + } + public writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void { target.scrollLeft = scrollPosition; } diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index d79f69c41e..f0d3f0d4c8 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -287,12 +287,22 @@ export abstract class AbstractScrollableElement extends Widget { * depend on Editor. */ public updateOptions(newOptions: ScrollableElementChangeOptions): void { - let massagedOptions = resolveOptions(newOptions); - this._options.handleMouseWheel = massagedOptions.handleMouseWheel; - this._options.mouseWheelScrollSensitivity = massagedOptions.mouseWheelScrollSensitivity; - this._options.fastScrollSensitivity = massagedOptions.fastScrollSensitivity; - this._options.scrollPredominantAxis = massagedOptions.scrollPredominantAxis; - this._setListeningToMouseWheel(this._options.handleMouseWheel); + if (typeof newOptions.handleMouseWheel !== 'undefined') { + this._options.handleMouseWheel = newOptions.handleMouseWheel; + this._setListeningToMouseWheel(this._options.handleMouseWheel); + } + if (typeof newOptions.mouseWheelScrollSensitivity !== 'undefined') { + this._options.mouseWheelScrollSensitivity = newOptions.mouseWheelScrollSensitivity; + } + if (typeof newOptions.fastScrollSensitivity !== 'undefined') { + this._options.fastScrollSensitivity = newOptions.fastScrollSensitivity; + } + if (typeof newOptions.scrollPredominantAxis !== 'undefined') { + this._options.scrollPredominantAxis = newOptions.scrollPredominantAxis; + } + if (typeof newOptions.horizontalScrollbarSize !== 'undefined') { + this._horizontalScrollbar.updateScrollbarSize(newOptions.horizontalScrollbarSize); + } if (!this._options.lazyRender) { this._render(); diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts index 10449df397..2e02a137d5 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts @@ -119,8 +119,9 @@ export interface ScrollableElementCreationOptions { export interface ScrollableElementChangeOptions { handleMouseWheel?: boolean; mouseWheelScrollSensitivity?: number; - fastScrollSensitivity: number; - scrollPredominantAxis: boolean; + fastScrollSensitivity?: number; + scrollPredominantAxis?: boolean; + horizontalScrollbarSize?: number; } export interface ScrollableElementResolvedOptions { diff --git a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts index 3c82b08ba8..46479ee209 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollbarState.ts @@ -14,7 +14,7 @@ export class ScrollbarState { * For the vertical scrollbar: the width. * For the horizontal scrollbar: the height. */ - private readonly _scrollbarSize: number; + private _scrollbarSize: number; /** * For the vertical scrollbar: the height of the pair horizontal scrollbar. @@ -114,6 +114,10 @@ export class ScrollbarState { return false; } + public setScrollbarSize(scrollbarSize: number): void { + this._scrollbarSize = scrollbarSize; + } + private static _computeValues(oppositeScrollbarSize: number, arrowSize: number, visibleSize: number, scrollSize: number, scrollPosition: number) { const computedAvailableSize = Math.max(0, visibleSize - oppositeScrollbarSize); const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * arrowSize); diff --git a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts index 297ef8dab6..269d104660 100644 --- a/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts @@ -93,6 +93,10 @@ export class VerticalScrollbar extends AbstractScrollbar { return e.posx; } + protected _updateScrollbarSize(size: number): void { + this.slider.setWidth(size); + } + public writeScrollPosition(target: INewScrollPosition, scrollPosition: number): void { target.scrollTop = scrollPosition; } diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 13ea6c26b8..2bb4a0ddd9 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -214,7 +214,11 @@ export abstract class Pane extends Disposable implements IView { .event(() => this.setExpanded(true), null)); this._register(domEvent(this.header, 'click') - (() => this.setExpanded(!this.isExpanded()), null)); + (e => { + if (!e.defaultPrevented) { + this.setExpanded(!this.isExpanded()); + } + }, null)); this.body = append(this.element, $('.pane-body')); this.renderBody(this.body); diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index d60eab1c22..b247a9fcdd 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -269,7 +269,7 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt return options.ariaProvider!.getRole!(el.element as T); } : () => 'treeitem', isChecked: options.ariaProvider!.isChecked ? (e) => { - return options.ariaProvider?.isChecked!(e.element as T); + return !!(options.ariaProvider?.isChecked!(e.element as T)); } : undefined }, ariaRole: ListAriaRootRole.TREE, diff --git a/src/vs/base/browser/ui/tree/treeDefaults.ts b/src/vs/base/browser/ui/tree/treeDefaults.ts index e913dc579a..b25ac6b433 100644 --- a/src/vs/base/browser/ui/tree/treeDefaults.ts +++ b/src/vs/base/browser/ui/tree/treeDefaults.ts @@ -10,16 +10,14 @@ import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; export class CollapseAllAction extends Action { constructor(private viewer: AsyncDataTree, enabled: boolean) { - super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'monaco-tree-action collapse-all', enabled); + super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'collapse-all', enabled); } - public run(context?: any): Promise { + async run(context?: any): Promise { this.viewer.collapseAll(); this.viewer.setSelection([]); this.viewer.setFocus([]); this.viewer.domFocus(); this.viewer.focusFirst(); - - return Promise.resolve(); } } diff --git a/src/vs/base/common/amd.ts b/src/vs/base/common/amd.ts index f8e89666ed..6dc02ef9cb 100644 --- a/src/vs/base/common/amd.ts +++ b/src/vs/base/common/amd.ts @@ -12,12 +12,3 @@ export function getPathFromAmdModule(requirefn: typeof require, relativePath: st export function getUriFromAmdModule(requirefn: typeof require, relativePath: string): URI { return URI.parse(requirefn.toUrl(relativePath)); } - -/** - * Reference a resource that might be inlined. - * Do not inline icons that will be used by the native mac touchbar. - * Do not rename this method unless you adopt the build scripts. - */ -export function registerAndGetAmdImageURL(absolutePath: string): string { - return require.toUrl(absolutePath); -} diff --git a/src/vs/base/common/codicon.ts b/src/vs/base/common/codicon.ts index 5fcb356aa4..e2668dbd06 100644 --- a/src/vs/base/common/codicon.ts +++ b/src/vs/base/common/codicon.ts @@ -6,7 +6,7 @@ import { matchesFuzzy, IMatch } from 'vs/base/common/filters'; import { ltrim } from 'vs/base/common/strings'; -const codiconStartMarker = '$('; +export const codiconStartMarker = '$('; export interface IParsedCodicons { readonly text: string; diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 8d64acf3f5..b7c5b4e594 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -3,6 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { codiconStartMarker } from 'vs/base/common/codicon'; + const escapeCodiconsRegex = /(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi; export function escapeCodicons(text: string): string { return text.replace(escapeCodiconsRegex, (match, escaped) => escaped ? match : `\\${match}`); @@ -30,5 +32,9 @@ export function renderCodicons(text: string): string { const stripCodiconsRegex = /(\s)?(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)(\s)?/gi; export function stripCodicons(text: string): string { + if (text.indexOf(codiconStartMarker) === -1) { + return text; + } + return text.replace(stripCodiconsRegex, (match, preWhitespace, escaped, postWhitespace) => escaped ? match : preWhitespace || postWhitespace || ''); } diff --git a/src/vs/base/common/fuzzyScorer.ts b/src/vs/base/common/fuzzyScorer.ts index 6c72d39c92..74485f0bee 100644 --- a/src/vs/base/common/fuzzyScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -9,6 +9,7 @@ import { sep } from 'vs/base/common/path'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; +import { distinctES6 } from 'vs/base/common/arrays'; export type Score = [number /* score */, number[] /* match positions */]; export type ScorerCache = { [key: string]: IItemScore }; @@ -19,7 +20,40 @@ const NO_SCORE: Score = [NO_MATCH, []]; // const DEBUG = false; // const DEBUG_MATRIX = false; -export function score(target: string, query: string, queryLower: string, fuzzy: boolean): Score { +export function score(target: string, query: IPreparedQuery, fuzzy: boolean): Score { + if (query.values && query.values.length > 1) { + return scoreMultiple(target, query.values, fuzzy); + } + + return scoreSingle(target, query.value, query.valueLowercase, fuzzy); +} + +function scoreMultiple(target: string, query: IPreparedQueryPiece[], fuzzy: boolean): Score { + let totalScore = NO_MATCH; + const totalPositions: number[] = []; + + for (const { value, valueLowercase } of query) { + const [scoreValue, positions] = scoreSingle(target, value, valueLowercase, fuzzy); + if (scoreValue === NO_MATCH) { + // if a single query value does not match, return with + // no score entirely, we require all queries to match + return NO_SCORE; + } + + totalScore += scoreValue; + totalPositions.push(...positions); + } + + if (totalScore === NO_MATCH) { + return NO_SCORE; + } + + // if we have a score, ensure that the positions are + // sorted in ascending order and distinct + return [totalScore, distinctES6(totalPositions).sort((a, b) => a - b)]; +} + +function scoreSingle(target: string, query: string, queryLower: string, fuzzy: boolean): Score { if (!target || !query) { return NO_SCORE; // return early if target or query are undefined } @@ -303,21 +337,62 @@ const LABEL_PREFIX_SCORE = 1 << 17; const LABEL_CAMELCASE_SCORE = 1 << 16; const LABEL_SCORE_THRESHOLD = 1 << 15; -export interface IPreparedQuery { +export interface IPreparedQueryPiece { original: string; + originalLowercase: string; + value: string; - lowercase: string; + valueLowercase: string; +} + +export interface IPreparedQuery extends IPreparedQueryPiece { + + // Split by spaces + values: IPreparedQueryPiece[] | undefined; + containsPathSeparator: boolean; } /** - * Helper function to prepare a search value for scoring by removing unwanted characters. + * Helper function to prepare a search value for scoring by removing unwanted characters + * and allowing to score on multiple pieces separated by whitespace character. */ +const MULTIPL_QUERY_VALUES_SEPARATOR = ' '; export function prepareQuery(original: string): IPreparedQuery { - if (!original) { + if (typeof original !== 'string') { original = ''; } + const originalLowercase = original.toLowerCase(); + const value = prepareQueryValue(original); + const valueLowercase = value.toLowerCase(); + const containsPathSeparator = value.indexOf(sep) >= 0; + + let values: IPreparedQueryPiece[] | undefined = undefined; + + const originalSplit = original.split(MULTIPL_QUERY_VALUES_SEPARATOR); + if (originalSplit.length > 1) { + for (const originalPiece of originalSplit) { + const valuePiece = prepareQueryValue(originalPiece); + if (valuePiece) { + if (!values) { + values = []; + } + + values.push({ + original: originalPiece, + originalLowercase: originalPiece.toLowerCase(), + value: valuePiece, + valueLowercase: valuePiece.toLowerCase() + }); + } + } + } + + return { original, originalLowercase, value, valueLowercase, values, containsPathSeparator }; +} + +function prepareQueryValue(original: string): string { let value = stripWildcards(original).replace(/\s/g, ''); // get rid of all wildcards and whitespace if (isWindows) { value = value.replace(/\//g, sep); // Help Windows users to search for paths when using slash @@ -325,10 +400,7 @@ export function prepareQuery(original: string): IPreparedQuery { value = value.replace(/\\/g, sep); // Help macOS/Linux users to search for paths when using backslash } - const lowercase = value.toLowerCase(); - const containsPathSeparator = value.indexOf(sep) >= 0; - - return { original, value, lowercase, containsPathSeparator }; + return value; } export function scoreItem(item: T, query: IPreparedQuery, fuzzy: boolean, accessor: IItemAccessor, cache: ScorerCache): IItemScore { @@ -404,7 +476,7 @@ function doScoreItem(label: string, description: string | undefined, path: strin } // 4.) prefer scores on the label if any - const [labelScore, labelPositions] = score(label, query.value, query.lowercase, fuzzy); + const [labelScore, labelPositions] = score(label, query, fuzzy); if (labelScore) { return { score: labelScore + LABEL_SCORE_THRESHOLD, labelMatch: createMatches(labelPositions) }; } @@ -420,7 +492,7 @@ function doScoreItem(label: string, description: string | undefined, path: strin const descriptionPrefixLength = descriptionPrefix.length; const descriptionAndLabel = `${descriptionPrefix}${label}`; - const [labelDescriptionScore, labelDescriptionPositions] = score(descriptionAndLabel, query.value, query.lowercase, fuzzy); + const [labelDescriptionScore, labelDescriptionPositions] = score(descriptionAndLabel, query, fuzzy); if (labelDescriptionScore) { const labelDescriptionMatches = createMatches(labelDescriptionPositions); const labelMatch: IMatch[] = []; diff --git a/src/vs/base/parts/quickinput/browser/media/arrow-left-dark.svg b/src/vs/base/parts/quickinput/browser/media/arrow-left-dark.svg deleted file mode 100644 index 0d24c218ae..0000000000 --- a/src/vs/base/parts/quickinput/browser/media/arrow-left-dark.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - arrow-left - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/src/vs/base/parts/quickinput/browser/media/arrow-left-light.svg b/src/vs/base/parts/quickinput/browser/media/arrow-left-light.svg deleted file mode 100644 index b8362b2745..0000000000 --- a/src/vs/base/parts/quickinput/browser/media/arrow-left-light.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - arrow-left - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 65ecf5b815..03aa842674 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -794,7 +794,7 @@ class QuickPick extends QuickInput implements IQuickPi if (firstPart.shiftKey && keyCode === KeyCode.Shift) { if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) { - return false; // this is an optimistic check for the shift key being used to navigate back in quick open + return false; // this is an optimistic check for the shift key being used to navigate back in quick input } return true; @@ -1053,7 +1053,7 @@ class InputBox extends QuickInput implements IInputBox { } export class QuickInputController extends Disposable { - private static readonly MAX_WIDTH = 600; // Max total width of quick open widget + private static readonly MAX_WIDTH = 600; // Max total width of quick input widget private idPrefix: string; private ui: QuickInputUI | undefined; diff --git a/src/vs/base/parts/quickinput/browser/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts index 61760489d9..8e931e1591 100644 --- a/src/vs/base/parts/quickinput/browser/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -106,6 +106,11 @@ class ListElementRenderer implements IListRenderer { + if (!data.checkbox.offsetParent) { // If checkbox not visible: + e.preventDefault(); // Prevent toggle of checkbox when it is immediately shown afterwards. #91740 + } + })); data.checkbox = dom.append(label, $('input.quick-input-list-checkbox')); data.checkbox.type = 'checkbox'; data.toDisposeTemplate.push(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => { @@ -275,7 +280,13 @@ export class QuickInputList { setRowLineHeight: false, multipleSelectionSupport: false, horizontalScrolling: false, - accessibilityProvider + accessibilityProvider, + ariaProvider: { + getRole: () => 'option', + getSetSize: (_: ListElement, _index: number, listLength: number) => listLength, + getPosInSet: (_: ListElement, index: number) => index + }, + ariaRole: 'listbox' } as IListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); @@ -317,6 +328,9 @@ export class QuickInputList { this._onLeave.fire(); } })); + this.disposables.push(this.list.onMouseMiddleClick(e => { + this._onLeave.fire(); + })); this.disposables.push(this.list.onContextMenu(e => { if (typeof e.index === 'number') { e.browserEvent.preventDefault(); diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts deleted file mode 100644 index 9834c11c38..0000000000 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ /dev/null @@ -1,578 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import * as types from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; -import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; -import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { IQuickNavigateConfiguration, IModel, IDataSource, IFilter, IAccessiblityProvider, IRenderer, IRunner, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; -import { IAction, IActionRunner } from 'vs/base/common/actions'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; -import * as DOM from 'vs/base/browser/dom'; -import { IQuickOpenStyles } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; -import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; -import { OS } from 'vs/base/common/platform'; -import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { IItemAccessor } from 'vs/base/common/fuzzyScorer'; -import { coalesce } from 'vs/base/common/arrays'; -import { IMatch } from 'vs/base/common/filters'; - -export interface IContext { - event: any; - quickNavigateConfiguration: IQuickNavigateConfiguration; -} - -export interface IHighlight extends IMatch { - start: number; - end: number; -} - -let IDS = 0; - -export class QuickOpenItemAccessorClass implements IItemAccessor { - - getItemLabel(entry: QuickOpenEntry): string | undefined { - return entry.getLabel(); - } - - getItemDescription(entry: QuickOpenEntry): string | undefined { - return entry.getDescription(); - } - - getItemPath(entry: QuickOpenEntry): string | undefined { - const resource = entry.getResource(); - - return resource ? resource.fsPath : undefined; - } -} - -export const QuickOpenItemAccessor = new QuickOpenItemAccessorClass(); - -export class QuickOpenEntry { - private id: string; - private labelHighlights?: IHighlight[]; - private descriptionHighlights?: IHighlight[]; - private detailHighlights?: IHighlight[]; - private hidden: boolean | undefined; - - constructor(highlights: IHighlight[] = []) { - this.id = (IDS++).toString(); - this.labelHighlights = highlights; - this.descriptionHighlights = []; - } - - /** - * A unique identifier for the entry - */ - getId(): string { - return this.id; - } - - /** - * The label of the entry to identify it from others in the list - */ - getLabel(): string | undefined { - return undefined; - } - - /** - * The options for the label to use for this entry - */ - getLabelOptions(): IIconLabelValueOptions | undefined { - return undefined; - } - - /** - * The label of the entry to use when a screen reader wants to read about the entry - */ - getAriaLabel(): string { - return coalesce([this.getLabel(), this.getDescription(), this.getDetail()]) - .join(', '); - } - - /** - * Detail information about the entry that is optional and can be shown below the label - */ - getDetail(): string | undefined { - return undefined; - } - - /** - * The icon of the entry to identify it from others in the list - */ - getIcon(): string | undefined { - return undefined; - } - - /** - * A secondary description that is optional and can be shown right to the label - */ - getDescription(): string | undefined { - return undefined; - } - - /** - * A tooltip to show when hovering over the entry. - */ - getTooltip(): string | undefined { - return undefined; - } - - /** - * A tooltip to show when hovering over the description portion of the entry. - */ - getDescriptionTooltip(): string | undefined { - return undefined; - } - - /** - * An optional keybinding to show for an entry. - */ - getKeybinding(): ResolvedKeybinding | undefined { - return undefined; - } - - /** - * A resource for this entry. Resource URIs can be used to compare different kinds of entries and group - * them together. - */ - getResource(): URI | undefined { - return undefined; - } - - /** - * Allows to reuse the same model while filtering. Hidden entries will not show up in the viewer. - */ - isHidden(): boolean { - return !!this.hidden; - } - - /** - * Allows to reuse the same model while filtering. Hidden entries will not show up in the viewer. - */ - setHidden(hidden: boolean): void { - this.hidden = hidden; - } - - /** - * Allows to set highlight ranges that should show up for the entry label and optionally description if set. - */ - setHighlights(labelHighlights?: IHighlight[], descriptionHighlights?: IHighlight[], detailHighlights?: IHighlight[]): void { - this.labelHighlights = labelHighlights; - this.descriptionHighlights = descriptionHighlights; - this.detailHighlights = detailHighlights; - } - - /** - * Allows to return highlight ranges that should show up for the entry label and description. - */ - getHighlights(): [IHighlight[] | undefined /* Label */, IHighlight[] | undefined /* Description */, IHighlight[] | undefined /* Detail */] { - return [this.labelHighlights, this.descriptionHighlights, this.detailHighlights]; - } - - /** - * Called when the entry is selected for opening. Returns a boolean value indicating if an action was performed or not. - * The mode parameter gives an indication if the element is previewed (using arrow keys) or opened. - * - * The context parameter provides additional context information how the run was triggered. - */ - run(mode: Mode, context: IEntryRunContext): boolean { - return false; - } - - /** - * Determines if this quick open entry should merge with the editor history in quick open. If set to true - * and the resource of this entry is the same as the resource for an editor history, it will not show up - * because it is considered to be a duplicate of an editor history. - */ - mergeWithEditorHistory(): boolean { - return false; - } -} - -export class QuickOpenEntryGroup extends QuickOpenEntry { - private entry?: QuickOpenEntry; - private groupLabel?: string; - private withBorder?: boolean; - - constructor(entry?: QuickOpenEntry, groupLabel?: string, withBorder?: boolean) { - super(); - - this.entry = entry; - this.groupLabel = groupLabel; - this.withBorder = withBorder; - } - - /** - * The label of the group or null if none. - */ - getGroupLabel(): string | undefined { - return this.groupLabel; - } - - setGroupLabel(groupLabel: string | undefined): void { - this.groupLabel = groupLabel; - } - - /** - * Whether to show a border on top of the group entry or not. - */ - showBorder(): boolean { - return !!this.withBorder; - } - - setShowBorder(showBorder: boolean): void { - this.withBorder = showBorder; - } - - getLabel(): string | undefined { - return this.entry ? this.entry.getLabel() : super.getLabel(); - } - - getLabelOptions(): IIconLabelValueOptions | undefined { - return this.entry ? this.entry.getLabelOptions() : super.getLabelOptions(); - } - - getAriaLabel(): string { - return this.entry ? this.entry.getAriaLabel() : super.getAriaLabel(); - } - - getDetail(): string | undefined { - return this.entry ? this.entry.getDetail() : super.getDetail(); - } - - getResource(): URI | undefined { - return this.entry ? this.entry.getResource() : super.getResource(); - } - - getIcon(): string | undefined { - return this.entry ? this.entry.getIcon() : super.getIcon(); - } - - getDescription(): string | undefined { - return this.entry ? this.entry.getDescription() : super.getDescription(); - } - - getEntry(): QuickOpenEntry | undefined { - return this.entry; - } - - getHighlights(): [IHighlight[] | undefined, IHighlight[] | undefined, IHighlight[] | undefined] { - return this.entry ? this.entry.getHighlights() : super.getHighlights(); - } - - isHidden(): boolean { - return this.entry ? this.entry.isHidden() : super.isHidden(); - } - - setHighlights(labelHighlights?: IHighlight[], descriptionHighlights?: IHighlight[], detailHighlights?: IHighlight[]): void { - this.entry ? this.entry.setHighlights(labelHighlights, descriptionHighlights, detailHighlights) : super.setHighlights(labelHighlights, descriptionHighlights, detailHighlights); - } - - setHidden(hidden: boolean): void { - this.entry ? this.entry.setHidden(hidden) : super.setHidden(hidden); - } - - run(mode: Mode, context: IEntryRunContext): boolean { - return this.entry ? this.entry.run(mode, context) : super.run(mode, context); - } -} - -class NoActionProvider implements IActionProvider { - - hasActions(tree: ITree, element: any): boolean { - return false; - } - - getActions(tree: ITree, element: any): IAction[] | null { - return null; - } -} - -export interface IQuickOpenEntryTemplateData { - container: HTMLElement; - entry: HTMLElement; - icon: HTMLSpanElement; - label: IconLabel; - detail: HighlightedLabel; - keybinding: KeybindingLabel; - actionBar: ActionBar; -} - -export interface IQuickOpenEntryGroupTemplateData extends IQuickOpenEntryTemplateData { - group?: HTMLDivElement; -} - -const templateEntry = 'quickOpenEntry'; -const templateEntryGroup = 'quickOpenEntryGroup'; - -class Renderer implements IRenderer { - - private actionProvider: IActionProvider; - private actionRunner?: IActionRunner; - - constructor(actionProvider: IActionProvider = new NoActionProvider(), actionRunner?: IActionRunner) { - this.actionProvider = actionProvider; - this.actionRunner = actionRunner; - } - - getHeight(entry: QuickOpenEntry): number { - if (entry.getDetail()) { - return 44; - } - - return 22; - } - - getTemplateId(entry: QuickOpenEntry): string { - if (entry instanceof QuickOpenEntryGroup) { - return templateEntryGroup; - } - - return templateEntry; - } - - renderTemplate(templateId: string, container: HTMLElement, styles: IQuickOpenStyles): IQuickOpenEntryGroupTemplateData { - const entryContainer = document.createElement('div'); - DOM.addClass(entryContainer, 'sub-content'); - container.appendChild(entryContainer); - - // Entry - const row1 = DOM.$('.quick-open-row'); - const row2 = DOM.$('.quick-open-row'); - const entry = DOM.$('.quick-open-entry', undefined, row1, row2); - entryContainer.appendChild(entry); - - // Icon - const icon = document.createElement('span'); - row1.appendChild(icon); - - // Label - const label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportCodicons: true }); - - // Keybinding - const keybindingContainer = document.createElement('span'); - row1.appendChild(keybindingContainer); - DOM.addClass(keybindingContainer, 'quick-open-entry-keybinding'); - const keybinding = new KeybindingLabel(keybindingContainer, OS); - - // Detail - const detailContainer = document.createElement('div'); - row2.appendChild(detailContainer); - DOM.addClass(detailContainer, 'quick-open-entry-meta'); - const detail = new HighlightedLabel(detailContainer, true); - - // Entry Group - let group: HTMLDivElement | undefined; - if (templateId === templateEntryGroup) { - group = document.createElement('div'); - DOM.addClass(group, 'results-group'); - container.appendChild(group); - } - - // Actions - DOM.addClass(container, 'actions'); - - const actionBarContainer = document.createElement('div'); - DOM.addClass(actionBarContainer, 'primary-action-bar'); - container.appendChild(actionBarContainer); - - const actionBar = new ActionBar(actionBarContainer, { - actionRunner: this.actionRunner - }); - - return { - container, - entry, - icon, - label, - detail, - keybinding, - group, - actionBar - }; - } - - renderElement(entry: QuickOpenEntry, templateId: string, data: IQuickOpenEntryGroupTemplateData, styles: IQuickOpenStyles): void { - - // Action Bar - if (this.actionProvider.hasActions(null, entry)) { - DOM.addClass(data.container, 'has-actions'); - } else { - DOM.removeClass(data.container, 'has-actions'); - } - - data.actionBar.context = entry; // make sure the context is the current element - - const actions = this.actionProvider.getActions(null, entry); - if (data.actionBar.isEmpty() && actions && actions.length > 0) { - data.actionBar.push(actions, { icon: true, label: false }); - } else if (!data.actionBar.isEmpty() && (!actions || actions.length === 0)) { - data.actionBar.clear(); - } - - // Entry group class - if (entry instanceof QuickOpenEntryGroup && entry.getGroupLabel()) { - DOM.addClass(data.container, 'has-group-label'); - } else { - DOM.removeClass(data.container, 'has-group-label'); - } - - // Entry group - if (entry instanceof QuickOpenEntryGroup) { - const group = entry; - const groupData = data; - - // Border - if (group.showBorder()) { - DOM.addClass(groupData.container, 'results-group-separator'); - if (styles.pickerGroupBorder) { - groupData.container.style.borderTopColor = styles.pickerGroupBorder.toString(); - } - } else { - DOM.removeClass(groupData.container, 'results-group-separator'); - groupData.container.style.borderTopColor = ''; - } - - // Group Label - const groupLabel = group.getGroupLabel() || ''; - if (groupData.group) { - groupData.group.textContent = groupLabel; - if (styles.pickerGroupForeground) { - groupData.group.style.color = styles.pickerGroupForeground.toString(); - } - } - } - - // Normal Entry - if (entry instanceof QuickOpenEntry) { - const [labelHighlights, descriptionHighlights, detailHighlights] = entry.getHighlights(); - - // Icon - const iconClass = entry.getIcon() ? ('quick-open-entry-icon ' + entry.getIcon()) : ''; - data.icon.className = iconClass; - - // Label - const options: IIconLabelValueOptions = entry.getLabelOptions() || Object.create(null); - options.matches = labelHighlights || []; - options.title = entry.getTooltip(); - options.descriptionTitle = entry.getDescriptionTooltip() || entry.getDescription(); // tooltip over description because it could overflow - options.descriptionMatches = descriptionHighlights || []; - data.label.setLabel(entry.getLabel() || '', entry.getDescription(), options); - - // Meta - data.detail.set(entry.getDetail(), detailHighlights); - - // Keybinding - data.keybinding.set(entry.getKeybinding()!); - } - } - - disposeTemplate(templateId: string, templateData: IQuickOpenEntryGroupTemplateData): void { - templateData.actionBar.dispose(); - templateData.actionBar = null!; - templateData.container = null!; - templateData.entry = null!; - templateData.keybinding = null!; - templateData.detail = null!; - templateData.group = null!; - templateData.icon = null!; - templateData.label.dispose(); - templateData.label = null!; - } -} - -export class QuickOpenModel implements - IModel, - IDataSource, - IFilter, - IRunner, - IAccessiblityProvider -{ - private _entries: QuickOpenEntry[]; - private _dataSource: IDataSource; - private _renderer: IRenderer; - private _filter: IFilter; - private _runner: IRunner; - private _accessibilityProvider: IAccessiblityProvider; - - constructor(entries: QuickOpenEntry[] = [], actionProvider: IActionProvider = new NoActionProvider()) { - this._entries = entries; - this._dataSource = this; - this._renderer = new Renderer(actionProvider); - this._filter = this; - this._runner = this; - this._accessibilityProvider = this; - } - - get entries() { return this._entries; } - get dataSource() { return this._dataSource; } - get renderer() { return this._renderer; } - get filter() { return this._filter; } - get runner() { return this._runner; } - get accessibilityProvider() { return this._accessibilityProvider; } - - set entries(entries: QuickOpenEntry[]) { - this._entries = entries; - } - - /** - * Adds entries that should show up in the quick open viewer. - */ - addEntries(entries: QuickOpenEntry[]): void { - if (types.isArray(entries)) { - this._entries = this._entries.concat(entries); - } - } - - /** - * Set the entries that should show up in the quick open viewer. - */ - setEntries(entries: QuickOpenEntry[]): void { - if (types.isArray(entries)) { - this._entries = entries; - } - } - - /** - * Get the entries that should show up in the quick open viewer. - * - * @visibleOnly optional parameter to only return visible entries - */ - getEntries(visibleOnly?: boolean): QuickOpenEntry[] { - if (visibleOnly) { - return this._entries.filter((e) => !e.isHidden()); - } - - return this._entries; - } - - getId(entry: QuickOpenEntry): string { - return entry.getId(); - } - - getLabel(entry: QuickOpenEntry): string | null { - return types.withUndefinedAsNull(entry.getLabel()); - } - - getAriaLabel(entry: QuickOpenEntry): string { - const ariaLabel = entry.getAriaLabel(); - if (ariaLabel) { - return nls.localize('quickOpenAriaLabelEntry', "{0}, picker", entry.getAriaLabel()); - } - - return nls.localize('quickOpenAriaLabel', "picker"); - } - - isVisible(entry: QuickOpenEntry): boolean { - return !entry.isHidden(); - } - - run(entry: QuickOpenEntry, mode: Mode, context: IEntryRunContext): boolean { - return entry.run(mode, context); - } -} diff --git a/src/vs/base/parts/quickopen/browser/quickOpenViewer.ts b/src/vs/base/parts/quickopen/browser/quickOpenViewer.ts deleted file mode 100644 index 2583abc37c..0000000000 --- a/src/vs/base/parts/quickopen/browser/quickOpenViewer.ts +++ /dev/null @@ -1,142 +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 { isFunction } from 'vs/base/common/types'; -import { ITree, IRenderer, IFilter, IDataSource, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree'; -import { IModel } from 'vs/base/parts/quickopen/common/quickOpen'; -import { IQuickOpenStyles } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; - -export interface IModelProvider { - getModel(): IModel; -} - -export class DataSource implements IDataSource { - - private modelProvider: IModelProvider; - - constructor(model: IModel); - constructor(modelProvider: IModelProvider); - constructor(arg: any) { - this.modelProvider = isFunction(arg.getModel) ? arg : { getModel: () => arg }; - } - - getId(tree: ITree, element: any): string { - if (!element) { - return null!; - } - - const model = this.modelProvider.getModel(); - return model === element ? '__root__' : model.dataSource.getId(element); - } - - hasChildren(tree: ITree, element: any): boolean { - const model = this.modelProvider.getModel(); - return !!(model && model === element && model.entries.length > 0); - } - - getChildren(tree: ITree, element: any): Promise { - const model = this.modelProvider.getModel(); - return Promise.resolve(model === element ? model.entries : []); - } - - getParent(tree: ITree, element: any): Promise { - return Promise.resolve(null); - } -} - -export class AccessibilityProvider implements IAccessibilityProvider { - constructor(private modelProvider: IModelProvider) { } - - getAriaLabel(tree: ITree, element: any): string | null { - const model = this.modelProvider.getModel(); - - return model.accessibilityProvider ? model.accessibilityProvider.getAriaLabel(element) : null; - } - - getPosInSet(tree: ITree, element: any): string { - const model = this.modelProvider.getModel(); - let i = 0; - if (model.filter) { - for (const entry of model.entries) { - if (model.filter.isVisible(entry)) { - i++; - } - if (entry === element) { - break; - } - } - } else { - i = model.entries.indexOf(element) + 1; - } - return String(i); - } - - getSetSize(): string { - const model = this.modelProvider.getModel(); - let n = 0; - if (model.filter) { - for (const entry of model.entries) { - if (model.filter.isVisible(entry)) { - n++; - } - } - } else { - n = model.entries.length; - } - return String(n); - } -} - -export class Filter implements IFilter { - - constructor(private modelProvider: IModelProvider) { } - - isVisible(tree: ITree, element: any): boolean { - const model = this.modelProvider.getModel(); - - if (!model.filter) { - return true; - } - - return model.filter.isVisible(element); - } -} - -export class Renderer implements IRenderer { - private styles: IQuickOpenStyles; - - constructor(private modelProvider: IModelProvider, styles: IQuickOpenStyles) { - this.styles = styles; - } - - updateStyles(styles: IQuickOpenStyles): void { - this.styles = styles; - } - - getHeight(tree: ITree, element: any): number { - const model = this.modelProvider.getModel(); - return model.renderer.getHeight(element); - } - - getTemplateId(tree: ITree, element: any): string { - const model = this.modelProvider.getModel(); - return model.renderer.getTemplateId(element); - } - - renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any { - const model = this.modelProvider.getModel(); - return model.renderer.renderTemplate(templateId, container, this.styles); - } - - renderElement(tree: ITree, element: any, templateId: string, templateData: any): void { - const model = this.modelProvider.getModel(); - model.renderer.renderElement(element, templateId, templateData, this.styles); - } - - disposeTemplate(tree: ITree, templateId: string, templateData: any): void { - const model = this.modelProvider.getModel(); - model.renderer.disposeTemplate(templateId, templateData); - } -} diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts deleted file mode 100644 index b54ac4aeb3..0000000000 --- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts +++ /dev/null @@ -1,1041 +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!./quickopen'; -import * as nls from 'vs/nls'; -import * as platform from 'vs/base/common/platform'; -import * as types from 'vs/base/common/types'; -import { IQuickNavigateConfiguration, IAutoFocus, IEntryRunContext, IModel, Mode, IKeyMods } from 'vs/base/parts/quickopen/common/quickOpen'; -import { Filter, Renderer, DataSource, IModelProvider, AccessibilityProvider } from 'vs/base/parts/quickopen/browser/quickOpenViewer'; -import { ITree, ContextMenuEvent, IActionProvider, ITreeStyles, ITreeOptions, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree'; -import { InputBox, MessageType, IInputBoxStyles, IRange } from 'vs/base/browser/ui/inputbox/inputBox'; -import Severity from 'vs/base/common/severity'; -import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { DefaultController, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults'; -import * as DOM from 'vs/base/browser/dom'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { Color } from 'vs/base/common/color'; -import { mixin } from 'vs/base/common/objects'; -import { StandardMouseEvent, IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IThemable } from 'vs/base/common/styler'; - -export interface IQuickOpenCallbacks { - onOk: () => void; - onCancel: () => void; - onType: (value: string) => void; - onShow?: () => void; - onHide?: (reason: HideReason) => void; - onFocusLost?: () => boolean /* veto close */; -} - -export interface IQuickOpenOptions extends IQuickOpenStyles { - minItemsToShow?: number; - maxItemsToShow?: number; - inputPlaceHolder?: string; - inputAriaLabel?: string; - actionProvider?: IActionProvider; - keyboardSupport?: boolean; - treeCreator?: (container: HTMLElement, configuration: ITreeConfiguration, options?: ITreeOptions) => ITree; -} - -export interface IQuickOpenStyles extends IInputBoxStyles, ITreeStyles { - background?: Color; - foreground?: Color; - borderColor?: Color; - pickerGroupForeground?: Color; - pickerGroupBorder?: Color; - widgetShadow?: Color; - progressBarBackground?: Color; -} - -export interface IShowOptions { - quickNavigateConfiguration?: IQuickNavigateConfiguration; - autoFocus?: IAutoFocus; - inputSelection?: IRange; - value?: string; -} - -export class QuickOpenController extends DefaultController { - - onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean { - if (platform.isMacintosh) { - return this.onLeftClick(tree, element, event); // https://github.com/Microsoft/vscode/issues/1011 - } - - return super.onContextMenu(tree, element, event); - } - - onMouseMiddleClick(tree: ITree, element: any, event: IMouseEvent): boolean { - return this.onLeftClick(tree, element, event); - } -} - -export const enum HideReason { - ELEMENT_SELECTED, - FOCUS_LOST, - CANCELED -} - -const defaultStyles = { - background: Color.fromHex('#1E1E1E'), - foreground: Color.fromHex('#CCCCCC'), - pickerGroupForeground: Color.fromHex('#0097FB'), - pickerGroupBorder: Color.fromHex('#3F3F46'), - widgetShadow: Color.fromHex('#000000'), - progressBarBackground: Color.fromHex('#0E70C0') -}; - -const DEFAULT_INPUT_ARIA_LABEL = nls.localize('quickOpenAriaLabel', "Quick picker. Type to narrow down results."); - -export class QuickOpenWidget extends Disposable implements IModelProvider, IThemable { - - private static readonly MAX_WIDTH = 600; // Max total width of quick open widget - private static readonly MAX_ITEMS_HEIGHT = 20 * 22; // Max height of item list below input field - - private isDisposed: boolean; - private options: IQuickOpenOptions; - // @ts-ignore (legacy widget - to be replaced with quick input) - private element: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private tree: ITree; - // @ts-ignore (legacy widget - to be replaced with quick input) - private inputBox: InputBox; - // @ts-ignore (legacy widget - to be replaced with quick input) - private inputContainer: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private helpText: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private resultCount: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private treeContainer: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private progressBar: ProgressBar; - // @ts-ignore (legacy widget - to be replaced with quick input) - private visible: boolean; - // @ts-ignore (legacy widget - to be replaced with quick input) - private isLoosingFocus: boolean; - private callbacks: IQuickOpenCallbacks; - private quickNavigateConfiguration: IQuickNavigateConfiguration | undefined; - private container: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private treeElement: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private inputElement: HTMLElement; - // @ts-ignore (legacy widget - to be replaced with quick input) - private layoutDimensions: DOM.Dimension; - private model: IModel | null; - private inputChangingTimeoutHandle: any; - // @ts-ignore (legacy widget - to be replaced with quick input) - private styles: IQuickOpenStyles; - // @ts-ignore (legacy widget - to be replaced with quick input) - private renderer: Renderer; - - constructor(container: HTMLElement, callbacks: IQuickOpenCallbacks, options: IQuickOpenOptions) { - super(); - - this.isDisposed = false; - this.container = container; - this.callbacks = callbacks; - this.options = options; - this.styles = options || Object.create(null); - mixin(this.styles, defaultStyles, false); - this.model = null; - } - - getElement(): HTMLElement { - return this.element; - } - - getModel(): IModel { - return this.model!; - } - - setCallbacks(callbacks: IQuickOpenCallbacks): void { - this.callbacks = callbacks; - } - - create(): HTMLElement { - - // Container - this.element = document.createElement('div'); - DOM.addClass(this.element, 'monaco-quick-open-widget'); - this.container.appendChild(this.element); - - this._register(DOM.addDisposableListener(this.element, DOM.EventType.CONTEXT_MENU, e => DOM.EventHelper.stop(e, true))); // Do this to fix an issue on Mac where the menu goes into the way - this._register(DOM.addDisposableListener(this.element, DOM.EventType.FOCUS, e => this.gainingFocus(), true)); - this._register(DOM.addDisposableListener(this.element, DOM.EventType.BLUR, e => this.loosingFocus(e), true)); - this._register(DOM.addDisposableListener(this.element, DOM.EventType.KEY_DOWN, e => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - if (keyboardEvent.keyCode === KeyCode.Escape) { - DOM.EventHelper.stop(e, true); - - this.hide(HideReason.CANCELED); - } else if (keyboardEvent.keyCode === KeyCode.Tab && !keyboardEvent.altKey && !keyboardEvent.ctrlKey && !keyboardEvent.metaKey) { - const stops = (e.currentTarget as HTMLElement).querySelectorAll('input, .monaco-tree, .monaco-tree-row.focused .action-label.icon') as NodeListOf; - if (keyboardEvent.shiftKey && keyboardEvent.target === stops[0]) { - DOM.EventHelper.stop(e, true); - stops[stops.length - 1].focus(); - } else if (!keyboardEvent.shiftKey && keyboardEvent.target === stops[stops.length - 1]) { - DOM.EventHelper.stop(e, true); - stops[0].focus(); - } - } - })); - - // Progress Bar - this.progressBar = this._register(new ProgressBar(this.element, { progressBarBackground: this.styles.progressBarBackground })); - this.progressBar.hide(); - - // Input Field - this.inputContainer = document.createElement('div'); - DOM.addClass(this.inputContainer, 'quick-open-input'); - this.element.appendChild(this.inputContainer); - - this.inputBox = this._register(new InputBox(this.inputContainer, undefined, { - placeholder: this.options.inputPlaceHolder || '', - ariaLabel: DEFAULT_INPUT_ARIA_LABEL, - inputBackground: this.styles.inputBackground, - inputForeground: this.styles.inputForeground, - inputBorder: this.styles.inputBorder, - inputValidationInfoBackground: this.styles.inputValidationInfoBackground, - inputValidationInfoForeground: this.styles.inputValidationInfoForeground, - inputValidationInfoBorder: this.styles.inputValidationInfoBorder, - inputValidationWarningBackground: this.styles.inputValidationWarningBackground, - inputValidationWarningForeground: this.styles.inputValidationWarningForeground, - inputValidationWarningBorder: this.styles.inputValidationWarningBorder, - inputValidationErrorBackground: this.styles.inputValidationErrorBackground, - inputValidationErrorForeground: this.styles.inputValidationErrorForeground, - inputValidationErrorBorder: this.styles.inputValidationErrorBorder - })); - - this.inputElement = this.inputBox.inputElement; - this.inputElement.setAttribute('role', 'combobox'); - this.inputElement.setAttribute('aria-haspopup', 'false'); - this.inputElement.setAttribute('aria-autocomplete', 'list'); - - this._register(DOM.addDisposableListener(this.inputBox.inputElement, DOM.EventType.INPUT, (e: Event) => this.onType())); - this._register(DOM.addDisposableListener(this.inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - const shouldOpenInBackground = this.shouldOpenInBackground(keyboardEvent); - - // Do not handle Tab: It is used to navigate between elements without mouse - if (keyboardEvent.keyCode === KeyCode.Tab) { - return; - } - - // Pass tree navigation keys to the tree but leave focus in input field - else if (keyboardEvent.keyCode === KeyCode.DownArrow || keyboardEvent.keyCode === KeyCode.UpArrow || keyboardEvent.keyCode === KeyCode.PageDown || keyboardEvent.keyCode === KeyCode.PageUp) { - DOM.EventHelper.stop(e, true); - - this.navigateInTree(keyboardEvent.keyCode, keyboardEvent.shiftKey); - - // Position cursor at the end of input to allow right arrow (open in background) - // to function immediately unless the user has made a selection - if (this.inputBox.inputElement.selectionStart === this.inputBox.inputElement.selectionEnd) { - this.inputBox.inputElement.selectionStart = this.inputBox.value.length; - } - } - - // Select element on Enter or on Arrow-Right if we are at the end of the input - else if (keyboardEvent.keyCode === KeyCode.Enter || shouldOpenInBackground) { - DOM.EventHelper.stop(e, true); - - const focus = this.tree.getFocus(); - if (focus) { - this.elementSelected(focus, e, shouldOpenInBackground ? Mode.OPEN_IN_BACKGROUND : Mode.OPEN); - } - } - })); - - // Result count for screen readers - this.resultCount = document.createElement('div'); - DOM.addClass(this.resultCount, 'quick-open-result-count'); - this.resultCount.setAttribute('aria-live', 'polite'); - this.resultCount.setAttribute('aria-atomic', 'true'); - this.element.appendChild(this.resultCount); - - // Tree - this.treeContainer = document.createElement('div'); - DOM.addClass(this.treeContainer, 'quick-open-tree'); - this.element.appendChild(this.treeContainer); - - const createTree = this.options.treeCreator || ((container, config, opts) => new Tree(container, config, opts)); - - this.tree = this._register(createTree(this.treeContainer, { - dataSource: new DataSource(this), - controller: new QuickOpenController({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: this.options.keyboardSupport }), - renderer: (this.renderer = new Renderer(this, this.styles)), - filter: new Filter(this), - accessibilityProvider: new AccessibilityProvider(this) - }, { - twistiePixels: 11, - indentPixels: 0, - alwaysFocused: true, - verticalScrollMode: ScrollbarVisibility.Visible, - horizontalScrollMode: ScrollbarVisibility.Hidden, - ariaLabel: nls.localize('treeAriaLabel', "Quick Picker"), - keyboardSupport: this.options.keyboardSupport, - preventRootFocus: false - })); - - this.treeElement = this.tree.getHTMLElement(); - - // Handle Focus and Selection event - this._register(this.tree.onDidChangeFocus(event => { - this.elementFocused(event.focus, event); - })); - - this._register(this.tree.onDidChangeSelection(event => { - if (event.selection && event.selection.length > 0) { - const mouseEvent: StandardMouseEvent = event.payload && event.payload.originalEvent instanceof StandardMouseEvent ? event.payload.originalEvent : undefined; - const shouldOpenInBackground = mouseEvent ? this.shouldOpenInBackground(mouseEvent) : false; - - this.elementSelected(event.selection[0], event, shouldOpenInBackground ? Mode.OPEN_IN_BACKGROUND : Mode.OPEN); - } - })); - - this._register(DOM.addDisposableListener(this.treeContainer, DOM.EventType.KEY_DOWN, e => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - - // Only handle when in quick navigation mode - if (!this.quickNavigateConfiguration) { - return; - } - - // Support keyboard navigation in quick navigation mode - if (keyboardEvent.keyCode === KeyCode.DownArrow || keyboardEvent.keyCode === KeyCode.UpArrow || keyboardEvent.keyCode === KeyCode.PageDown || keyboardEvent.keyCode === KeyCode.PageUp) { - DOM.EventHelper.stop(e, true); - - this.navigateInTree(keyboardEvent.keyCode); - } - - // Support to open item with Enter still even in quick nav mode - else if (keyboardEvent.keyCode === KeyCode.Enter) { - DOM.EventHelper.stop(e, true); - - const focus = this.tree.getFocus(); - if (focus) { - this.elementSelected(focus, e); - } - } - })); - - this._register(DOM.addDisposableListener(this.treeContainer, DOM.EventType.KEY_UP, e => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - const keyCode = keyboardEvent.keyCode; - - // Only handle when in quick navigation mode - if (!this.quickNavigateConfiguration) { - return; - } - - // Select element when keys are pressed that signal it - const quickNavKeys = this.quickNavigateConfiguration.keybindings; - const wasTriggerKeyPressed = quickNavKeys.some(k => { - const [firstPart, chordPart] = k.getParts(); - if (chordPart) { - return false; - } - - if (firstPart.shiftKey && keyCode === KeyCode.Shift) { - if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) { - return false; // this is an optimistic check for the shift key being used to navigate back in quick open - } - - return true; - } - - if (firstPart.altKey && keyCode === KeyCode.Alt) { - return true; - } - - if (firstPart.ctrlKey && keyCode === KeyCode.Ctrl) { - return true; - } - - if (firstPart.metaKey && keyCode === KeyCode.Meta) { - return true; - } - - return false; - }); - - if (wasTriggerKeyPressed) { - const focus = this.tree.getFocus(); - if (focus) { - this.elementSelected(focus, e); - } - } - })); - - // Support layout - if (this.layoutDimensions) { - this.layout(this.layoutDimensions); - } - - this.applyStyles(); - - // Allows focus to switch to next/previous entry after tab into an actionbar item - this._register(DOM.addDisposableListener(this.treeContainer, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - // Only handle when not in quick navigation mode - if (this.quickNavigateConfiguration) { - return; - } - if (keyboardEvent.keyCode === KeyCode.DownArrow || keyboardEvent.keyCode === KeyCode.UpArrow || keyboardEvent.keyCode === KeyCode.PageDown || keyboardEvent.keyCode === KeyCode.PageUp) { - DOM.EventHelper.stop(e, true); - this.navigateInTree(keyboardEvent.keyCode, keyboardEvent.shiftKey); - this.treeElement.focus(); - } - })); - - return this.element; - } - - style(styles: IQuickOpenStyles): void { - this.styles = styles; - - this.applyStyles(); - } - - protected applyStyles(): void { - if (this.element) { - const foreground = this.styles.foreground ? this.styles.foreground.toString() : ''; - const background = this.styles.background ? this.styles.background.toString() : ''; - const borderColor = this.styles.borderColor ? this.styles.borderColor.toString() : ''; - const widgetShadow = this.styles.widgetShadow ? this.styles.widgetShadow.toString() : ''; - - this.element.style.color = foreground; - this.element.style.backgroundColor = background; - this.element.style.borderColor = borderColor; - this.element.style.borderWidth = borderColor ? '1px' : ''; - this.element.style.borderStyle = borderColor ? 'solid' : ''; - this.element.style.boxShadow = widgetShadow ? `0 5px 8px ${widgetShadow}` : ''; - } - - if (this.progressBar) { - this.progressBar.style({ - progressBarBackground: this.styles.progressBarBackground - }); - } - - if (this.inputBox) { - this.inputBox.style({ - inputBackground: this.styles.inputBackground, - inputForeground: this.styles.inputForeground, - inputBorder: this.styles.inputBorder, - inputValidationInfoBackground: this.styles.inputValidationInfoBackground, - inputValidationInfoForeground: this.styles.inputValidationInfoForeground, - inputValidationInfoBorder: this.styles.inputValidationInfoBorder, - inputValidationWarningBackground: this.styles.inputValidationWarningBackground, - inputValidationWarningForeground: this.styles.inputValidationWarningForeground, - inputValidationWarningBorder: this.styles.inputValidationWarningBorder, - inputValidationErrorBackground: this.styles.inputValidationErrorBackground, - inputValidationErrorForeground: this.styles.inputValidationErrorForeground, - inputValidationErrorBorder: this.styles.inputValidationErrorBorder - }); - } - - if (this.tree && !this.options.treeCreator) { - this.tree.style(this.styles); - } - - if (this.renderer) { - this.renderer.updateStyles(this.styles); - } - } - - private shouldOpenInBackground(e: StandardKeyboardEvent | StandardMouseEvent): boolean { - - // Keyboard - if (e instanceof StandardKeyboardEvent) { - if (e.keyCode !== KeyCode.RightArrow) { - return false; // only for right arrow - } - - if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) { - return false; // no modifiers allowed - } - - // validate the cursor is at the end of the input and there is no selection, - // and if not prevent opening in the background such as the selection can be changed - const element = this.inputBox.inputElement; - return element.selectionEnd === this.inputBox.value.length && element.selectionStart === element.selectionEnd; - } - - // Mouse - return e.middleButton; - } - - private onType(): void { - const value = this.inputBox.value; - - // Adjust help text as needed if present - if (this.helpText) { - if (value) { - DOM.hide(this.helpText); - } else { - DOM.show(this.helpText); - } - } - - // Send to callbacks - this.callbacks.onType(value); - } - - navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void { - if (this.isVisible()) { - - // Transition into quick navigate mode if not yet done - if (!this.quickNavigateConfiguration && quickNavigate) { - this.quickNavigateConfiguration = quickNavigate; - this.tree.domFocus(); - } - - // Navigate - this.navigateInTree(next ? KeyCode.DownArrow : KeyCode.UpArrow); - } - } - - private navigateInTree(keyCode: KeyCode, isShift?: boolean): void { - const model: IModel = this.tree.getInput(); - const entries = model ? model.entries : []; - const oldFocus = this.tree.getFocus(); - - // Normal Navigation - switch (keyCode) { - case KeyCode.DownArrow: - this.tree.focusNext(); - break; - - case KeyCode.UpArrow: - this.tree.focusPrevious(); - break; - - case KeyCode.PageDown: - this.tree.focusNextPage(); - break; - - case KeyCode.PageUp: - this.tree.focusPreviousPage(); - break; - - case KeyCode.Tab: - if (isShift) { - this.tree.focusPrevious(); - } else { - this.tree.focusNext(); - } - break; - } - - let newFocus = this.tree.getFocus(); - - // Support cycle-through navigation if focus did not change - if (entries.length > 1 && oldFocus === newFocus) { - - // Up from no entry or first entry goes down to last - if (keyCode === KeyCode.UpArrow || (keyCode === KeyCode.Tab && isShift)) { - this.tree.focusLast(); - } - - // Down from last entry goes to up to first - else if (keyCode === KeyCode.DownArrow || keyCode === KeyCode.Tab && !isShift) { - this.tree.focusFirst(); - } - } - - // Reveal - newFocus = this.tree.getFocus(); - if (newFocus) { - this.tree.reveal(newFocus); - } - } - - private elementFocused(value: any, event?: any): void { - if (!value || !this.isVisible()) { - return; - } - - // ARIA - const arivaActiveDescendant = this.treeElement.getAttribute('aria-activedescendant'); - if (arivaActiveDescendant) { - this.inputElement.setAttribute('aria-activedescendant', arivaActiveDescendant); - } else { - this.inputElement.removeAttribute('aria-activedescendant'); - } - - const context: IEntryRunContext = { event: event, keymods: this.extractKeyMods(event), quickNavigateConfiguration: this.quickNavigateConfiguration }; - this.model!.runner.run(value, Mode.PREVIEW, context); - } - - private elementSelected(value: any, event?: any, preferredMode?: Mode): void { - let hide = true; - - // Trigger open of element on selection - if (this.isVisible()) { - let mode = preferredMode || Mode.OPEN; - - const context: IEntryRunContext = { event, keymods: this.extractKeyMods(event), quickNavigateConfiguration: this.quickNavigateConfiguration }; - - hide = this.model!.runner.run(value, mode, context); - } - - // Hide if command was run successfully - if (hide) { - this.hide(HideReason.ELEMENT_SELECTED); - } - } - - private extractKeyMods(event: any): IKeyMods { - return { - ctrlCmd: event && (event.ctrlKey || event.metaKey || (event.payload && event.payload.originalEvent && (event.payload.originalEvent.ctrlKey || event.payload.originalEvent.metaKey))), - alt: event && (event.altKey || (event.payload && event.payload.originalEvent && event.payload.originalEvent.altKey)) - }; - } - - show(prefix: string, options?: IShowOptions): void; - show(input: IModel, options?: IShowOptions): void; - show(param: any, options?: IShowOptions): void { - this.visible = true; - this.isLoosingFocus = false; - this.quickNavigateConfiguration = options ? options.quickNavigateConfiguration : undefined; - - // Adjust UI for quick navigate mode - if (this.quickNavigateConfiguration) { - DOM.hide(this.inputContainer); - DOM.show(this.element); - this.tree.domFocus(); - } - - // Otherwise use normal UI - else { - DOM.show(this.inputContainer); - DOM.show(this.element); - this.inputBox.focus(); - } - - // Adjust Help text for IE - if (this.helpText) { - if (this.quickNavigateConfiguration || types.isString(param)) { - DOM.hide(this.helpText); - } else { - DOM.show(this.helpText); - } - } - - // Show based on param - if (types.isString(param)) { - this.doShowWithPrefix(param); - } else { - if (options && options.value) { - this.restoreLastInput(options.value); - } - this.doShowWithInput(param, options && options.autoFocus ? options.autoFocus : {}); - } - - // Respect selectAll option - if (options && options.inputSelection && !this.quickNavigateConfiguration) { - this.inputBox.select(options.inputSelection); - } - - if (this.callbacks.onShow) { - this.callbacks.onShow(); - } - } - - private restoreLastInput(lastInput: string) { - this.inputBox.value = lastInput; - this.inputBox.select(); - this.callbacks.onType(lastInput); - } - - private doShowWithPrefix(prefix: string): void { - this.inputBox.value = prefix; - this.callbacks.onType(prefix); - } - - private doShowWithInput(input: IModel, autoFocus: IAutoFocus): void { - this.setInput(input, autoFocus); - } - - private setInputAndLayout(input: IModel, autoFocus?: IAutoFocus): void { - this.treeContainer.style.height = `${this.getHeight(input)}px`; - - this.tree.setInput(null).then(() => { - this.model = input; - - // ARIA - this.inputElement.setAttribute('aria-haspopup', String(input && input.entries && input.entries.length > 0)); - - return this.tree.setInput(input); - }).then(() => { - - // Indicate entries to tree - this.tree.layout(); - - const entries = input ? input.entries.filter(e => this.isElementVisible(input, e)) : []; - this.updateResultCount(entries.length); - - // Handle auto focus - if (entries.length) { - this.autoFocus(input, entries, autoFocus); - } - }); - } - - private isElementVisible(input: IModel, e: T): boolean { - if (!input.filter) { - return true; - } - - return input.filter.isVisible(e); - } - - private autoFocus(input: IModel, entries: any[], autoFocus: IAutoFocus = {}): void { - - // First check for auto focus of prefix matches - if (autoFocus.autoFocusPrefixMatch) { - let caseSensitiveMatch: any; - let caseInsensitiveMatch: any; - const prefix = autoFocus.autoFocusPrefixMatch; - const lowerCasePrefix = prefix.toLowerCase(); - for (const entry of entries) { - const label = input.dataSource.getLabel(entry) || ''; - - if (!caseSensitiveMatch && label.indexOf(prefix) === 0) { - caseSensitiveMatch = entry; - } else if (!caseInsensitiveMatch && label.toLowerCase().indexOf(lowerCasePrefix) === 0) { - caseInsensitiveMatch = entry; - } - - if (caseSensitiveMatch && caseInsensitiveMatch) { - break; - } - } - - const entryToFocus = caseSensitiveMatch || caseInsensitiveMatch; - if (entryToFocus) { - this.tree.setFocus(entryToFocus); - this.tree.reveal(entryToFocus, 0.5); - - return; - } - } - - // Second check for auto focus of first entry - if (autoFocus.autoFocusFirstEntry) { - this.tree.focusFirst(); - this.tree.reveal(this.tree.getFocus()); - } - - // Third check for specific index option - else if (typeof autoFocus.autoFocusIndex === 'number') { - if (entries.length > autoFocus.autoFocusIndex) { - this.tree.focusNth(autoFocus.autoFocusIndex); - this.tree.reveal(this.tree.getFocus()); - } - } - - // Check for auto focus of second entry - else if (autoFocus.autoFocusSecondEntry) { - if (entries.length > 1) { - this.tree.focusNth(1); - } - } - - // Finally check for auto focus of last entry - else if (autoFocus.autoFocusLastEntry) { - if (entries.length > 1) { - this.tree.focusLast(); - this.tree.reveal(this.tree.getFocus()); - } - } - } - - refresh(input?: IModel, autoFocus?: IAutoFocus): void { - if (!this.isVisible()) { - return; - } - - if (!input) { - input = this.tree.getInput(); - } - - if (!input) { - return; - } - - // Apply height & Refresh - this.treeContainer.style.height = `${this.getHeight(input)}px`; - this.tree.refresh().then(() => { - - // Indicate entries to tree - this.tree.layout(); - - const entries = input ? input.entries!.filter(e => this.isElementVisible(input!, e)) : []; - this.updateResultCount(entries.length); - - // Handle auto focus - if (autoFocus) { - if (entries.length) { - this.autoFocus(input!, entries, autoFocus); - } - } - }); - } - - private getHeight(input: IModel): number { - const renderer = input.renderer; - - if (!input) { - const itemHeight = renderer.getHeight(null); - - return this.options.minItemsToShow ? this.options.minItemsToShow * itemHeight : 0; - } - - let height = 0; - - let preferredItemsHeight: number | undefined; - if (this.layoutDimensions && this.layoutDimensions.height) { - preferredItemsHeight = (this.layoutDimensions.height - 50 /* subtract height of input field (30px) and some spacing (drop shadow) to fit */) * 0.4 /* max 40% of screen */; - } - - if (!preferredItemsHeight || preferredItemsHeight > QuickOpenWidget.MAX_ITEMS_HEIGHT) { - preferredItemsHeight = QuickOpenWidget.MAX_ITEMS_HEIGHT; - } - - const entries = input.entries.filter(e => this.isElementVisible(input, e)); - const maxEntries = this.options.maxItemsToShow || entries.length; - for (let i = 0; i < maxEntries && i < entries.length; i++) { - const entryHeight = renderer.getHeight(entries[i]); - if (height + entryHeight <= preferredItemsHeight) { - height += entryHeight; - } else { - break; - } - } - - return height; - } - - updateResultCount(count: number) { - this.resultCount.textContent = nls.localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results", count); - } - - hide(reason?: HideReason): void { - if (!this.isVisible()) { - return; - } - - this.visible = false; - DOM.hide(this.element); - this.element.blur(); - - // Clear input field and clear tree - this.inputBox.value = ''; - this.tree.setInput(null); - - // ARIA - this.inputElement.setAttribute('aria-haspopup', 'false'); - - // Reset Tree Height - this.treeContainer.style.height = `${this.options.minItemsToShow ? this.options.minItemsToShow * 22 : 0}px`; - - // Clear any running Progress - this.progressBar.stop().hide(); - - // Clear Focus - if (this.tree.isDOMFocused()) { - this.tree.domBlur(); - } else if (this.inputBox.hasFocus()) { - this.inputBox.blur(); - } - - // Callbacks - if (reason === HideReason.ELEMENT_SELECTED) { - this.callbacks.onOk(); - } else { - this.callbacks.onCancel(); - } - - if (this.callbacks.onHide) { - this.callbacks.onHide(reason!); - } - } - - getQuickNavigateConfiguration(): IQuickNavigateConfiguration { - return this.quickNavigateConfiguration!; - } - - setPlaceHolder(placeHolder: string): void { - if (this.inputBox) { - this.inputBox.setPlaceHolder(placeHolder); - } - } - - setValue(value: string, selectionOrStableHint?: [number, number] | null): void { - if (this.inputBox) { - this.inputBox.value = value; - if (selectionOrStableHint === null) { - // null means stable-selection - } else if (Array.isArray(selectionOrStableHint)) { - const [start, end] = selectionOrStableHint; - this.inputBox.select({ start, end }); - } else { - this.inputBox.select(); - } - } - } - - setPassword(isPassword: boolean): void { - if (this.inputBox) { - this.inputBox.inputElement.type = isPassword ? 'password' : 'text'; - } - } - - setInput(input: IModel, autoFocus?: IAutoFocus, ariaLabel?: string): void { - if (!this.isVisible()) { - return; - } - - // If the input changes, indicate this to the tree - if (!!this.getInput()) { - this.onInputChanging(); - } - - // Adapt tree height to entries and apply input - this.setInputAndLayout(input, autoFocus); - - // Apply ARIA - if (this.inputBox) { - this.inputBox.setAriaLabel(ariaLabel || DEFAULT_INPUT_ARIA_LABEL); - } - } - - private onInputChanging(): void { - if (this.inputChangingTimeoutHandle) { - clearTimeout(this.inputChangingTimeoutHandle); - this.inputChangingTimeoutHandle = null; - } - - // when the input is changing in quick open, we indicate this as CSS class to the widget - // for a certain timeout. this helps reducing some hectic UI updates when input changes quickly - DOM.addClass(this.element, 'content-changing'); - this.inputChangingTimeoutHandle = setTimeout(() => { - DOM.removeClass(this.element, 'content-changing'); - }, 500); - } - - getInput(): IModel { - return this.tree.getInput(); - } - - showInputDecoration(decoration: Severity): void { - if (this.inputBox) { - this.inputBox.showMessage({ type: decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR, content: '' }); - } - } - - clearInputDecoration(): void { - if (this.inputBox) { - this.inputBox.hideMessage(); - } - } - - focus(): void { - if (this.isVisible() && this.inputBox) { - this.inputBox.focus(); - } - } - - accept(): void { - if (this.isVisible()) { - const focus = this.tree.getFocus(); - if (focus) { - this.elementSelected(focus); - } - } - } - - getProgressBar(): ProgressBar { - return this.progressBar; - } - - getInputBox(): InputBox { - return this.inputBox; - } - - setExtraClass(clazz: string | null): void { - const previousClass = this.element.getAttribute('quick-open-extra-class'); - if (previousClass) { - DOM.removeClasses(this.element, previousClass); - } - - if (clazz) { - DOM.addClasses(this.element, clazz); - this.element.setAttribute('quick-open-extra-class', clazz); - } else if (previousClass) { - this.element.removeAttribute('quick-open-extra-class'); - } - } - - isVisible(): boolean { - return this.visible; - } - - layout(dimension: DOM.Dimension): void { - this.layoutDimensions = dimension; - - // Apply to quick open width (height is dynamic by number of items to show) - const quickOpenWidth = Math.min(this.layoutDimensions.width * 0.62 /* golden cut */, QuickOpenWidget.MAX_WIDTH); - if (this.element) { - - // quick open - this.element.style.width = `${quickOpenWidth}px`; - this.element.style.marginLeft = `-${quickOpenWidth / 2}px`; - - // input field - this.inputContainer.style.width = `${quickOpenWidth - 12}px`; - } - } - - private gainingFocus(): void { - this.isLoosingFocus = false; - } - - private loosingFocus(e: FocusEvent): void { - if (!this.isVisible()) { - return; - } - - const relatedTarget = e.relatedTarget as HTMLElement; - if (!this.quickNavigateConfiguration && DOM.isAncestor(relatedTarget, this.element)) { - return; // user clicked somewhere into quick open widget, do not close thereby - } - - this.isLoosingFocus = true; - setTimeout(() => { - if (!this.isLoosingFocus || this.isDisposed) { - return; - } - - const veto = this.callbacks.onFocusLost && this.callbacks.onFocusLost(); - if (!veto) { - this.hide(HideReason.FOCUS_LOST); - } - }, 0); - } - - dispose(): void { - super.dispose(); - - this.isDisposed = true; - } -} diff --git a/src/vs/base/parts/quickopen/browser/quickopen.css b/src/vs/base/parts/quickopen/browser/quickopen.css deleted file mode 100644 index d7e2f8a242..0000000000 --- a/src/vs/base/parts/quickopen/browser/quickopen.css +++ /dev/null @@ -1,169 +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 { - position: absolute; - width: 600px; - z-index: 2000; - padding-bottom: 6px; - left: 50%; - margin-left: -300px; -} - -.monaco-quick-open-widget .monaco-progress-container { - position: absolute; - left: 0; - top: 38px; - z-index: 1; - height: 2px; -} - -.monaco-quick-open-widget .monaco-progress-container .progress-bit { - height: 2px; -} - -.monaco-quick-open-widget .quick-open-input { - width: 588px; - border: none; - margin: 6px; -} - -.monaco-quick-open-widget .quick-open-input .monaco-inputbox { - width: 100%; - height: 25px; -} - -.monaco-quick-open-widget .quick-open-result-count { - position: absolute; - left: -10000px; -} - -.monaco-quick-open-widget .quick-open-tree { - line-height: 22px; -} - -.monaco-quick-open-widget .quick-open-tree .monaco-tree-row > .content > .sub-content { - overflow: hidden; -} - -.monaco-quick-open-widget.content-changing .quick-open-tree .monaco-scrollable-element .slider { - display: none; /* scrollbar slider causes some hectic updates when input changes quickly, so hide it while quick open changes */ -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry { - overflow: hidden; - text-overflow: ellipsis; - display: flex; - flex-direction: column; - height: 100%; -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry > .quick-open-row { - display: flex; - align-items: center; -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon { - overflow: hidden; - width: 16px; - height: 16px; - margin-right: 4px; - display: flex; - align-items: center; - vertical-align: middle; - flex-shrink: 0; -} - -.monaco-quick-open-widget .quick-open-tree .monaco-icon-label, -.monaco-quick-open-widget .quick-open-tree .monaco-icon-label .monaco-icon-label-container > .monaco-icon-name-container { - flex: 1; /* make sure the icon label grows within the row */ -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .monaco-highlighted-label span { - opacity: 1; -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry .monaco-highlighted-label .codicon { - vertical-align: sub; /* vertically align codicon */ -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry-meta { - opacity: 0.7; - line-height: normal; -} - -.monaco-quick-open-widget .quick-open-tree .content.has-group-label .quick-open-entry-keybinding { - margin-right: 8px; -} - -.monaco-quick-open-widget .quick-open-tree .quick-open-entry-keybinding .monaco-keybinding-key { - vertical-align: text-bottom; -} - -.monaco-quick-open-widget .quick-open-tree .results-group { - margin-right: 18px; -} - -.monaco-quick-open-widget .quick-open-tree .monaco-tree-row.focused > .content.has-actions > .results-group, -.monaco-quick-open-widget .quick-open-tree .monaco-tree-row:hover:not(.highlighted) > .content.has-actions > .results-group, -.monaco-quick-open-widget .quick-open-tree .focused .monaco-tree-row.focused > .content.has-actions > .results-group { - margin-right: 0px; -} - -.monaco-quick-open-widget .quick-open-tree .results-group-separator { - border-top-width: 1px; - border-top-style: solid; - box-sizing: border-box; - margin-left: -11px; - padding-left: 11px; -} - -/* Actions in Quick Open Items */ - -.monaco-tree .monaco-tree-row > .content.actions { - position: relative; - display: flex; -} - -.monaco-tree .monaco-tree-row > .content.actions > .sub-content { - flex: 1; -} - -.monaco-tree .monaco-tree-row > .content.actions .action-item { - margin: 0; -} - -.monaco-tree .monaco-tree-row > .content.actions > .primary-action-bar { - line-height: 22px; -} - -.monaco-tree .monaco-tree-row > .content.actions > .primary-action-bar { - display: none; - padding: 0 0.8em 0 0.4em; -} - -.monaco-tree .monaco-tree-row.focused > .content.has-actions > .primary-action-bar { - width: 0; /* in order to support a11y with keyboard, we use width: 0 to hide the actions, which still allows to "Tab" into the actions */ - display: block; -} - -.monaco-tree .monaco-tree-row:hover:not(.highlighted) > .content.has-actions > .primary-action-bar, -.monaco-tree.focused .monaco-tree-row.focused > .content.has-actions > .primary-action-bar, -.monaco-tree .monaco-tree-row > .content.has-actions.more > .primary-action-bar { - width: inherit; - display: block; -} - -.monaco-tree .monaco-tree-row > .content.actions > .primary-action-bar .action-label { - margin-right: 0.4em; - margin-top: 4px; - background-repeat: no-repeat; - width: 16px; - height: 16px; -} - -.monaco-quick-open-widget .quick-open-tree .monaco-highlighted-label .highlight { - font-weight: bold; -} diff --git a/src/vs/base/parts/quickopen/common/quickOpen.ts b/src/vs/base/parts/quickopen/common/quickOpen.ts deleted file mode 100644 index fb7de11df1..0000000000 --- a/src/vs/base/parts/quickopen/common/quickOpen.ts +++ /dev/null @@ -1,95 +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 { ResolvedKeybinding } from 'vs/base/common/keyCodes'; - -export interface IQuickNavigateConfiguration { - keybindings: ResolvedKeybinding[]; -} - -export interface IAutoFocus { - - /** - * The index of the element to focus in the result list. - */ - autoFocusIndex?: number; - - /** - * If set to true, will automatically select the first entry from the result list. - */ - autoFocusFirstEntry?: boolean; - - /** - * If set to true, will automatically select the second entry from the result list. - */ - autoFocusSecondEntry?: boolean; - - /** - * If set to true, will automatically select the last entry from the result list. - */ - autoFocusLastEntry?: boolean; - - /** - * If set to true, will automatically select any entry whose label starts with the search - * value. Since some entries to the top might match the query but not on the prefix, this - * allows to select the most accurate match (matching the prefix) while still showing other - * elements. - */ - autoFocusPrefixMatch?: string; -} - -export const enum Mode { - PREVIEW, - OPEN, - OPEN_IN_BACKGROUND -} - -export interface IEntryRunContext { - event: any; - keymods: IKeyMods; - quickNavigateConfiguration: IQuickNavigateConfiguration | undefined; -} - -export interface IKeyMods { - ctrlCmd: boolean; - alt: boolean; -} - -export interface IDataSource { - getId(entry: T): string; - getLabel(entry: T): string | null; -} - -/** - * See vs/base/parts/tree/browser/tree.ts - IRenderer - */ -export interface IRenderer { - getHeight(entry: T): number; - getTemplateId(entry: T): string; - renderTemplate(templateId: string, container: any /* HTMLElement */, styles: any): any; - renderElement(entry: T, templateId: string, templateData: any, styles: any): void; - disposeTemplate(templateId: string, templateData: any): void; -} - -export interface IFilter { - isVisible(entry: T): boolean; -} - -export interface IAccessiblityProvider { - getAriaLabel(entry: T): string; -} - -export interface IRunner { - run(entry: T, mode: Mode, context: IEntryRunContext): boolean; -} - -export interface IModel { - entries: T[]; - dataSource: IDataSource; - renderer: IRenderer; - runner: IRunner; - filter?: IFilter; - accessibilityProvider?: IAccessiblityProvider; -} diff --git a/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts b/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts deleted file mode 100644 index 3f63ae9f56..0000000000 --- a/src/vs/base/parts/quickopen/test/browser/quickopen.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { DataSource } from 'vs/base/parts/quickopen/browser/quickOpenViewer'; - -suite('QuickOpen', () => { - test('QuickOpenModel', () => { - const model = new QuickOpenModel(); - - const entry1 = new QuickOpenEntry(); - const entry2 = new QuickOpenEntry(); - const entry3 = new QuickOpenEntryGroup(); - - assert.notEqual(entry1.getId(), entry2.getId()); - assert.notEqual(entry2.getId(), entry3.getId()); - - model.addEntries([entry1, entry2, entry3]); - assert.equal(3, model.getEntries().length); - - model.setEntries([entry1, entry2]); - assert.equal(2, model.getEntries().length); - - entry1.setHidden(true); - assert.equal(1, model.getEntries(true).length); - assert.equal(entry2, model.getEntries(true)[0]); - }); - - test('QuickOpenDataSource', async () => { - const model = new QuickOpenModel(); - - const entry1 = new QuickOpenEntry(); - const entry2 = new QuickOpenEntry(); - const entry3 = new QuickOpenEntryGroup(); - - model.addEntries([entry1, entry2, entry3]); - - const ds = new DataSource(model); - assert.equal(entry1.getId(), ds.getId(null!, entry1)); - assert.equal(true, ds.hasChildren(null!, model)); - assert.equal(false, ds.hasChildren(null!, entry1)); - - const children = await ds.getChildren(null!, model); - assert.equal(3, children.length); - }); -}); diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index 689672993f..e1a5292078 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -18,17 +18,17 @@ export enum StorageHint { } export interface IStorageOptions { - hint?: StorageHint; + readonly hint?: StorageHint; } export interface IUpdateRequest { - insert?: Map; - delete?: Set; + readonly insert?: Map; + readonly delete?: Set; } export interface IStorageItemsChangeEvent { - changed?: Map; - deleted?: Set; + readonly changed?: Map; + readonly deleted?: Set; } export interface IStorageDatabase { diff --git a/src/vs/base/parts/storage/node/storage.ts b/src/vs/base/parts/storage/node/storage.ts index 4b72c64392..aaf22e10c3 100644 --- a/src/vs/base/parts/storage/node/storage.ts +++ b/src/vs/base/parts/storage/node/storage.ts @@ -14,7 +14,6 @@ import { IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/b interface IDatabaseConnection { readonly db: Database; - readonly isInMemory: boolean; isErroneous?: boolean; @@ -22,7 +21,7 @@ interface IDatabaseConnection { } export interface ISQLiteStorageDatabaseOptions { - logging?: ISQLiteStorageDatabaseLoggingOptions; + readonly logging?: ISQLiteStorageDatabaseLoggingOptions; } export interface ISQLiteStorageDatabaseLoggingOptions { @@ -45,7 +44,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { private readonly whenConnected = this.connect(this.path); - constructor(private path: string, private options: ISQLiteStorageDatabaseOptions = Object.create(null)) { } + constructor(private readonly path: string, private readonly options: ISQLiteStorageDatabaseOptions = Object.create(null)) { } async getItems(): Promise> { const connection = await this.whenConnected; diff --git a/src/vs/workbench/test/browser/actionRegistry.test.ts b/src/vs/base/test/browser/actionbar.test.ts similarity index 78% rename from src/vs/workbench/test/browser/actionRegistry.test.ts rename to src/vs/base/test/browser/actionbar.test.ts index a10b12630d..5363219378 100644 --- a/src/vs/workbench/test/browser/actionRegistry.test.ts +++ b/src/vs/base/test/browser/actionbar.test.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { prepareActions } from 'vs/workbench/browser/actions'; +import { Separator, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; -suite('Workbench action registry', () => { +suite('Actionbar', () => { - test('Workbench Action Bar prepareActions()', function () { + test('prepareActions()', function () { let a1 = new Separator(); let a2 = new Separator(); let a3 = new Action('a3'); diff --git a/src/vs/base/test/common/codicon.test.ts b/src/vs/base/test/common/codicon.test.ts index be8708e57c..da0fcb4d20 100644 --- a/src/vs/base/test/common/codicon.test.ts +++ b/src/vs/base/test/common/codicon.test.ts @@ -2,9 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import { IMatch } from 'vs/base/common/filters'; import { matchesFuzzyCodiconAware, parseCodicons, IParsedCodicons } from 'vs/base/common/codicon'; +import { stripCodicons } from 'vs/base/common/codicons'; export interface ICodiconFilter { // Returns null if word doesn't match. @@ -64,3 +66,13 @@ suite('Codicon', () => { ]); }); }); + +suite('Codicons', () => { + + test('stripCodicons', () => { + assert.equal(stripCodicons('Hello World'), 'Hello World'); + assert.equal(stripCodicons('$(Hello World'), '$(Hello World'); + assert.equal(stripCodicons('$(Hello) World'), ' World'); + assert.equal(stripCodicons('$(Hello) W$(oi)rld'), ' Wrld'); + }); +}); diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index f6a62ce54c..0ed3dca5c4 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -43,7 +43,7 @@ class NullAccessorClass implements scorer.IItemAccessor { } function _doScore(target: string, query: string, fuzzy: boolean): scorer.Score { - return scorer.score(target, query, query.toLowerCase(), fuzzy); + return scorer.score(target, scorer.prepareQuery(query), fuzzy); } function scoreItem(item: T, query: string, fuzzy: boolean, accessor: scorer.IItemAccessor, cache: scorer.ScorerCache): scorer.IItemScore { @@ -109,6 +109,42 @@ suite('Fuzzy Scorer', () => { assert.equal(_doScore(target, 'eo', false)[0], 0); }); + test('score (fuzzy, multiple)', function () { + const target = 'HeLlo-World'; + + const [firstSingleScore, firstSinglePositions] = _doScore(target, 'HelLo', true); + const [secondSingleScore, secondSinglePositions] = _doScore(target, 'World', true); + const firstAndSecondSinglePositions = [...firstSinglePositions, ...secondSinglePositions]; + + let [multiScore, multiPositions] = _doScore(target, 'HelLo World', true); + + function assertScore() { + assert.ok(multiScore >= firstSingleScore + secondSingleScore); + for (let i = 0; i < multiPositions.length; i++) { + assert.equal(multiPositions[i], firstAndSecondSinglePositions[i]); + } + } + + function assertNoScore() { + assert.equal(multiScore, 0); + assert.equal(multiPositions.length, 0); + } + + assertScore(); + + [multiScore, multiPositions] = _doScore(target, 'World HelLo', true); + assertScore(); + + [multiScore, multiPositions] = _doScore(target, 'World HelLo World', true); + assertScore(); + + [multiScore, multiPositions] = _doScore(target, 'World HelLo Nothing', true); + assertNoScore(); + + [multiScore, multiPositions] = _doScore(target, 'More Nothing', true); + assertNoScore(); + }); + test('scoreItem - matches are proper', function () { let res = scoreItem(null, 'something', true, ResourceAccessor, cache); assert.ok(!res.score); @@ -820,11 +856,42 @@ suite('Fuzzy Scorer', () => { assert.equal(res[0], resourceB); }); - test('prepareSearchForScoring', () => { + test('prepareQuery', () => { assert.equal(scorer.prepareQuery(' f*a ').value, 'fa'); + assert.equal(scorer.prepareQuery('model Tester.ts').original, 'model Tester.ts'); + assert.equal(scorer.prepareQuery('model Tester.ts').originalLowercase, 'model Tester.ts'.toLowerCase()); assert.equal(scorer.prepareQuery('model Tester.ts').value, 'modelTester.ts'); - assert.equal(scorer.prepareQuery('Model Tester.ts').lowercase, 'modeltester.ts'); + assert.equal(scorer.prepareQuery('Model Tester.ts').valueLowercase, 'modeltester.ts'); assert.equal(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false); assert.equal(scorer.prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true); + + // with spaces + let query = scorer.prepareQuery('He*llo World'); + assert.equal(query.original, 'He*llo World'); + assert.equal(query.value, 'HelloWorld'); + assert.equal(query.valueLowercase, 'HelloWorld'.toLowerCase()); + assert.equal(query.values?.length, 2); + assert.equal(query.values?.[0].original, 'He*llo'); + assert.equal(query.values?.[0].value, 'Hello'); + assert.equal(query.values?.[0].valueLowercase, 'Hello'.toLowerCase()); + assert.equal(query.values?.[1].original, 'World'); + assert.equal(query.values?.[1].value, 'World'); + assert.equal(query.values?.[1].valueLowercase, 'World'.toLowerCase()); + + // with spaces that are empty + query = scorer.prepareQuery(' Hello World '); + assert.equal(query.original, ' Hello World '); + assert.equal(query.originalLowercase, ' Hello World '.toLowerCase()); + assert.equal(query.value, 'HelloWorld'); + assert.equal(query.valueLowercase, 'HelloWorld'.toLowerCase()); + assert.equal(query.values?.length, 2); + assert.equal(query.values?.[0].original, 'Hello'); + assert.equal(query.values?.[0].originalLowercase, 'Hello'.toLowerCase()); + assert.equal(query.values?.[0].value, 'Hello'); + assert.equal(query.values?.[0].valueLowercase, 'Hello'.toLowerCase()); + assert.equal(query.values?.[1].original, 'World'); + assert.equal(query.values?.[1].originalLowercase, 'World'.toLowerCase()); + assert.equal(query.values?.[1].value, 'World'); + assert.equal(query.values?.[1].valueLowercase, 'World'.toLowerCase()); }); }); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4cf1232f3c..560bf444a5 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -171,7 +171,7 @@ export class CodeApplication extends Disposable { return false; } - if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') { + if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%20role%3D%22document%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') { return true; } diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 61f3782e69..c13e19d9b9 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -457,6 +457,9 @@ export class TextAreaHandler extends ViewPart { this.textArea.setAttribute('aria-autocomplete', 'both'); this.textArea.removeAttribute('aria-activedescendant'); } + if (options.role) { + this.textArea.setAttribute('role', options.role); + } } // --- end view API diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 3b98b6b3eb..19baa090ad 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -333,6 +333,7 @@ export interface IOverviewRuler { */ export interface IEditorAriaOptions { activeDescendant: string | undefined; + role?: string; } /** diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 805fb5e64f..0a69cd89c2 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -14,17 +14,17 @@ import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, Overview import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IColorTheme, IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService'; -class RefCountedStyleSheet { +export class RefCountedStyleSheet { private readonly _parent: CodeEditorServiceImpl; private readonly _editorId: string; - public readonly styleSheet: HTMLStyleElement; + private readonly _styleSheet: HTMLStyleElement; private _refCount: number; constructor(parent: CodeEditorServiceImpl, editorId: string, styleSheet: HTMLStyleElement) { this._parent = parent; this._editorId = editorId; - this.styleSheet = styleSheet; + this._styleSheet = styleSheet; this._refCount = 0; } @@ -35,17 +35,26 @@ class RefCountedStyleSheet { public unref(): void { this._refCount--; if (this._refCount === 0) { - this.styleSheet.parentNode?.removeChild(this.styleSheet); + this._styleSheet.parentNode?.removeChild(this._styleSheet); this._parent._removeEditorStyleSheets(this._editorId); } } + + public insertRule(rule: string, index?: number): void { + const sheet = this._styleSheet.sheet; + sheet.insertRule(rule, index); + } + + public removeRulesContainingSelector(ruleName: string): void { + dom.removeCSSRulesContainingSelector(ruleName, this._styleSheet); + } } -class GlobalStyleSheet { - public readonly styleSheet: HTMLStyleElement; +export class GlobalStyleSheet { + private readonly _styleSheet: HTMLStyleElement; constructor(styleSheet: HTMLStyleElement) { - this.styleSheet = styleSheet; + this._styleSheet = styleSheet; } public ref(): void { @@ -53,6 +62,15 @@ class GlobalStyleSheet { public unref(): void { } + + public insertRule(rule: string, index?: number): void { + const sheet = this._styleSheet.sheet; + sheet.insertRule(rule, index); + } + + public removeRulesContainingSelector(ruleName: string): void { + dom.removeCSSRulesContainingSelector(ruleName, this._styleSheet); + } } export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { @@ -62,9 +80,9 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { private readonly _editorStyleSheets = new Map(); private readonly _themeService: IThemeService; - constructor(@IThemeService themeService: IThemeService, styleSheet: HTMLStyleElement | null = null) { + constructor(@IThemeService themeService: IThemeService, styleSheet: GlobalStyleSheet | null = null) { super(); - this._globalStyleSheet = styleSheet ? new GlobalStyleSheet(styleSheet) : null; + this._globalStyleSheet = styleSheet ? styleSheet : null; this._themeService = themeService; } @@ -100,7 +118,7 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { if (!provider) { const styleSheet = this._getOrCreateStyleSheet(editor); const providerArgs: ProviderArguments = { - styleSheet: styleSheet.styleSheet, + styleSheet: styleSheet, key: key, parentTypeKey: parentTypeKey, options: options || Object.create(null) @@ -188,7 +206,7 @@ class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvide } interface ProviderArguments { - styleSheet: HTMLStyleElement; + styleSheet: GlobalStyleSheet | RefCountedStyleSheet; key: string; parentTypeKey?: string; options: IDecorationRenderOptions; @@ -330,7 +348,7 @@ class DecorationCSSRules { private readonly _providerArgs: ProviderArguments; private _usesThemeColors: boolean; - public constructor(ruleType: ModelDecorationCSSRuleType, providerArgs: ProviderArguments, themeService: IThemeService) { + constructor(ruleType: ModelDecorationCSSRuleType, providerArgs: ProviderArguments, themeService: IThemeService) { this._theme = themeService.getColorTheme(); this._ruleType = ruleType; this._providerArgs = providerArgs; @@ -414,7 +432,7 @@ class DecorationCSSRules { default: throw new Error('Unknown rule type: ' + this._ruleType); } - const sheet = this._providerArgs.styleSheet.sheet; + const sheet = this._providerArgs.styleSheet; let hasContent = false; if (unthemedCSS.length > 0) { @@ -433,7 +451,7 @@ class DecorationCSSRules { } private _removeCSS(): void { - dom.removeCSSRulesContainingSelector(this._unThemedSelector, this._providerArgs.styleSheet); + this._providerArgs.styleSheet.removeRulesContainingSelector(this._unThemedSelector); } /** diff --git a/src/vs/editor/common/model/editStack.ts b/src/vs/editor/common/model/editStack.ts index 7cfb281313..daf740d1ff 100644 --- a/src/vs/editor/common/model/editStack.ts +++ b/src/vs/editor/common/model/editStack.ts @@ -40,9 +40,9 @@ class SingleModelEditStackData { public changes: TextChange[] ) { } - public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { - if (operations.length > 0) { - this.changes = compressConsecutiveTextChanges(this.changes, operations.map(op => op.textChange)); + public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { + if (textChanges.length > 0) { + this.changes = compressConsecutiveTextChanges(this.changes, textChanges); } this.afterEOL = afterEOL; this.afterVersionId = afterVersionId; @@ -168,9 +168,9 @@ export class SingleModelEditStackElement implements IResourceUndoRedoElement { return (this.model === model && this._data instanceof SingleModelEditStackData); } - public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { + public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { if (this._data instanceof SingleModelEditStackData) { - this._data.append(model, operations, afterEOL, afterVersionId, afterCursorState); + this._data.append(model, textChanges, afterEOL, afterVersionId, afterCursorState); } } @@ -258,10 +258,10 @@ export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement { return false; } - public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { + public append(model: ITextModel, textChanges: TextChange[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { const key = uriGetComparisonKey(model.uri); const editStackElement = this._editStackElementsMap.get(key)!; - editStackElement.append(model, operations, afterEOL, afterVersionId, afterCursorState); + editStackElement.append(model, textChanges, afterEOL, afterVersionId, afterCursorState); } public close(): void { @@ -355,7 +355,14 @@ export class EditStack { const editStackElement = this._getOrCreateEditStackElement(beforeCursorState); const inverseEditOperations = this._model.applyEdits(editOperations, true); const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations); - editStackElement.append(this._model, inverseEditOperations, getModelEOL(this._model), this._model.getAlternativeVersionId(), afterCursorState); + const textChanges = inverseEditOperations.map((op, index) => ({ index: index, textChange: op.textChange })); + textChanges.sort((a, b) => { + if (a.textChange.oldPosition === b.textChange.oldPosition) { + return a.index - b.index; + } + return a.textChange.oldPosition - b.textChange.oldPosition; + }); + editStackElement.append(this._model, textChanges.map(op => op.textChange), getModelEOL(this._model), this._model.getAlternativeVersionId(), afterCursorState); return afterCursorState; } diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index f5ea8e56a2..5338f9d530 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -661,12 +661,14 @@ const enum Constants { class HashTableEntry { public readonly tokenTypeIndex: number; public readonly tokenModifierSet: number; + public readonly languageId: number; public readonly metadata: number; public next: HashTableEntry | null; - constructor(tokenTypeIndex: number, tokenModifierSet: number, metadata: number) { + constructor(tokenTypeIndex: number, tokenModifierSet: number, languageId: number, metadata: number) { this.tokenTypeIndex = tokenTypeIndex; this.tokenModifierSet = tokenModifierSet; + this.languageId = languageId; this.metadata = metadata; this.next = null; } @@ -697,16 +699,17 @@ class HashTable { } } - private _hashFunc(tokenTypeIndex: number, tokenModifierSet: number): number { - return ((((tokenTypeIndex << 5) - tokenTypeIndex) + tokenModifierSet) | 0) % this._currentLength; // tokenTypeIndex * 31 + tokenModifierSet, keep as int32 + private _hashFunc(tokenTypeIndex: number, tokenModifierSet: number, languageId: number): number { + const hash = (n1: number, n2: number) => (((n1 << 5) - n1) + n2) | 0; // n1 * 31 + n2, keep as int32 + return hash(hash(tokenTypeIndex, tokenModifierSet), languageId) % this._currentLength; } - public get(tokenTypeIndex: number, tokenModifierSet: number): HashTableEntry | null { - const hash = this._hashFunc(tokenTypeIndex, tokenModifierSet); + public get(tokenTypeIndex: number, tokenModifierSet: number, languageId: number): HashTableEntry | null { + const hash = this._hashFunc(tokenTypeIndex, tokenModifierSet, languageId); let p = this._elements[hash]; while (p) { - if (p.tokenTypeIndex === tokenTypeIndex && p.tokenModifierSet === tokenModifierSet) { + if (p.tokenTypeIndex === tokenTypeIndex && p.tokenModifierSet === tokenModifierSet && p.languageId === languageId) { return p; } p = p.next; @@ -715,7 +718,7 @@ class HashTable { return null; } - public add(tokenTypeIndex: number, tokenModifierSet: number, metadata: number): void { + public add(tokenTypeIndex: number, tokenModifierSet: number, languageId: number, metadata: number): void { this._elementsCount++; if (this._growCount !== 0 && this._elementsCount >= this._growCount) { // expand! @@ -737,11 +740,11 @@ class HashTable { } } } - this._add(new HashTableEntry(tokenTypeIndex, tokenModifierSet, metadata)); + this._add(new HashTableEntry(tokenTypeIndex, tokenModifierSet, languageId, metadata)); } private _add(element: HashTableEntry): void { - const hash = this._hashFunc(element.tokenTypeIndex, element.tokenModifierSet); + const hash = this._hashFunc(element.tokenTypeIndex, element.tokenModifierSet, element.languageId); element.next = this._elements[hash]; this._elements[hash] = element; } @@ -759,8 +762,8 @@ class SemanticColoringProviderStyling { this._hashTable = new HashTable(); } - public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number { - const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet); + public getMetadata(tokenTypeIndex: number, tokenModifierSet: number, languageId: LanguageIdentifier): number { + const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet, languageId.id); let metadata: number; if (entry) { metadata = entry.metadata; @@ -775,7 +778,7 @@ class SemanticColoringProviderStyling { modifierSet = modifierSet >> 1; } - const tokenStyle = this._themeService.getColorTheme().getTokenStyleMetadata(tokenType, tokenModifiers); + const tokenStyle = this._themeService.getColorTheme().getTokenStyleMetadata(tokenType, tokenModifiers, languageId.language); if (typeof tokenStyle === 'undefined') { metadata = Constants.NO_STYLING; } else { @@ -801,7 +804,7 @@ class SemanticColoringProviderStyling { metadata = Constants.NO_STYLING; } } - this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); + this._hashTable.add(tokenTypeIndex, tokenModifierSet, languageId.id, metadata); } if (this._logService.getLevel() === LogLevel.Trace) { const type = this._legend.tokenTypes[tokenTypeIndex]; @@ -1042,6 +1045,8 @@ class ModelSemanticColoring extends Disposable { const result: MultilineTokens2[] = []; + const languageId = this._model.getLanguageIdentifier(); + let tokenIndex = 0; let lastLineNumber = 1; let lastStartCharacter = 0; @@ -1081,7 +1086,7 @@ class ModelSemanticColoring extends Disposable { const length = srcData[srcOffset + 2]; const tokenTypeIndex = srcData[srcOffset + 3]; const tokenModifierSet = srcData[srcOffset + 4]; - const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet); + const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet, languageId); if (metadata !== Constants.NO_STYLING) { if (areaLine === 0) { diff --git a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css index 586448c7f7..cd30029b08 100644 --- a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css +++ b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css @@ -20,10 +20,6 @@ color: var(--outline-element-color); } -.monaco-tree .monaco-tree-row.focused .outline-element .outline-element-detail { - visibility: inherit; -} - .monaco-list .outline-element .outline-element-decoration { opacity: 0.75; font-size: 90%; diff --git a/src/vs/editor/contrib/quickOpen/quickOpen.ts b/src/vs/editor/contrib/gotoSymbol/documentSymbols.ts similarity index 99% rename from src/vs/editor/contrib/quickOpen/quickOpen.ts rename to src/vs/editor/contrib/gotoSymbol/documentSymbols.ts index b899f089ff..2520b7c094 100644 --- a/src/vs/editor/contrib/quickOpen/quickOpen.ts +++ b/src/vs/editor/contrib/gotoSymbol/documentSymbols.ts @@ -62,7 +62,6 @@ function flatten(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideCo } } - CommandsRegistry.registerCommand('_executeDocumentSymbolProvider', async function (accessor, ...args) { const [resource] = args; assertType(URI.isUri(resource)); diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 644a171e51..8e5af6ec93 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -241,7 +241,7 @@ export class ChangeIndentationSizeAction extends EditorAction { } } }); - }, 50/* quick open is sensitive to being opened so soon after another */); + }, 50/* quick input is sensitive to being opened so soon after another */); } } diff --git a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts index c5b306ff8e..a5dd05576f 100644 --- a/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/editorNavigationQuickAccess.ts @@ -72,7 +72,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu // Remember view state and update it when the cursor position // changes even later because it could be that the user has - // configured quick open to remain open when focus is lost and + // configured quick access to remain open when focus is lost and // we always want to restore the current location. let lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); disposables.add(codeEditor.onDidChangeCursorPosition(() => { diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts index dfac041741..b402c4476e 100644 --- a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -17,6 +17,7 @@ import { values } from 'vs/base/common/collections'; import { trim, format } from 'vs/base/common/strings'; import { fuzzyScore, FuzzyScore, createMatches } from 'vs/base/common/filters'; import { assign } from 'vs/base/common/objects'; +import { prepareQuery, IPreparedQuery } from 'vs/base/common/fuzzyScorer'; export interface IGotoSymbolQuickPickItem extends IQuickPickItem { kind: SymbolKind, @@ -155,7 +156,7 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit // Collect symbol picks picker.busy = true; try { - const items = await this.doGetSymbolPicks(symbolsPromise, picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim(), picksCts.token); + const items = await this.doGetSymbolPicks(symbolsPromise, prepareQuery(picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim()), undefined, picksCts.token); if (token.isCancellationRequested) { return; } @@ -194,18 +195,24 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit return disposables; } - protected async doGetSymbolPicks(symbolsPromise: Promise, filter: string, token: CancellationToken): Promise> { + protected async doGetSymbolPicks(symbolsPromise: Promise, query: IPreparedQuery, options: { extraContainerLabel?: string } | undefined, token: CancellationToken): Promise> { const symbols = await symbolsPromise; if (token.isCancellationRequested) { return []; } - // Normalize filter - const filterBySymbolKind = filter.indexOf(AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX) === 0; + const filterBySymbolKind = query.original.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(); + + // Split between symbol and container query if separated by space + let symbolQuery: IPreparedQuery; + let containerQuery: IPreparedQuery | undefined; + if (query.values && query.values.length > 1) { + symbolQuery = prepareQuery(query.values[0].original); + containerQuery = prepareQuery(query.values[1].original); + } else { + symbolQuery = query; + } // Convert to symbol picks and apply filtering const filteredSymbolPicks: IGotoSymbolQuickPickItem[] = []; @@ -213,22 +220,28 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit const symbol = symbols[index]; const symbolLabel = trim(symbol.name); - const containerLabel = symbol.containerName; + + let containerLabel = symbol.containerName; + if (containerLabel && options?.extraContainerLabel) { + containerLabel = `${options.extraContainerLabel} • ${containerLabel}`; + } else { + containerLabel = options?.extraContainerLabel; + } let symbolScore: FuzzyScore | undefined = undefined; let containerScore: FuzzyScore | undefined = undefined; let includeSymbol = true; - if (filter.length > filterPos) { + if (query.original.length > filterPos) { // Score by symbol - symbolScore = fuzzyScore(symbolFilter, symbolFilterLow, filterPos, symbolLabel, symbolLabel.toLowerCase(), 0, true); + symbolScore = fuzzyScore(symbolQuery.original, symbolQuery.originalLowercase, filterPos, symbolLabel, symbolLabel.toLowerCase(), 0, true); includeSymbol = !!symbolScore; // Score by container if specified - if (includeSymbol && containerFilter && containerFilterLow) { + if (includeSymbol && containerQuery) { if (containerLabel) { - containerScore = fuzzyScore(containerFilter, containerFilterLow, filterPos, containerLabel, containerLabel.toLowerCase(), 0, true); + containerScore = fuzzyScore(containerQuery.original, containerQuery.originalLowercase, filterPos, containerLabel, containerLabel.toLowerCase(), 0, true); } includeSymbol = !!containerScore; diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index b4f31645e7..69bf93cc24 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -401,12 +401,13 @@ class WordHighlighter { private renderDecorations(): void { this.renderDecorationsTimer = -1; let decorations: IModelDeltaDecoration[] = []; - for (let i = 0, len = this.workerRequestValue.length; i < len; i++) { - let info = this.workerRequestValue[i]; - decorations.push({ - range: info.range, - options: WordHighlighter._getDecorationOptions(info.kind) - }); + for (const info of this.workerRequestValue) { + if (info.range) { + decorations.push({ + range: info.range, + options: WordHighlighter._getDecorationOptions(info.kind) + }); + } } this._decorationIds = this.editor.deltaDecorations(this._decorationIds, decorations); diff --git a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts index fc5a098f45..07da036245 100644 --- a/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts +++ b/src/vs/editor/standalone/browser/quickInput/standaloneQuickInputServiceImpl.ts @@ -129,10 +129,6 @@ export class StandaloneQuickInputServiceImpl implements IQuickInputService { cancel(): Promise { return this.activeService.cancel(); } - - hide(focusLost?: boolean | undefined): void { - return this.activeService.hide(focusLost); - } } export class QuickInputEditorContribution implements IEditorContribution { diff --git a/src/vs/editor/standalone/browser/standalone-tokens.css b/src/vs/editor/standalone/browser/standalone-tokens.css index 7c9285c908..71a10570f7 100644 --- a/src/vs/editor/standalone/browser/standalone-tokens.css +++ b/src/vs/editor/standalone/browser/standalone-tokens.css @@ -134,26 +134,6 @@ box-sizing: border-box; } - /* tree */ - .monaco-editor.vs .monaco-tree .monaco-tree-row, - .monaco-editor.vs-dark .monaco-tree .monaco-tree-row { - -ms-high-contrast-adjust: none; - color: windowtext !important; - } - .monaco-editor.vs .monaco-tree .monaco-tree-row.selected, - .monaco-editor.vs-dark .monaco-tree .monaco-tree-row.selected, - .monaco-editor.vs .monaco-tree .monaco-tree-row.focused, - .monaco-editor.vs-dark .monaco-tree .monaco-tree-row.focused { - color: highlighttext !important; - background-color: highlight !important; - } - .monaco-editor.vs .monaco-tree .monaco-tree-row:hover, - .monaco-editor.vs-dark .monaco-tree .monaco-tree-row:hover { - background: transparent !important; - border: 1px solid highlight; - box-sizing: border-box; - } - /* scrollbars */ .monaco-editor.vs .monaco-scrollable-element > .scrollbar, .monaco-editor.vs-dark .monaco-scrollable-element > .scrollbar { diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index af4e7fd70f..16a17ac2e6 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -131,7 +131,7 @@ class StandaloneTheme implements IStandaloneTheme { return this._tokenTheme; } - public getTokenStyleMetadata(type: string, modifiers: string[]): ITokenStyle | undefined { + public getTokenStyleMetadata(type: string, modifiers: string[], modelLanguage: string): ITokenStyle | undefined { return undefined; } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 17d9a277ed..83e87eef96 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -56,7 +56,7 @@ suite('TokenizationSupport2Adapter', () => { throw new Error('Not implemented'); }, - getTokenStyleMetadata: (type: string, modifiers: string[]): ITokenStyle | undefined => { + getTokenStyleMetadata: (type: string, modifiers: string[], modelLanguage: string): ITokenStyle | undefined => { return undefined; }, diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index ae0c191a01..17598ea4dc 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -5616,4 +5616,24 @@ suite('Undo stops', () => { }); }); + test('issue #93585: Undo multi cursor edit corrupts document', () => { + let model = createTextModel( + [ + 'hello world', + 'hello world', + ].join('\n') + ); + + withTestCodeEditor(null, { model: model }, (editor, cursor) => { + cursor.setSelections('test', [ + new Selection(2, 7, 2, 12), + new Selection(1, 7, 1, 12), + ]); + cursorCommand(cursor, H.Type, { text: 'no' }, 'keyboard'); + assert.equal(model.getValue(), 'hello no\nhello no'); + + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + assert.equal(model.getValue(), 'hello world\nhello world'); + }); + }); }); diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index 57d8b46e15..6058c2b55c 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -4,18 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as dom from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { CodeEditorServiceImpl } from 'vs/editor/browser/services/codeEditorServiceImpl'; +import { CodeEditorServiceImpl, GlobalStyleSheet } from 'vs/editor/browser/services/codeEditorServiceImpl'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { TestColorTheme, TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; const themeServiceMock = new TestThemeService(); -export class TestCodeEditorServiceImpl extends CodeEditorServiceImpl { +class TestCodeEditorServiceImpl extends CodeEditorServiceImpl { getActiveCodeEditor(): ICodeEditor | null { return null; } @@ -25,6 +24,32 @@ export class TestCodeEditorServiceImpl extends CodeEditorServiceImpl { } } +class TestGlobalStyleSheet extends GlobalStyleSheet { + + public rules: string[] = []; + + constructor() { + super(null!); + } + + public insertRule(rule: string, index?: number): void { + this.rules.unshift(rule); + } + + public removeRulesContainingSelector(ruleName: string): void { + for (let i = 0; i < this.rules.length; i++) { + if (this.rules[i].indexOf(ruleName) >= 0) { + this.rules.splice(i, 1); + i--; + } + } + } + + public read(): string { + return this.rules.join('\n'); + } +} + suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suite let options: IDecorationRenderOptions = { gutterIconPath: URI.parse('https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png'), @@ -45,59 +70,45 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit assert.throws(() => s.resolveDecorationOptions('example', false)); }); - function readStyleSheet(styleSheet: HTMLStyleElement): string { - if ((styleSheet.sheet).rules) { - return Array.prototype.map.call((styleSheet.sheet).rules, (r: { cssText: string }) => r.cssText).join('\n'); - } - return styleSheet.sheet!.toString(); + function readStyleSheet(styleSheet: TestGlobalStyleSheet): string { + return styleSheet.read(); } test('css properties', () => { - let styleSheet = dom.createStyleSheet(); - let s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); + const styleSheet = new TestGlobalStyleSheet(); + const s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); s.registerDecorationType('example', options); - let sheet = readStyleSheet(styleSheet); - assert( - sheet.indexOf('background: url(\'https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png") center center / contain no-repeat;') > 0 - || sheet.indexOf('background-image: url("https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png"); background-size: contain; background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); - assert(sheet.indexOf('border-color: yellow;') > 0); - assert(sheet.indexOf('background-color: red;') > 0); + const sheet = readStyleSheet(styleSheet); + assert(sheet.indexOf(`{background:url('https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png') center center no-repeat;background-size:contain;}`) >= 0); + assert(sheet.indexOf(`{background-color:red;border-color:yellow;box-sizing: border-box;}`) >= 0); }); test('theme color', () => { - let options: IDecorationRenderOptions = { + const options: IDecorationRenderOptions = { backgroundColor: { id: 'editorBackground' }, borderColor: { id: 'editorBorder' }, }; - let colors: { [key: string]: string } = { + + const styleSheet = new TestGlobalStyleSheet(); + const themeService = new TestThemeService(new TestColorTheme({ editorBackground: '#FF0000' - }; - - let styleSheet = dom.createStyleSheet(); - let themeService = new TestThemeService(new TestColorTheme(colors)); - let s = new TestCodeEditorServiceImpl(themeService, styleSheet); + })); + const s = new TestCodeEditorServiceImpl(themeService, styleSheet); s.registerDecorationType('example', options); - let sheet = readStyleSheet(styleSheet); - assert.equal(sheet, '.monaco-editor .ced-example-0 { background-color: rgb(255, 0, 0); border-color: transparent; box-sizing: border-box; }'); + assert.equal(readStyleSheet(styleSheet), '.monaco-editor .ced-example-0 {background-color:#ff0000;border-color:transparent;box-sizing: border-box;}'); - colors = { + themeService.setTheme(new TestColorTheme({ editorBackground: '#EE0000', editorBorder: '#00FFFF' - }; - themeService.setTheme(new TestColorTheme(colors)); - sheet = readStyleSheet(styleSheet); - assert.equal(sheet, '.monaco-editor .ced-example-0 { background-color: rgb(238, 0, 0); border-color: rgb(0, 255, 255); box-sizing: border-box; }'); + })); + assert.equal(readStyleSheet(styleSheet), '.monaco-editor .ced-example-0 {background-color:#ee0000;border-color:#00ffff;box-sizing: border-box;}'); s.removeDecorationType('example'); - sheet = readStyleSheet(styleSheet); - assert.equal(sheet, ''); - + assert.equal(readStyleSheet(styleSheet), ''); }); test('theme overrides', () => { - let options: IDecorationRenderOptions = { + const options: IDecorationRenderOptions = { color: { id: 'editorBackground' }, light: { color: '#FF00FF' @@ -109,86 +120,59 @@ suite.skip('Decoration Render Options', () => { // {{SQL CARBON EDIT}} skip suit } } }; - let colors: { [key: string]: string } = { + + const styleSheet = new TestGlobalStyleSheet(); + const themeService = new TestThemeService(new TestColorTheme({ editorBackground: '#FF0000', infoForeground: '#444444' - }; - - let styleSheet = dom.createStyleSheet(); - let themeService = new TestThemeService(new TestColorTheme(colors)); - let s = new TestCodeEditorServiceImpl(themeService, styleSheet); + })); + const s = new TestCodeEditorServiceImpl(themeService, styleSheet); s.registerDecorationType('example', options); - let sheet = readStyleSheet(styleSheet); - let expected = - '.vs-dark.monaco-editor .ced-example-4::after, .hc-black.monaco-editor .ced-example-4::after { color: rgb(68, 68, 68) !important; }\n' + - '.vs-dark.monaco-editor .ced-example-1, .hc-black.monaco-editor .ced-example-1 { color: rgb(0, 0, 0) !important; }\n' + - '.vs.monaco-editor .ced-example-1 { color: rgb(255, 0, 255) !important; }\n' + - '.monaco-editor .ced-example-1 { color: rgb(255, 0, 0) !important; }'; - assert.equal(sheet, expected); + const expected = [ + '.vs-dark.monaco-editor .ced-example-4::after, .hc-black.monaco-editor .ced-example-4::after {color:#444444 !important;}', + '.vs-dark.monaco-editor .ced-example-1, .hc-black.monaco-editor .ced-example-1 {color:#000000 !important;}', + '.vs.monaco-editor .ced-example-1 {color:#FF00FF !important;}', + '.monaco-editor .ced-example-1 {color:#ff0000 !important;}' + ].join('\n'); + assert.equal(readStyleSheet(styleSheet), expected); s.removeDecorationType('example'); - sheet = readStyleSheet(styleSheet); - assert.equal(sheet, ''); + assert.equal(readStyleSheet(styleSheet), ''); }); test('css properties, gutterIconPaths', () => { - let styleSheet = dom.createStyleSheet(); + const styleSheet = new TestGlobalStyleSheet(); + const s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); - // unix file path (used as string) - let s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); - s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/bar.png') }); - let sheet = readStyleSheet(styleSheet);//.innerHTML || styleSheet.sheet.toString(); - assert( - sheet.indexOf('background: url(\'file:///Users/foo/bar.png\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("file:///Users/foo/bar.png") center center no-repeat;') > 0 - || sheet.indexOf('background-image: url("file:///Users/foo/bar.png"); background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); + // URI, only minimal encoding + s.registerDecorationType('example', { gutterIconPath: URI.parse('data:image/svg+xml;base64,PHN2ZyB4b+') }); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('data:image/svg+xml;base64,PHN2ZyB4b+') center center no-repeat;}`) > 0); s.removeDecorationType('example'); - // windows file path (used as string) if (platform.isWindows) { - s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); + // windows file path (used as string) s.registerDecorationType('example', { gutterIconPath: URI.file('c:\\files\\miles\\more.png') }); - sheet = readStyleSheet(styleSheet); - assert( - sheet.indexOf('background: url(\'file:///c%3A/files/miles/more.png\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("file:///c%3A/files/miles/more.png") center center no-repeat;') > 0 - || sheet.indexOf('background: url("file:///c:/files/miles/more.png") center center no-repeat;') > 0 - || sheet.indexOf('background-image: url("file:///c:/files/miles/more.png"); background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///c:/files/miles/more.png') center center no-repeat;}`) > 0); + s.removeDecorationType('example'); + + // single quote must always be escaped/encoded + s.registerDecorationType('example', { gutterIconPath: URI.file('c:\\files\\foo\\b\'ar.png') }); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///c:/files/foo/b%27ar.png') center center no-repeat;}`) > 0); + s.removeDecorationType('example'); + } else { + // unix file path (used as string) + s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/bar.png') }); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///Users/foo/bar.png') center center no-repeat;}`) > 0); + s.removeDecorationType('example'); + + // single quote must always be escaped/encoded + s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/b\'ar.png') }); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('file:///Users/foo/b%27ar.png') center center no-repeat;}`) > 0); s.removeDecorationType('example'); } - // URI, only minimal encoding - s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); - s.registerDecorationType('example', { gutterIconPath: URI.parse('data:image/svg+xml;base64,PHN2ZyB4b+') }); - sheet = readStyleSheet(styleSheet); - assert( - sheet.indexOf('background: url(\'data:image/svg+xml;base64,PHN2ZyB4b+\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("data:image/svg+xml;base64,PHN2ZyB4b+") center center no-repeat;') > 0 - || sheet.indexOf('background-image: url("data:image/svg+xml;base64,PHN2ZyB4b+"); background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); - s.removeDecorationType('example'); - - // single quote must always be escaped/encoded - s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); - s.registerDecorationType('example', { gutterIconPath: URI.file('/Users/foo/b\'ar.png') }); - sheet = readStyleSheet(styleSheet); - assert( - sheet.indexOf('background: url(\'file:///Users/foo/b%27ar.png\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("file:///Users/foo/b%27ar.png") center center no-repeat;') > 0 - || sheet.indexOf('background-image: url("file:///Users/foo/b%27ar.png"); background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); - s.removeDecorationType('example'); - - s = new TestCodeEditorServiceImpl(themeServiceMock, styleSheet); s.registerDecorationType('example', { gutterIconPath: URI.parse('http://test/pa\'th') }); - sheet = readStyleSheet(styleSheet); - assert( - sheet.indexOf('background: url(\'http://test/pa%27th\') center center no-repeat;') > 0 - || sheet.indexOf('background: url("http://test/pa%27th") center center no-repeat;') > 0 - || sheet.indexOf('background-image: url("http://test/pa%27th"); background-position: center center; background-repeat: no-repeat no-repeat;') > 0 - ); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('http://test/pa%27th') center center no-repeat;}`) > 0); s.removeDecorationType('example'); }); }); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 8053f2b8a6..37d88b6db8 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -92,6 +92,7 @@ export class MenuId { static readonly MenubarSwitchGroupMenu = new MenuId('MenubarSwitchGroupMenu'); static readonly MenubarTerminalMenu = new MenuId('MenubarTerminalMenu'); static readonly MenubarViewMenu = new MenuId('MenubarViewMenu'); + static readonly MenubarWebNavigationMenu = new MenuId('MenubarWebNavigationMenu'); static readonly OpenEditorsContext = new MenuId('OpenEditorsContext'); static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); static readonly SCMChangeContext = new MenuId('SCMChangeContext'); @@ -127,7 +128,6 @@ export class MenuId { static readonly TimelineTitle = new MenuId('TimelineTitle'); static readonly TimelineTitleContext = new MenuId('TimelineTitleContext'); static readonly AccountsContext = new MenuId('AccountsContext'); - static readonly WebMenuActions = new MenuId('MenubarWebMenu'); readonly id: number; readonly _debugName: string; diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index f2ad5ca748..27d77e4fd6 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -265,8 +265,12 @@ export function addToValueTree(settingsTreeRoot: any, key: string, value: any, c curr = obj; } - if (typeof curr === 'object') { - curr[last] = value; // workaround https://github.com/Microsoft/vscode/issues/13606 + if (typeof curr === 'object' && curr !== null) { + try { + curr[last] = value; // workaround https://github.com/Microsoft/vscode/issues/13606 + } catch (e) { + conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`); + } } else { conflictReporter(`Ignoring ${key} as ${segments.join('.')} is ${JSON.stringify(curr)}`); } diff --git a/src/vs/platform/configuration/common/configurationService.ts b/src/vs/platform/configuration/common/configurationService.ts index 8fb26a0b87..22195c31b5 100644 --- a/src/vs/platform/configuration/common/configurationService.ts +++ b/src/vs/platform/configuration/common/configurationService.ts @@ -29,6 +29,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe fileService: IFileService ) { super(); + this._register(fileService.watch(settingsResource)); this.userConfiguration = this._register(new UserSettings(this.settingsResource, undefined, fileService)); this.configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index c09c091d18..cd10df9e1c 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -22,6 +22,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IFileService } from 'vs/platform/files/common/files'; +import { VSBuffer } from 'vs/base/common/buffer'; suite('ConfigurationService - Node', () => { @@ -110,10 +111,10 @@ suite('ConfigurationService - Node', () => { test('trigger configuration change event when file does not exist', async () => { const res = await testFile('config', 'config.json'); - - const service = new ConfigurationService(URI.file(res.testFile), fileService); + const settingsFile = URI.file(res.testFile); + const service = new ConfigurationService(settingsFile, fileService); await service.initialize(); - return new Promise((c, e) => { + return new Promise(async (c, e) => { const disposable = Event.filter(service.onDidChangeConfiguration, e => e.source === ConfigurationTarget.USER)(async (e) => { disposable.dispose(); assert.equal(service.getValue('foo'), 'bar'); @@ -121,7 +122,7 @@ suite('ConfigurationService - Node', () => { await res.cleanUp(); c(); }); - fs.writeFileSync(res.testFile, '{ "foo": "bar" }'); + await fileService.writeFile(settingsFile, VSBuffer.fromString('{ "foo": "bar" }')); }); }); diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index a434b4e76b..50032f3d67 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -56,7 +56,7 @@ export interface ParsedArgs { 'open-url'?: boolean; 'skip-getting-started'?: boolean; 'skip-release-notes'?: boolean; - 'sticky-quickopen'?: boolean; + 'sticky-quickinput'?: boolean; 'disable-restore-windows'?: boolean; 'disable-telemetry'?: boolean; 'export-default-configuration'?: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 4f797a8b9a..476b4e31cf 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -90,7 +90,7 @@ export const OPTIONS: OptionDescriptions> = { 'logExtensionHostCommunication': { type: 'boolean' }, 'skip-getting-started': { type: 'boolean' }, 'skip-release-notes': { type: 'boolean' }, - 'sticky-quickopen': { type: 'boolean' }, + 'sticky-quickinput': { type: 'boolean' }, 'disable-restore-windows': { type: 'boolean' }, 'disable-telemetry': { type: 'boolean' }, 'disable-updates': { type: 'boolean' }, diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 5a47f89a70..d0d6644bd3 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -395,6 +395,8 @@ export function toFileOperationResult(error: Error): FileOperationResult { return FileOperationResult.FILE_NOT_FOUND; case FileSystemProviderErrorCode.FileIsADirectory: return FileOperationResult.FILE_IS_DIRECTORY; + case FileSystemProviderErrorCode.FileNotADirectory: + return FileOperationResult.FILE_NOT_DIRECTORY; case FileSystemProviderErrorCode.NoPermissions: return FileOperationResult.FILE_PERMISSION_DENIED; case FileSystemProviderErrorCode.FileExists: @@ -763,6 +765,7 @@ export const enum FileOperationResult { FILE_TOO_LARGE, FILE_INVALID_PATH, FILE_EXCEEDS_MEMORY_LIMIT, + FILE_NOT_DIRECTORY, FILE_OTHER_ERROR } diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 09c7a6adb0..3425e4f3e8 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -668,6 +668,9 @@ export class DiskFileSystemProvider extends Disposable implements case 'EISDIR': code = FileSystemProviderErrorCode.FileIsADirectory; break; + case 'ENOTDIR': + code = FileSystemProviderErrorCode.FileNotADirectory; + break; case 'EEXIST': code = FileSystemProviderErrorCode.FileExists; break; diff --git a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts index 482f51df6c..3a237e42b2 100644 --- a/src/vs/platform/files/test/electron-browser/diskFileService.test.ts +++ b/src/vs/platform/files/test/electron-browser/diskFileService.test.ts @@ -1409,6 +1409,24 @@ suite('Disk File Service', function () { assert.equal(error!.fileOperationResult, FileOperationResult.FILE_IS_DIRECTORY); }); + test('readFile - FILE_NOT_DIRECTORY', async () => { + if (isWindows) { + return; // error code does not seem to be supported on windows + } + + const resource = URI.file(join(testDir, 'lorem.txt', 'file.txt')); + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_DIRECTORY); + }); + test('readFile - FILE_NOT_FOUND', async () => { const resource = URI.file(join(testDir, '404.html')); diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 0488f13581..4def0fe1ae 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -21,8 +21,8 @@ if (isWeb) { // Running out of sources if (Object.keys(product).length === 0) { assign(product, { - version: '1.16.0-dev', - vscodeVersion: '1.43.0-dev', + version: '1.17.0-dev', + vscodeVersion: '1.44.0-dev', nameLong: 'Azure Data Studio Web Dev', nameShort: 'Azure Data Studio Web Dev', urlProtocol: 'azuredatastudio-oss' diff --git a/src/vs/platform/quickOpen/common/quickOpen.ts b/src/vs/platform/quickOpen/common/quickOpen.ts deleted file mode 100644 index 5a0f053919..0000000000 --- a/src/vs/platform/quickOpen/common/quickOpen.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IQuickNavigateConfiguration, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; - -export interface IShowOptions { - quickNavigateConfiguration?: IQuickNavigateConfiguration; - inputSelection?: { start: number; end: number; }; - autoFocus?: IAutoFocus; -} - -export const IQuickOpenService = createDecorator('quickOpenService'); - -export interface IQuickOpenService { - - _serviceBrand: undefined; - - /** - * Asks the container to show the quick open control with the optional prefix set. If the optional parameter - * is set for quick navigation mode, the quick open control will quickly navigate when the quick navigate - * key is pressed and will run the selection after the ctrl key is released. - * - * The returned promise completes when quick open is closing. - */ - show(prefix?: string, options?: IShowOptions): Promise; - - /** - * Allows to navigate from the outside in an opened picker. - */ - navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void; - - /** - * Accepts the selected value in quick open if visible. - */ - accept(): void; - - /** - * Focus into the quick open if visible. - */ - focus(): void; - - /** - * Closes any opened quick open. - */ - close(): void; - - /** - * Allows to register on the event that quick open is showing - */ - onShow: Event; - - /** - * Allows to register on the event that quick open is hiding - */ - onHide: Event; -} diff --git a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts index 4d2853661d..bc7773dde9 100644 --- a/src/vs/platform/quickinput/browser/pickerQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/pickerQuickAccess.ts @@ -115,15 +115,27 @@ export abstract class PickerQuickAccessProvider): void { + function applyPicks(picks: Picks, skipEmpty?: boolean): boolean { + let items: ReadonlyArray>; + let activeItem: T | undefined = undefined; + if (isPicksWithActive(picks)) { - picker.items = picks.items; - if (picks.active) { - picker.activeItems = [picks.active]; - } + items = picks.items; + activeItem = picks.active; } else { - picker.items = picks; + items = picks; } + + if (items.length === 0 && skipEmpty) { + return false; + } + + picker.items = items; + if (activeItem) { + picker.activeItems = [activeItem]; + } + + return true; } // No Picks @@ -133,8 +145,8 @@ export abstract class PickerQuickAccessProvider { - try { - await timeout(PickerQuickAccessProvider.FAST_PICKS_RACE_DELAY); - if (picksToken.isCancellationRequested) { - return; - } + await timeout(PickerQuickAccessProvider.FAST_PICKS_RACE_DELAY); + if (picksToken.isCancellationRequested) { + return; + } - if (!slowPicksHandlerDone) { - applyPicks(providedPicks.picks); - } - } finally { - fastPicksHandlerDone = true; + if (!slowPicksApplied) { + fastPicksApplied = applyPicks(providedPicks.picks, true /* skip over empty to reduce flicker */); } })(), @@ -186,7 +194,7 @@ export abstract class PickerQuickAccessProvider 0 || !fastPicksHandlerDone) { + if (additionalPicks.length > 0 || !fastPicksApplied) { applyPicks({ items: [...picks, ...additionalPicks], active: activePick as T || additionalActivePick as T // {{SQL CARBON EDIT}} strict-null-checks @@ -197,7 +205,7 @@ export abstract class PickerQuickAccessProvider; - - // TODO@Ben remove once quick open is gone - hide(focusLost?: boolean): void; } diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index fd0f260b8b..36315f76b0 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { IWorkspaceStorageChangeEvent, IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason, logStorage } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; @@ -189,8 +189,8 @@ export class BrowserStorageService extends Disposable implements IStorageService export class FileStorageDatabase extends Disposable implements IStorageDatabase { - private readonly _onDidChangeItemsExternal: Emitter = this._register(new Emitter()); - readonly onDidChangeItemsExternal: Event = this._onDidChangeItemsExternal.event; + private readonly _onDidChangeItemsExternal = this._register(new Emitter()); + readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event; private cache: Map | undefined; diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 0f6a610b8a..45ab1da6f9 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -125,8 +125,8 @@ export const enum StorageScope { } export interface IWorkspaceStorageChangeEvent { - key: string; - scope: StorageScope; + readonly key: string; + readonly scope: StorageScope; } export class InMemoryStorageService extends Disposable implements IStorageService { @@ -139,8 +139,8 @@ export class InMemoryStorageService extends Disposable implements IStorageServic protected readonly _onWillSaveState = this._register(new Emitter()); readonly onWillSaveState = this._onWillSaveState.event; - private globalCache: Map = new Map(); - private workspaceCache: Map = new Map(); + private readonly globalCache = new Map(); + private readonly workspaceCache = new Map(); private getCache(scope: StorageScope): Map { return scope === StorageScope.GLOBAL ? this.globalCache : this.workspaceCache; diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index 2e1bee6594..473349f742 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -23,8 +23,8 @@ interface ISerializableUpdateRequest { } interface ISerializableItemsChangeEvent { - changed?: Item[]; - deleted?: Key[]; + readonly changed?: Item[]; + readonly deleted?: Key[]; } export class GlobalStorageDatabaseChannel extends Disposable implements IServerChannel { @@ -34,15 +34,13 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC private readonly _onDidChangeItems = this._register(new Emitter()); readonly onDidChangeItems = this._onDidChangeItems.event; - private whenReady: Promise; + private readonly whenReady = this.init(); constructor( private logService: ILogService, private storageMainService: IStorageMainService ) { super(); - - this.whenReady = this.init(); } private async init(): Promise { @@ -166,8 +164,8 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS _serviceBrand: undefined; - private readonly _onDidChangeItemsExternal: Emitter = this._register(new Emitter()); - readonly onDidChangeItemsExternal: Event = this._onDidChangeItemsExternal.event; + private readonly _onDidChangeItemsExternal = this._register(new Emitter()); + readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event; private onDidChangeItemsOnMainListener: IDisposable | undefined; diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index a293db5da9..19203845a2 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -48,6 +48,11 @@ export class NativeStorageService extends Disposable implements IStorageService ) { super(); + this.registerListeners(); + } + + private registerListeners(): void { + // Global Storage change events this._register(this.globalStorage.onDidChangeStorage(key => this.handleDidChangeStorage(key, StorageScope.GLOBAL))); } diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 554ce5b424..4bfeb6cbbf 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -154,7 +154,7 @@ export function attachFindReplaceInputBoxStyler(widget: IThemable, themeService: } as IInputBoxStyleOverrides, widget); } -export interface IQuickOpenStyleOverrides extends IListStyleOverrides, IInputBoxStyleOverrides, IProgressBarStyleOverrides { +export interface IQuickInputStyleOverrides extends IListStyleOverrides, IInputBoxStyleOverrides, IProgressBarStyleOverrides { foreground?: ColorIdentifier; background?: ColorIdentifier; borderColor?: ColorIdentifier; @@ -163,7 +163,7 @@ export interface IQuickOpenStyleOverrides extends IListStyleOverrides, IInputBox pickerGroupBorder?: ColorIdentifier; } -export function attachQuickOpenStyler(widget: IThemable, themeService: IThemeService, style?: IQuickOpenStyleOverrides): IDisposable { +export function attachQuickInputStyler(widget: IThemable, themeService: IThemeService, style?: IQuickInputStyleOverrides): IDisposable { return attachStyler(themeService, { foreground: (style && style.foreground) || foreground, background: (style && style.background) || editorBackground, @@ -199,7 +199,7 @@ export function attachQuickOpenStyler(widget: IThemable, themeService: IThemeSer listFocusOutline: (style && style.listFocusOutline) || activeContrastBorder, listSelectionOutline: (style && style.listSelectionOutline) || activeContrastBorder, listHoverOutline: (style && style.listHoverOutline) || activeContrastBorder - } as IQuickOpenStyleOverrides, widget); + } as IQuickInputStyleOverrides, widget); } export interface IListStyleOverrides extends IStyleOverrides { diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index 976f567831..a339736341 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -106,7 +106,7 @@ export interface IColorTheme { /** * Returns the token style for a given classification. The result uses the MetadataConsts format */ - getTokenStyleMetadata(type: string, modifiers: string[]): ITokenStyle | undefined; + getTokenStyleMetadata(type: string, modifiers: string[], modelLanguage: string): ITokenStyle | undefined; /** * List of all colors used with tokens. getTokenStyleMetadata references the colors by index into this list. diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index d3b3447b1f..2685f642c4 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -13,15 +13,21 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; export const TOKEN_TYPE_WILDCARD = '*'; +export const TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR = ':'; +export const CLASSIFIER_MODIFIER_SEPARATOR = '.'; -// qualified string [type|*](.modifier)* +// qualified string [type|*](.modifier)*(/language)! export type TokenClassificationString = string; -export const typeAndModifierIdPattern = '^\\w+[-_\\w+]*$'; +export const idPattern = '\\w+[-_\\w+]*'; +export const typeAndModifierIdPattern = `^${idPattern}$`; + +export const selectorPattern = `^(${idPattern}|\\*)(\\${CLASSIFIER_MODIFIER_SEPARATOR}${idPattern})*(\\${TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR}${idPattern})?$`; + export const fontStylePattern = '^(\\s*(-?italic|-?bold|-?underline))*\\s*$'; export interface TokenSelector { - match(type: string, modifiers: string[]): number; + match(type: string, modifiers: string[], language: string): number; readonly selectorString: string; } @@ -52,7 +58,36 @@ export class TokenStyle implements Readonly { } export namespace TokenStyle { - export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }) { + export function toJSONObject(style: TokenStyle): any { + return { + _foreground: style.foreground === undefined ? null : Color.Format.CSS.formatHexA(style.foreground, true), + _bold: style.bold === undefined ? null : style.bold, + _underline: style.underline === undefined ? null : style.underline, + _italic: style.italic === undefined ? null : style.italic, + }; + } + export function fromJSONObject(obj: any): TokenStyle | undefined { + if (obj) { + const boolOrUndef = (b: any) => (typeof b === 'boolean') ? b : undefined; + const colorOrUndef = (s: any) => (typeof s === 'string') ? Color.fromHex(s) : undefined; + return new TokenStyle(colorOrUndef(obj._foreground), boolOrUndef(obj._bold), boolOrUndef(obj._underline), boolOrUndef(obj._italic)); + } + return undefined; + } + export function equals(s1: any, s2: any): boolean { + if (s1 === s2) { + return true; + } + return s1 !== undefined && s2 !== undefined + && (s1.foreground instanceof Color ? s1.foreground.equals(s2.foreground) : s2.foreground === undefined) + && s1.bold === s2.bold + && s1.underline === s2.underline + && s1.italic === s2.italic; + } + export function is(s: any): s is TokenStyle { + return s instanceof TokenStyle; + } + export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }): TokenStyle { return new TokenStyle(data.foreground, data.bold, data.underline, data.italic); } export function fromSettings(foreground: string | undefined, fontStyle: string | undefined): TokenStyle { @@ -105,6 +140,38 @@ export interface TokenStylingRule { selector: TokenSelector; } +export namespace TokenStylingRule { + export function fromJSONObject(registry: ITokenClassificationRegistry, o: any): TokenStylingRule | undefined { + if (o && typeof o._selector === 'string' && o._style) { + const style = TokenStyle.fromJSONObject(o._style); + if (style) { + try { + return { selector: registry.parseTokenSelector(o._selector), style }; + } catch (_ignore) { + } + } + } + return undefined; + } + export function toJSONObject(rule: TokenStylingRule): any { + return { + _selector: rule.selector.selectorString, + _style: TokenStyle.toJSONObject(rule.style) + }; + } + export function equals(r1: TokenStylingRule | undefined, r2: TokenStylingRule | undefined) { + if (r1 === r2) { + return true; + } + return r1 !== undefined && r2 !== undefined + && r1.selector && r2.selector && r1.selector.selectorString === r2.selector.selectorString + && TokenStyle.equals(r1.style, r2.style); + } + export function is(r: any): r is TokenStylingRule { + return r && r.selector && typeof r.selector.selectorString === 'string' && TokenStyle.is(r.style); + } +} + /** * A TokenStyle Value is either a token style literal, or a TokenClassificationString */ @@ -269,9 +336,9 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } public parseTokenSelector(selectorString: string): TokenSelector { - const [selectorType, ...selectorModifiers] = selectorString.split('.'); + const selector = parseClassifierString(selectorString); - if (!selectorType) { + if (!selector.type) { return { match: () => -1, selectorString @@ -279,23 +346,29 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } return { - match: (type: string, modifiers: string[]) => { + match: (type: string, modifiers: string[], language: string) => { let score = 0; - if (selectorType !== TOKEN_TYPE_WILDCARD) { + if (selector.language !== undefined) { + if (selector.language !== language) { + return -1; + } + score += 100; + } + if (selector.type !== TOKEN_TYPE_WILDCARD) { const hierarchy = this.getTypeHierarchy(type); - const level = hierarchy.indexOf(selectorType); + const level = hierarchy.indexOf(selector.type); if (level === -1) { return -1; } - score = 100 - level; + score += (100 - level); } // all selector modifiers must be present - for (const selectorModifier of selectorModifiers) { + for (const selectorModifier of selector.modifiers) { if (modifiers.indexOf(selectorModifier) === -1) { return -1; } } - return score + selectorModifiers.length * 100; + return score + selector.modifiers.length * 100; }, selectorString }; @@ -366,15 +439,41 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } +const CHAR_LANGUAGE = TOKEN_CLASSIFIER_LANGUAGE_SEPARATOR.charCodeAt(0); +const CHAR_MODIFIER = CLASSIFIER_MODIFIER_SEPARATOR.charCodeAt(0); -const tokenClassificationRegistry = new TokenClassificationRegistry(); +export function parseClassifierString(s: string): { type: string, modifiers: string[], language: string | undefined; } { + let k = s.length; + let language: string | undefined = undefined; + const modifiers = []; + + for (let i = k - 1; i >= 0; i--) { + const ch = s.charCodeAt(i); + if (ch === CHAR_LANGUAGE || ch === CHAR_MODIFIER) { + const segment = s.substring(i + 1, k); + k = i; + if (ch === CHAR_LANGUAGE) { + language = segment; + } else { + modifiers.push(segment); + } + } + } + const type = s.substring(0, k); + return { type, modifiers, language }; +} + + +let tokenClassificationRegistry = createDefaultTokenClassificationRegistry(); platform.Registry.add(Extensions.TokenClassificationContribution, tokenClassificationRegistry); -registerDefaultClassifications(); -function registerDefaultClassifications(): void { +function createDefaultTokenClassificationRegistry(): TokenClassificationRegistry { + + const registry = new TokenClassificationRegistry(); + function registerTokenType(id: string, description: string, scopesToProbe: ProbeScope[] = [], superType?: string, deprecationMessage?: string): string { - tokenClassificationRegistry.registerTokenType(id, description, superType, deprecationMessage); + registry.registerTokenType(id, description, superType, deprecationMessage); if (scopesToProbe) { registerTokenStyleDefault(id, scopesToProbe); } @@ -383,8 +482,8 @@ function registerDefaultClassifications(): void { function registerTokenStyleDefault(selectorString: string, scopesToProbe: ProbeScope[]) { try { - const selector = tokenClassificationRegistry.parseTokenSelector(selectorString); - tokenClassificationRegistry.registerTokenStyleDefault(selector, { scopesToProbe }); + const selector = registry.parseTokenSelector(selectorString); + registry.registerTokenStyleDefault(selector, { scopesToProbe }); } catch (e) { console.log(e); } @@ -422,18 +521,28 @@ function registerDefaultClassifications(): void { // default token modifiers - tokenClassificationRegistry.registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); - tokenClassificationRegistry.registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); - tokenClassificationRegistry.registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); - tokenClassificationRegistry.registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); - tokenClassificationRegistry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); - tokenClassificationRegistry.registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); - tokenClassificationRegistry.registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); - tokenClassificationRegistry.registerTokenModifier('readonly', nls.localize('readonly', "Style to use for symbols that are readonly."), undefined); + registry.registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); + registry.registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); + registry.registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); + registry.registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); + registry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); + registry.registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); + registry.registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); + registry.registerTokenModifier('readonly', nls.localize('readonly', "Style to use for symbols that are readonly."), undefined); registerTokenStyleDefault('variable.readonly', [['variable.other.constant']]); registerTokenStyleDefault('property.readonly', [['variable.other.constant.property']]); + registerTokenStyleDefault('type.defaultLibrary', [['support.type']]); + registerTokenStyleDefault('class.defaultLibrary', [['support.class']]); + registerTokenStyleDefault('interface.defaultLibrary', [['support.class']]); + registerTokenStyleDefault('variable.defaultLibrary', [['support.variable'], ['support.other.variable']]); + registerTokenStyleDefault('variable.defaultLibrary.readonly', [['support.constant']]); + registerTokenStyleDefault('property.defaultLibrary', [['support.variable.property']]); + registerTokenStyleDefault('property.defaultLibrary.readonly', [['support.constant.property']]); + registerTokenStyleDefault('function.defaultLibrary', [['support.function']]); + registerTokenStyleDefault('member.defaultLibrary', [['support.function']]); + return registry; } export function getTokenClassificationRegistry(): ITokenClassificationRegistry { diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index 244d6020e9..7cd68987a8 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -24,7 +24,7 @@ export class TestColorTheme implements IColorTheme { throw new Error('Method not implemented.'); } - getTokenStyleMetadata(type: string, modifiers: string[]): ITokenStyle | undefined { + getTokenStyleMetadata(type: string, modifiers: string[], modelLanguage: string): ITokenStyle | undefined { return undefined; } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4703b27f75..70b95168bc 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1093,7 +1093,7 @@ declare module 'vscode' { readonly dimensions: TerminalDimensions; } - namespace window { + export namespace window { /** * An event which fires when the [dimensions](#Terminal.dimensions) of the terminal change. */ @@ -1114,19 +1114,125 @@ declare module 'vscode' { //#region Terminal link handlers https://github.com/microsoft/vscode/issues/91606 export namespace window { + /** + * Register a [TerminalLinkHandler](#TerminalLinkHandler) that can be used to intercept and + * handle links that are activated within terminals. + */ 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. + * Handles a link that is activated within the terminal. + * + * @return Whether the link was handled, the link was handled this link will not be + * considered by any other extension or by the default built-in link handler. */ handleLink(terminal: Terminal, link: string): ProviderResult; } //#endregion + //#region Contribute to terminal environment https://github.com/microsoft/vscode/issues/46696 + + export enum EnvironmentVariableMutatorType { + /** + * Replace the variable's existing value. + */ + Replace = 1, + /** + * Append to the end of the variable's existing value. + */ + Append = 2, + /** + * Prepend to the start of the variable's existing value. + */ + Prepend = 3 + } + + export interface EnvironmentVariableMutator { + /** + * The type of mutation that will occur to the variable. + */ + readonly type: EnvironmentVariableMutatorType; + + /** + * The value to use for the variable. + */ + readonly value: string; + } + + /** + * A collection of mutations that an extension can apply to a process environment. + */ + export interface EnvironmentVariableCollection { + /** + * Replace an environment variable with a value. + * + * Note that an extension can only make a single change to any one variable, so this will + * overwrite any previous calls to replace, append or prepend. + */ + replace(variable: string, value: string): void; + + /** + * Append a value to an environment variable. + * + * Note that an extension can only make a single change to any one variable, so this will + * overwrite any previous calls to replace, append or prepend. + */ + append(variable: string, value: string): void; + + /** + * Prepend a value to an environment variable. + * + * Note that an extension can only make a single change to any one variable, so this will + * overwrite any previous calls to replace, append or prepend. + */ + prepend(variable: string, value: string): void; + + /** + * Gets the mutator that this collection applies to a variable, if any. + */ + get(variable: string): EnvironmentVariableMutator | undefined; + + /** + * Iterate over each mutator in this collection. + */ + forEach(callback: (variable: string, mutator: EnvironmentVariableMutator, collection: EnvironmentVariableCollection) => any, thisArg?: any): void; + + /** + * Deletes this collection's mutator for a variable. + */ + delete(variable: string): void; + + /** + * Clears all mutators from this collection. + */ + clear(): void; + + /** + * Disposes the collection, if the collection was persisted it will no longer be retained + * across reloads. + */ + dispose(): void; + } + + export namespace window { + /** + * Creates or returns the extension's environment variable collection for this workspace, + * enabling changes to be applied to terminal environment variables. + * + * @param persistent Whether the collection should be cached for the workspace and applied + * to the terminal across window reloads. When true the collection will be active + * immediately such when the window reloads. Additionally, this API will return the cached + * version if it exists. The collection will be invalidated when the extension is + * uninstalled or when the collection is disposed. Defaults to false. + */ + export function getEnvironmentVariableCollection(persistent?: boolean): EnvironmentVariableCollection; + } + + //#endregion + //#region Joh -> exclusive document filters export interface DocumentFilter { @@ -1449,9 +1555,20 @@ declare module 'vscode' { readonly uri: Uri; /** - * Event fired when there are no more references to the `CustomDocument`. + * Is this document representing an untitled file which has never been saved yet. */ - readonly onDidDispose: Event; + readonly isUntitled: boolean; + + /** + * The version number of this document (it will strictly increase after each + * change, including undo/redo). + */ + readonly version: number; + + /** + * `true` if there are unpersisted changes. + */ + readonly isDirty: boolean; /** * List of edits from document open to the document's current state. @@ -1465,6 +1582,17 @@ declare module 'vscode' { * or in front of the last entry in `appliedEdits` if the user saves and then hits undo. */ readonly savedEdits: ReadonlyArray; + + /** + * `true` if the document has been closed. A closed document isn't synchronized anymore + * and won't be re-used when the same resource is opened again. + */ + readonly isClosed: boolean; + + /** + * Event fired when there are no more references to the `CustomDocument`. + */ + readonly onDidDispose: Event; } /** @@ -1674,25 +1802,27 @@ declare module 'vscode' { /** * Controls if the content of a cell is editable or not. */ - editable: boolean; + editable?: boolean; /** * Controls if the cell is executable. * This metadata is ignored for markdown cell. */ - runnable: boolean; + runnable?: boolean; + /** + * Execution order information of the cell + */ executionOrder?: number; } export interface NotebookCell { readonly uri: Uri; - handle: number; + readonly cellKind: CellKind; + readonly source: string; language: string; - cellKind: CellKind; outputs: CellOutput[]; - getContent(): string; - metadata?: NotebookCellMetadata; + metadata: NotebookCellMetadata; } export interface NotebookDocumentMetadata { @@ -1713,18 +1843,24 @@ declare module 'vscode' { * Default to true. */ cellRunnable: boolean; + } export interface NotebookDocument { readonly uri: Uri; readonly fileName: string; readonly isDirty: boolean; + readonly cells: NotebookCell[]; languages: string[]; - cells: NotebookCell[]; displayOrder?: GlobPattern[]; metadata?: NotebookDocumentMetadata; } + export interface NotebookEditorCellEdit { + insert(index: number, content: string, language: string, type: CellKind, outputs: CellOutput[], metadata: NotebookCellMetadata | undefined): void; + delete(index: number): void; + } + export interface NotebookEditor { readonly document: NotebookDocument; viewColumn?: ViewColumn; @@ -1741,10 +1877,7 @@ declare module 'vscode' { */ postMessage(message: any): Thenable; - /** - * Create a notebook cell. The cell is not inserted into current document when created. Extensions should insert the cell into the document by [TextDocument.cells](#TextDocument.cells) - */ - createCell(content: string, language: string, type: CellKind, outputs: CellOutput[], metadata: NotebookCellMetadata | undefined): NotebookCell; + edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable; } export interface NotebookProvider { @@ -1764,11 +1897,24 @@ declare module 'vscode' { * @returns HTML fragment. We can probably return `CellOutput` instead of string ? * */ - render(document: NotebookDocument, cell: NotebookCell, output: CellOutput, mimeType: string): string; + render(document: NotebookDocument, output: CellOutput, mimeType: string): string; preloads?: Uri[]; } - namespace window { + export interface NotebookDocumentChangeEvent { + + /** + * The affected document. + */ + readonly document: NotebookDocument; + + /** + * An array of content changes. + */ + // readonly contentChanges: ReadonlyArray; + } + + export namespace notebook { export function registerNotebookProvider( notebookType: string, provider: NotebookProvider @@ -1777,6 +1923,8 @@ declare module 'vscode' { export function registerNotebookOutputRenderer(type: string, outputSelector: NotebookOutputSelector, renderer: NotebookOutputRenderer): Disposable; export let activeNotebookDocument: NotebookDocument | undefined; + + // export const onDidChangeNotebookDocument: Event; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 8cfc69e7d5..f97730d468 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -32,6 +32,23 @@ const BUILT_IN_AUTH_DEPENDENTS: AuthDependent[] = [ } ]; +interface AllowedExtension { + id: string; + name: string; +} + +function readAllowedExtensions(storageService: IStorageService, providerId: string, accountName: string): AllowedExtension[] { + let trustedExtensions: AllowedExtension[] = []; + try { + const trustedExtensionSrc = storageService.get(`${providerId}-${accountName}`, StorageScope.GLOBAL); + if (trustedExtensionSrc) { + trustedExtensions = JSON.parse(trustedExtensionSrc); + } + } catch (err) { } + + return trustedExtensions; +} + export class MainThreadAuthenticationProvider extends Disposable { private _sessionMenuItems = new Map(); private _accounts = new Map(); // Map account name to session ids @@ -48,30 +65,25 @@ export class MainThreadAuthenticationProvider extends Disposable { this.registerCommandsAndContextMenuItems(); } - private setPermissionsForAccount(quickInputService: IQuickInputService, doLogin?: boolean) { - const quickPick = quickInputService.createQuickPick(); + private manageTrustedExtensions(quickInputService: IQuickInputService, storageService: IStorageService, accountName: string) { + const quickPick = quickInputService.createQuickPick<{ label: string, extension: AllowedExtension }>(); quickPick.canSelectMany = true; - const items = this.dependents.map(dependent => { + const allowedExtensions = readAllowedExtensions(storageService, this.id, accountName); + const items = allowedExtensions.map(extension => { return { - label: dependent.label, - description: dependent.scopeDescriptions, - picked: true, - scopes: dependent.scopes + label: extension.name, + extension }; }); quickPick.items = items; - // TODO read from storage and filter is not doLogin quickPick.selectedItems = items; - quickPick.title = nls.localize('signInTo', "Sign in to {0}", this.displayName); - quickPick.placeholder = nls.localize('accountPermissions', "Choose what features and extensions to authorize to use this account"); + quickPick.title = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); + quickPick.placeholder = nls.localize('manageExensions', "Choose which extensions can access this account"); quickPick.onDidAccept(() => { - const scopes = quickPick.selectedItems.reduce((previous, current) => previous.concat((current as any).scopes), []); - if (scopes.length && doLogin) { - this.login(scopes); - } - + const updatedAllowedList = quickPick.selectedItems.map(item => item.extension); + storageService.store(`${this.id}-${accountName}`, JSON.stringify(updatedAllowedList), StorageScope.GLOBAL); quickPick.dispose(); }); @@ -87,7 +99,7 @@ export class MainThreadAuthenticationProvider extends Disposable { this._register(CommandsRegistry.registerCommand({ id: `signIn${this.id}`, handler: (accessor, args) => { - this.setPermissionsForAccount(accessor.get(IQuickInputService), true); + this.login(this.dependents.reduce((previous: string[], current) => previous.concat(current.scopes), [])); }, })); @@ -130,19 +142,26 @@ export class MainThreadAuthenticationProvider extends Disposable { id: `configureSessions${session.id}`, handler: (accessor, args) => { const quickInputService = accessor.get(IQuickInputService); + const storageService = accessor.get(IStorageService); const quickPick = quickInputService.createQuickPick(); - const items = [{ label: 'Sign Out' }]; + const manage = nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"); + const signOut = nls.localize('signOut', "Sign Out"); + const items = ([{ label: manage }, { label: signOut }]); quickPick.items = items; quickPick.onDidAccept(e => { const selected = quickPick.selectedItems[0]; - if (selected.label === 'Sign Out') { + if (selected.label === signOut) { const sessionsForAccount = this._accounts.get(session.accountName); sessionsForAccount?.forEach(sessionId => this.logout(sessionId)); } + if (selected.label === manage) { + this.manageTrustedExtensions(quickInputService, storageService, session.accountName); + } + quickPick.dispose(); }); @@ -185,6 +204,7 @@ export class MainThreadAuthenticationProvider extends Disposable { disposeables.forEach(disposeable => disposeable.dispose()); this._sessionMenuItems.delete(accountName); } + this._accounts.delete(accountName); } } }); @@ -242,55 +262,45 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu this.authenticationService.sessionsUpdate(id, event); } - async $getSessionsPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise { - const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL); - if (alwaysAllow) { - return alwaysAllow === 'true'; + async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise { + let allowList = readAllowedExtensions(this.storageService, providerId, accountName); + if (allowList.some(extension => extension.id === extensionId)) { + return true; } - const { choice, checkboxChecked } = await this.dialogService.show( + const { choice } = await this.dialogService.show( Severity.Info, - nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information from {1}.", extensionName, providerName), + nls.localize('confirmAuthenticationAccess', "The extension '{0}' is trying to access authentication information for the {1} account '{2}'.", extensionName, providerName, accountName), [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")], { - cancelId: 0, - checkbox: { - label: nls.localize('neverAgain', "Don't Show Again") - } + cancelId: 0 } ); const allow = choice === 1; - if (checkboxChecked) { - this.storageService.store(`${extensionId}-${providerId}`, allow ? 'true' : 'false', StorageScope.GLOBAL); + if (allow) { + allowList = allowList.concat({ id: extensionId, name: extensionName }); + this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL); } return allow; } - async $loginPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise { - const alwaysAllow = this.storageService.get(`${extensionId}-${providerId}`, StorageScope.GLOBAL); - if (alwaysAllow) { - return alwaysAllow === 'true'; - } - - const { choice, checkboxChecked } = await this.dialogService.show( + async $loginPrompt(providerName: string, extensionName: string): Promise { + const { choice } = await this.dialogService.show( Severity.Info, nls.localize('confirmLogin', "The extension '{0}' wants to sign in using {1}.", extensionName, providerName), - [nls.localize('cancel', "Cancel"), nls.localize('continue', "Continue")], + [nls.localize('cancel', "Cancel"), nls.localize('allow', "Allow")], { - cancelId: 0, - checkbox: { - label: nls.localize('neverAgain', "Don't Show Again") - } + cancelId: 0 } ); - const allow = choice === 1; - if (checkboxChecked) { - this.storageService.store(`${extensionId}-${providerId}`, allow ? 'true' : 'false', StorageScope.GLOBAL); - } + return choice === 1; + } - return allow; + async $setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise { + const allowList = readAllowedExtensions(this.storageService, providerId, accountName).concat({ id: extensionId, name: extensionName }); + this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL); } } diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index b7429187af..640b44354b 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -8,9 +8,8 @@ import { MainContext, MainThreadNotebookShape, NotebookExtensionDescription, IEx import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { INotebookService, IMainNotebookController } from 'vs/workbench/contrib/notebook/browser/notebookService'; -import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellsSplice, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, NOTEBOOK_DISPLAY_ORDER, NotebookCellOutputsSplice, CellKind, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -30,16 +29,17 @@ export class MainThreadNotebookDocument extends Disposable { ) { super(); this._textModel = new NotebookTextModel(handle, viewType, uri); + this._register(this._textModel.onDidModelChange(e => { + this._proxy.$acceptModelChanged(this.uri, e); + })); } - async deleteCell(uri: URI, index: number): Promise { - let deleteExtHostCell = await this._proxy.$deleteCell(this.viewType, uri, index); - if (deleteExtHostCell) { - this._textModel.removeCell(index); - return true; - } + applyEdit(modelVersionId: number, edits: ICellEditOperation[]): boolean { + return this._textModel.applyEdit(modelVersionId, edits); + } - return false; + updateRenderers(renderers: number[]) { + this._textModel.updateRenderers(renderers); } dispose() { @@ -65,6 +65,16 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo this.registerListeners(); } + async $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise { + let controller = this._notebookProviders.get(viewType); + + if (controller) { + return controller.tryApplyEdits(resource, modelVersionId, edits, renderers); + } + + return false; + } + registerListeners() { this._register(this._notebookService.onDidChangeActiveEditor(e => { this._proxy.$updateActiveEditor(e.viewType, e.uri); @@ -148,11 +158,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo return handle; } - async $spliceNotebookCells(viewType: string, resource: UriComponents, splices: NotebookCellsSplice[], renderers: number[]): Promise { - let controller = this._notebookProviders.get(viewType); - controller?.spliceNotebookCells(resource, splices, renderers); - } - async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise { let controller = this._notebookProviders.get(viewType); controller?.spliceNotebookCellOutputs(resource, cellHandle, splices, renderers); @@ -201,11 +206,8 @@ export class MainThreadNotebookController implements IMainNotebookController { mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); if (mainthreadNotebook && mainthreadNotebook.textModel.cells.length === 0) { // it's empty, we should create an empty template one - const templateCell = await this._proxy.$createEmptyCell(this._viewType, uri, 0, mainthreadNotebook.textModel.languages.length ? mainthreadNotebook.textModel.languages[0] : '', CellKind.Code); - if (templateCell) { - let mainCell = new NotebookCellTextModel(URI.revive(templateCell.uri), templateCell.handle, templateCell.source, templateCell.language, templateCell.cellKind, templateCell.outputs, templateCell.metadata); - mainthreadNotebook.textModel.insertTemplateCell(mainCell); - } + const mainCell = mainthreadNotebook.textModel.createCellTextModel([''], mainthreadNotebook.textModel.languages.length ? mainthreadNotebook.textModel.languages[0] : '', CellKind.Code, [], undefined); + mainthreadNotebook.textModel.insertTemplateCell(mainCell); } return mainthreadNotebook?.textModel; } @@ -213,10 +215,15 @@ export class MainThreadNotebookController implements IMainNotebookController { return undefined; } - spliceNotebookCells(resource: UriComponents, splices: NotebookCellsSplice[], renderers: number[]): void { + async tryApplyEdits(resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise { let mainthreadNotebook = this._mapping.get(URI.from(resource).toString()); - mainthreadNotebook?.textModel.updateRenderers(renderers); - mainthreadNotebook?.textModel.$spliceNotebookCells(splices); + + if (mainthreadNotebook) { + mainthreadNotebook.updateRenderers(renderers); + return mainthreadNotebook.applyEdit(modelVersionId, edits); + } + + return false; } spliceNotebookCellOutputs(resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): void { @@ -259,26 +266,6 @@ export class MainThreadNotebookController implements IMainNotebookController { document?.textModel.updateRenderers(renderers); } - async createRawCell(uri: URI, index: number, language: string, type: CellKind): Promise { - let cell = await this._proxy.$createEmptyCell(this._viewType, uri, index, language, type); - if (cell) { - let mainCell = new NotebookCellTextModel(URI.revive(cell.uri), cell.handle, cell.source, cell.language, cell.cellKind, cell.outputs, cell.metadata); - return mainCell; - } - - return undefined; - } - - async deleteCell(uri: URI, index: number): Promise { - let mainthreadNotebook = this._mapping.get(URI.from(uri).toString()); - - if (mainthreadNotebook) { - return mainthreadNotebook.deleteCell(uri, index); - } - - return false; - } - async executeNotebookCell(uri: URI, handle: number): Promise { return this._proxy.$executeNotebook(this._viewType, uri, handle); } diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 160e919bd0..05357ffa35 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ITextFileSaveParticipant, IResolvedTextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileSaveParticipant, ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { SaveReason } from 'vs/workbench/common/editor'; import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol'; import { canceled } from 'vs/base/common/errors'; @@ -23,9 +23,9 @@ class ExtHostSaveParticipant implements ITextFileSaveParticipant { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant); } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, _progress: IProgress, token: CancellationToken): Promise { + async participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason; }, _progress: IProgress, token: CancellationToken): Promise { - if (!shouldSynchronizeModel(editorModel.textEditorModel)) { + if (!editorModel.textEditorModel || !shouldSynchronizeModel(editorModel.textEditorModel)) { // the model never made it to the extension // host meaning we cannot participate in its save return undefined; diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index bcc8c54054..53f17b3491 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -13,6 +13,8 @@ import { ITerminalInstanceService, ITerminalService, ITerminalInstance, ITermina 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'; +import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -29,8 +31,9 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape extHostContext: IExtHostContext, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalInstanceService readonly terminalInstanceService: ITerminalInstanceService, - @IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService, + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); this._remoteAuthority = extHostContext.remoteAuthority; @@ -71,6 +74,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape if (activeInstance) { this._proxy.$acceptActiveTerminalChanged(activeInstance.id); } + if (this._environmentVariableService.collections.size > 0) { + const collectionAsArray = [...this._environmentVariableService.collections.entries()]; + const serializedCollections: [string, ISerializableEnvironmentVariableCollection][] = collectionAsArray.map(e => { + return [e[0], serializeEnvironmentVariableCollection(e[1].map)]; + }); + this._proxy.$initEnvironmentVariableCollections(serializedCollections); + } this._terminalService.extHostReady(extHostContext.remoteAuthority); } @@ -346,6 +356,18 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } return terminal; } + + $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void { + if (collection) { + const translatedCollection = { + persistent, + map: deserializeEnvironmentVariableCollection(collection) + }; + this._environmentVariableService.set(extensionIdentifier, translatedCollection); + } else { + this._environmentVariableService.delete(extensionIdentifier); + } + } } /** diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 4a391e8746..53e92a221f 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -11,7 +11,7 @@ import { Disposable, DisposableStore, dispose, IDisposable, IReference } from 'v import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; import { isWeb } from 'vs/base/common/platform'; -import { isEqual } from 'vs/base/common/resources'; +import { isEqual, isEqualOrParent } from 'vs/base/common/resources'; import { escape } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; @@ -35,9 +35,11 @@ import { CustomTextEditorModel } from 'vs/workbench/contrib/customEditor/common/ import { WebviewExtensionDescription, WebviewIcons } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewWorkbenchService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IWorkingCopy, IWorkingCopyBackup, IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { extHostNamedCustomer } from '../common/extHostCustomers'; @@ -121,6 +123,8 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma constructor( context: extHostProtocol.IExtHostContext, @IExtensionService extensionService: IExtensionService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @ICustomEditorService private readonly _customEditorService: ICustomEditorService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, @@ -164,6 +168,20 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma }, resolveWebview: () => { throw new Error('not implemented'); } })); + + workingCopyFileService.registerWorkingCopyProvider((editorResource) => { + const matchedWorkingCopies: IWorkingCopy[] = []; + + for (const workingCopy of workingCopyService.workingCopies) { + if (workingCopy instanceof MainThreadCustomEditorModel) { + if (isEqualOrParent(editorResource, workingCopy.editorResource)) { + matchedWorkingCopies.push(workingCopy); + } + } + } + return matchedWorkingCopies; + + }); } public $createWebviewPanel( @@ -564,6 +582,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod private _currentEditIndex: number = -1; private _savePoint: number = -1; private readonly _edits: Array = []; + private _fromBackup: boolean = false; public static async create( instantiationService: IInstantiationService, @@ -574,7 +593,9 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod cancellation: CancellationToken, ) { const { editable } = await proxy.$createWebviewCustomEditorDocument(resource, viewType, cancellation); - return instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, editable, getEditors); + const model = instantiationService.createInstance(MainThreadCustomEditorModel, proxy, viewType, resource, editable, getEditors); + await model.init(); + return model; } constructor( @@ -587,6 +608,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod @ILabelService private readonly _labelService: ILabelService, @IFileService private readonly _fileService: IFileService, @IUndoRedoService private readonly _undoService: IUndoRedoService, + @IBackupFileService private readonly _backupFileService: IBackupFileService, ) { super(); @@ -595,6 +617,15 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod } } + get editorResource() { + return this._editorResource; + } + + async init(): Promise { + const backup = await this._backupFileService.resolve(this.resource); + this._fromBackup = !!backup; + } + dispose() { if (this._editable) { this._undoService.removeElements(this._editorResource); @@ -624,7 +655,10 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod } public isDirty(): boolean { - return this._edits.length > 0 && this._savePoint !== this._currentEditIndex; + if (this._edits.length > 0) { + return this._savePoint !== this._currentEditIndex; + } + return this._fromBackup; } private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index fc4f065c59..a919d127e5 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -490,6 +490,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension); return extHostTerminalService.onDidWriteTerminalData(listener, thisArg, disposables); }, + getEnvironmentVariableCollection(persistent?: boolean): vscode.EnvironmentVariableCollection { + checkProposedApiEnabled(extension); + return extHostTerminalService.getEnvironmentVariableCollection(extension, persistent); + }, get state() { return extHostWindow.state; }, @@ -600,15 +604,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I createInputBox(): vscode.InputBox { return extHostQuickOpen.createInputBox(extension.identifier); }, - registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => { - return extHostNotebook.registerNotebookProvider(extension, viewType, provider); - }, - registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => { - return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer); - }, - get activeNotebookDocument(): vscode.NotebookDocument | undefined { - return extHostNotebook.activeNotebookDocument; - }, get activeColorTheme(): vscode.ColorTheme { checkProposedApiEnabled(extension); return extHostTheming.activeColorTheme; @@ -925,6 +920,19 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I } }; + // namespace: notebook + const notebook: typeof vscode.notebook = { + registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => { + return extHostNotebook.registerNotebookProvider(extension, viewType, provider); + }, + registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => { + return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer); + }, + get activeNotebookDocument(): vscode.NotebookDocument | undefined { + return extHostNotebook.activeNotebookDocument; + } + }; + return { // {{SQL CARBON EDIT}} - Expose the VS Code version here for extensions that rely on it version: initData.vscodeVersion, @@ -939,6 +947,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I comment, comments, tasks, + notebook, window, workspace, // types @@ -974,6 +983,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I DocumentLink: extHostTypes.DocumentLink, DocumentSymbol: extHostTypes.DocumentSymbol, EndOfLine: extHostTypes.EndOfLine, + EnvironmentVariableMutatorType: extHostTypes.EnvironmentVariableMutatorType, EvaluatableExpression: extHostTypes.EvaluatableExpression, EventEmitter: Emitter, ExtensionKind: extHostTypes.ExtensionKind, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index bab104e524..0c08f3c876 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,9 +51,10 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookMimeTypeSelector, IOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; +import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; // {{SQL CARBON EDIT}} import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; @@ -161,8 +162,9 @@ export interface MainThreadAuthenticationShape extends IDisposable { $registerAuthenticationProvider(id: string, displayName: string): void; $unregisterAuthenticationProvider(id: string): void; $onDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void; - $getSessionsPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise; - $loginPrompt(providerId: string, providerName: string, extensionId: string, extensionName: string): Promise; + $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise; + $loginPrompt(providerName: string, extensionName: string): Promise; + $setTrustedExtension(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise; } export interface MainThreadConfigurationShape extends IDisposable { @@ -440,6 +442,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable { $stopSendingDataEvents(): void; $startHandlingLinks(): void; $stopHandlingLinks(): void; + $setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: ISerializableEnvironmentVariableCollection | undefined): void; // Process $sendProcessTitle(terminalId: number, title: string): void; @@ -696,10 +699,10 @@ export interface MainThreadNotebookShape extends IDisposable { $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise; $unregisterNotebookRenderer(handle: number): Promise; $createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise; + $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[], renderers: number[]): Promise; $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; $updateNotebookMetadata(viewType: string, resource: UriComponents, metadata: NotebookDocumentMetadata): Promise; $updateNotebookCellMetadata(viewType: string, resource: UriComponents, handle: number, metadata: NotebookCellMetadata | undefined): Promise; - $spliceNotebookCells(viewType: string, resource: UriComponents, splices: NotebookCellsSplice[], renderers: number[]): Promise; $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[], renderers: number[]): Promise; $postMessage(handle: number, value: any): Promise; } @@ -1391,6 +1394,7 @@ export interface ExtHostTerminalServiceShape { $getAvailableShells(): Promise; $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; $handleLink(id: number, link: string): Promise; + $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void; } export interface ExtHostSCMShape { @@ -1535,13 +1539,12 @@ export interface ExtHostCommentsShape { export interface ExtHostNotebookShape { $resolveNotebook(viewType: string, uri: UriComponents): Promise; $executeNotebook(viewType: string, uri: UriComponents, cellHandle: number | undefined): Promise; - $createEmptyCell(viewType: string, uri: UriComponents, index: number, language: string, type: CellKind): Promise; - $deleteCell(viewType: string, uri: UriComponents, index: number): Promise; $saveNotebook(viewType: string, uri: UriComponents): Promise; $updateActiveEditor(viewType: string, uri: UriComponents): Promise; $destoryNotebookDocument(viewType: string, uri: UriComponents): Promise; $acceptDisplayOrder(displayOrder: INotebookDisplayOrder): void; $onDidReceiveMessage(uri: UriComponents, message: any): void; + $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void; } export interface ExtHostStorageShape { diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index 333c113264..338eed5ea2 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -50,6 +50,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { getAccessToken: async () => { const isAllowed = await this._proxy.$getSessionsPrompt( provider.id, + session.accountName, provider.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), requestingExtension.displayName || requestingExtension.name); @@ -70,12 +71,15 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { throw new Error(`No authentication provider with id '${providerId}' is currently registered.`); } - const isAllowed = await this._proxy.$loginPrompt(provider.id, provider.displayName, ExtensionIdentifier.toKey(requestingExtension.identifier), requestingExtension.displayName || requestingExtension.name); + const extensionName = requestingExtension.displayName || requestingExtension.name; + const isAllowed = await this._proxy.$loginPrompt(provider.displayName, extensionName); if (!isAllowed) { throw new Error('User did not consent to login.'); } - return provider.login(scopes); + const newSession = await provider.login(scopes); + await this._proxy.$setTrustedExtension(provider.id, newSession.accountName, ExtensionIdentifier.toKey(requestingExtension.identifier), extensionName); + return newSession; } registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index e2786976f5..8cfabdd9d2 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -3,17 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; import { readonly } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { CellKind, CellOutputKind, ExtHostNotebookShape, ICellDto, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice, NotebookCellsSplice } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, CellOutputKind, ExtHostNotebookShape, IMainContext, MainContext, MainThreadNotebookShape, NotebookCellOutputsSplice } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { CellUri, diff, IErrorOutput, INotebookDisplayOrder, IOrderedMimeType, IOutput, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import * as vscode from 'vscode'; +import { CellEditType, CellUri, diff, ICellEditOperation, ICellInsertEdit, IErrorOutput, INotebookDisplayOrder, INotebookEditData, IOrderedMimeType, IStreamOutput, ITransformedDisplayOutputDto, mimeTypeSupportedByCore, NotebookCellsChangedEvent, NotebookCellsSplice2, sortMimeTypes, ICellDeleteEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Disposable as VSCodeDisposable } from './extHostTypes'; interface IObservable { @@ -44,7 +44,8 @@ const notebookDocumentMetadataDefaults: vscode.NotebookDocumentMetadata = { }; export class ExtHostCell extends Disposable implements vscode.NotebookCell { - public source: string[]; + + private originalSource: string[]; private _outputs: any[]; private _onDidChangeOutputs = new Emitter[]>(); onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; @@ -55,21 +56,28 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { private _metadataChangeListener: IDisposable; + get source() { + if (this._textDocument && this._initalVersion !== this._textDocument?.version) { + return this._textDocument.getText(); + } else { + return this.originalSource.join('\n'); + } + } + constructor( private viewType: string, private documentUri: URI, readonly handle: number, readonly uri: URI, private _content: string, - public cellKind: CellKind, + public readonly cellKind: CellKind, public language: string, outputs: any[], _metadata: vscode.NotebookCellMetadata | undefined, private _proxy: MainThreadNotebookShape ) { super(); - - this.source = this._content.split(/\r|\n|\r\n/g); + this.originalSource = this._content.split(/\r|\n|\r\n/g); this._outputs = outputs; const observableMetadata = getObservable(_metadata || {} as any); @@ -106,7 +114,7 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { return this._metadata; } - set metadata(newMetadata: vscode.NotebookCellMetadata | undefined) { + set metadata(newMetadata: vscode.NotebookCellMetadata) { this._metadataChangeListener.dispose(); const observableMetadata = getObservable(newMetadata || {} as any); // TODO defaults this._metadata = observableMetadata.proxy; @@ -125,7 +133,7 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { if (this._textDocument && this._initalVersion !== this._textDocument?.version) { return this._textDocument.getText(); } else { - return this.source.join('\n'); + return this.originalSource.join('\n'); } } @@ -134,9 +142,9 @@ export class ExtHostCell extends Disposable implements vscode.NotebookCell { this._initalVersion = this._textDocument.version; } - detachTextDocument(document: vscode.TextDocument) { + detachTextDocument() { if (this._textDocument && this._textDocument.version !== this._initalVersion) { - this.source = this._textDocument.getText().split(/\r|\n|\r\n/g); + this.originalSource = this._textDocument.getText().split(/\r|\n|\r\n/g); } this._textDocument = undefined; @@ -156,29 +164,6 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo return this._cells; } - set cells(newCells: ExtHostCell[]) { - let diffs = diff(this._cells, newCells, (a) => { - return this._cellDisposableMapping.has(a.handle); - }); - - diffs.forEach(diff => { - for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { - this._cellDisposableMapping.get(this._cells[i].handle)?.clear(); - this._cellDisposableMapping.delete(this._cells[i].handle); - } - - diff.toInsert.forEach(cell => { - this._cellDisposableMapping.set(cell.handle, new DisposableStore()); - this._cellDisposableMapping.get(cell.handle)?.add(cell.onDidChangeOutputs((outputDiffs) => { - this.eventuallyUpdateCellOutputs(cell, outputDiffs); - })); - }); - }); - - this._cells = newCells; - this.eventuallyUpdateCells(diffs); - } - private _languages: string[] = []; get languages() { @@ -211,8 +196,15 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo this._displayOrder = newOrder; } + private _versionId = 0; + + get versionId() { + return this._versionId; + } + constructor( private readonly _proxy: MainThreadNotebookShape, + private _documentsAndEditors: ExtHostDocumentsAndEditors, public viewType: string, public uri: URI, public renderingHandler: ExtHostNotebookOutputRenderingHandler @@ -229,51 +221,47 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo get isDirty() { return false; } - eventuallyUpdateCells(diffs: ISplice[]) { - let renderers = new Set(); - let diffDtos: NotebookCellsSplice[] = []; + accpetModelChanged(event: NotebookCellsChangedEvent) { + this.$spliceNotebookCells(event.changes); + this._versionId = event.versionId; + } - diffDtos = diffs.map(diff => { - let inserts = diff.toInsert; + private $spliceNotebookCells(splices: NotebookCellsSplice2[]): void { + if (!splices.length) { + return; + } - let cellDtos = inserts.map(cell => { - let outputs: IOutput[] = []; - if (cell.outputs.length) { - outputs = cell.outputs.map(output => { - if (output.outputKind === CellOutputKind.Rich) { - const ret = this.transformMimeTypes(cell, output); + splices.reverse().forEach(splice => { + let cellDtos = splice[2]; + let newCells = cellDtos.map(cell => { + const extCell = new ExtHostCell(this.viewType, this.uri, cell.handle, URI.revive(cell.uri), cell.source.join('\n'), cell.cellKind, cell.language, cell.outputs, cell.metadata, this._proxy); + const document = this._documentsAndEditors.getDocument(URI.revive(cell.uri)); - if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) { - renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!); - } - return ret; - } else { - return output as IStreamOutput | IErrorOutput; - } - }); + if (document) { + extCell.attachTextDocument(document.document); } - return { - uri: cell.uri, - handle: cell.handle, - source: cell.source, - language: cell.language, - cellKind: cell.cellKind, - outputs: outputs, - metadata: cell.metadata, - isDirty: false - }; + if (!this._cellDisposableMapping.has(extCell.handle)) { + this._cellDisposableMapping.set(extCell.handle, new DisposableStore()); + } + + let store = this._cellDisposableMapping.get(extCell.handle)!; + + store.add(extCell.onDidChangeOutputs((diffs) => { + this.eventuallyUpdateCellOutputs(extCell, diffs); + })); + + return extCell; }); - return [diff.start, diff.deleteCount, cellDtos]; - }); + for (let j = splice[0]; j < splice[0] + splice[1]; j++) { + this._cellDisposableMapping.get(this.cells[j].handle)?.dispose(); + this._cellDisposableMapping.delete(this.cells[j].handle); - this._proxy.$spliceNotebookCells( - this.viewType, - this.uri, - diffDtos, - Array.from(renderers) - ); + } + + this.cells.splice(splice[0], splice[1], ...newCells); + }); } eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice[]) { @@ -283,7 +271,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo let transformedOutputs = outputs.map(output => { if (output.outputKind === CellOutputKind.Rich) { - const ret = this.transformMimeTypes(cell, output); + const ret = this.transformMimeTypes(output); if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) { renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!); @@ -300,35 +288,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo this._proxy.$spliceNotebookCellOutputs(this.viewType, this.uri, cell.handle, outputDtos, Array.from(renderers)); } - insertCell(index: number, cell: ExtHostCell) { - this.cells.splice(index, 0, cell); - - if (!this._cellDisposableMapping.has(cell.handle)) { - this._cellDisposableMapping.set(cell.handle, new DisposableStore()); - } - - let store = this._cellDisposableMapping.get(cell.handle)!; - - store.add(cell.onDidChangeOutputs((diffs) => { - this.eventuallyUpdateCellOutputs(cell, diffs); - })); - } - - deleteCell(index: number): boolean { - if (index >= this.cells.length) { - return false; - } - - let cell = this.cells[index]; - this._cellDisposableMapping.get(cell.handle)?.dispose(); - this._cellDisposableMapping.delete(cell.handle); - - this.cells.splice(index, 1); - return true; - } - - - transformMimeTypes(cell: ExtHostCell, output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { + transformMimeTypes(output: vscode.CellDisplayOutput): ITransformedDisplayOutputDto { let mimeTypes = Object.keys(output.data); // TODO@rebornix, the document display order might be assigned a bit later. We need to postpone sending the outputs to the core side. @@ -341,7 +301,7 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo let handlers = this.renderingHandler.findBestMatchedRenderer(mimeType); if (handlers.length) { - let renderedOutput = handlers[0].render(this, cell, output, mimeType); + let renderedOutput = handlers[0].render(this, output, mimeType); orderMimeTypes.push({ mimeType: mimeType, @@ -395,14 +355,84 @@ export class ExtHostNotebookDocument extends Disposable implements vscode.Notebo detachCellTextDocument(textDocument: vscode.TextDocument) { let cell = this.cells.find(cell => cell.uri.toString() === textDocument.uri.toString()); if (cell) { - cell.detachTextDocument(textDocument); + cell.detachTextDocument(); } } } +export class NotebookEditorCellEdit { + private _finalized: boolean = false; + private readonly _documentVersionId: number; + private _collectedEdits: ICellEditOperation[] = []; + private _renderers = new Set(); + + constructor( + readonly editor: ExtHostNotebookEditor + ) { + this._documentVersionId = editor.document.versionId; + } + + finalize(): INotebookEditData { + this._finalized = true; + return { + documentVersionId: this._documentVersionId, + edits: this._collectedEdits, + renderers: Array.from(this._renderers) + }; + } + + private _throwIfFinalized() { + if (this._finalized) { + throw new Error('Edit is only valid while callback runs'); + } + } + + insert(index: number, content: string, language: string, type: CellKind, outputs: vscode.CellOutput[], metadata: vscode.NotebookCellMetadata | undefined): void { + this._throwIfFinalized(); + + let cell = { + source: [content], + language, + cellKind: type, + outputs: (outputs as any[]), // TODO@rebornix + metadata + }; + + const transformedOutputs = outputs.map(output => { + if (output.outputKind === CellOutputKind.Rich) { + const ret = this.editor.document.transformMimeTypes(output); + + if (ret.orderedMimeTypes[ret.pickedMimeTypeIndex].isResolved) { + this._renderers.add(ret.orderedMimeTypes[ret.pickedMimeTypeIndex].rendererId!); + } + return ret; + } else { + return output as IStreamOutput | IErrorOutput; + } + }); + + cell.outputs = transformedOutputs; + + this._collectedEdits.push({ + editType: CellEditType.Insert, + index, + cells: [cell] + }); + } + + delete(index: number): void { + this._throwIfFinalized(); + + this._collectedEdits.push({ + editType: CellEditType.Delete, + index, + count: 1 + }); + } +} + export class ExtHostNotebookEditor extends Disposable implements vscode.NotebookEditor { private _viewColumn: vscode.ViewColumn | undefined; - private static _cellhandlePool: number = 0; onDidReceiveMessage: vscode.Event = this._onDidReceiveMessage.event; constructor( @@ -438,11 +468,52 @@ export class ExtHostNotebookEditor extends Disposable implements vscode.Notebook })); } - createCell(content: string, language: string, type: CellKind, outputs: vscode.CellOutput[], metadata: vscode.NotebookCellMetadata | undefined): vscode.NotebookCell { - const handle = ExtHostNotebookEditor._cellhandlePool++; - const uri = CellUri.generate(this.document.uri, handle); - const cell = new ExtHostCell(this.viewType, this.uri, handle, uri, content, type, language, outputs, metadata, this._proxy); - return cell; + edit(callback: (editBuilder: NotebookEditorCellEdit) => void): Thenable { + const edit = new NotebookEditorCellEdit(this); + callback(edit); + return this._applyEdit(edit); + } + + private _applyEdit(editBuilder: NotebookEditorCellEdit): Promise { + const editData = editBuilder.finalize(); + + // return when there is nothing to do + if (editData.edits.length === 0) { + return Promise.resolve(true); + } + + let compressedEdits: ICellEditOperation[] = []; + let compressedEditsIndex = -1; + + for (let i = 0; i < editData.edits.length; i++) { + if (compressedEditsIndex < 0) { + compressedEdits.push(editData.edits[i]); + compressedEditsIndex++; + continue; + } + + let prevIndex = compressedEditsIndex; + let prev = compressedEdits[prevIndex]; + + if (prev.editType === CellEditType.Insert && editData.edits[i].editType === CellEditType.Insert) { + if (prev.index === editData.edits[i].index) { + prev.cells.push(...(editData.edits[i] as ICellInsertEdit).cells); + continue; + } + } + + if (prev.editType === CellEditType.Delete && editData.edits[i].editType === CellEditType.Delete) { + if (prev.index === editData.edits[i].index) { + prev.count += (editData.edits[i] as ICellDeleteEdit).count; + continue; + } + } + + compressedEdits.push(editData.edits[i]); + compressedEditsIndex++; + } + + return this._proxy.$tryApplyEdits(this.viewType, this.uri, editData.documentVersionId, compressedEdits, editData.renderers); } get viewColumn(): vscode.ViewColumn | undefined { @@ -480,8 +551,8 @@ export class ExtHostNotebookOutputRenderer { return false; } - render(document: ExtHostNotebookDocument, cell: ExtHostCell, output: vscode.CellOutput, mimeType: string): string { - let html = this.renderer.render(document, cell, output, mimeType); + render(document: ExtHostNotebookDocument, output: vscode.CellOutput, mimeType: string): string { + let html = this.renderer.render(document, output, mimeType); return html; } @@ -584,7 +655,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN if (provider) { if (!this._documents.has(URI.revive(uri).toString())) { - let document = new ExtHostNotebookDocument(this._proxy, viewType, URI.revive(uri), this); + let document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, viewType, URI.revive(uri), this); await this._proxy.$createNotebookDocument( document.handle, viewType, @@ -632,55 +703,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN return provider.provider.executeCell(document!, cell); } - async $createEmptyCell(viewType: string, uri: URI, index: number, language: string, type: CellKind): Promise { - let provider = this._notebookProviders.get(viewType); - - if (provider) { - let editor = this._editors.get(URI.revive(uri).toString()); - let document = this._documents.get(URI.revive(uri).toString()); - - let rawCell = editor?.editor.createCell('', language, type, [], { editable: true, runnable: true }) as ExtHostCell; - document?.insertCell(index, rawCell!); - - let allDocuments = this._documentsAndEditors.allDocuments(); - for (let { document: textDocument } of allDocuments) { - let data = CellUri.parse(textDocument.uri); - if (data) { - if (uri.toString() === data.notebook.toString() && textDocument.uri.toString() === rawCell.uri.toString()) { - rawCell.attachTextDocument(textDocument); - } - } - } - return { - uri: rawCell.uri, - handle: rawCell.handle, - source: rawCell.source, - language: rawCell.language, - cellKind: rawCell.cellKind, - metadata: rawCell.metadata, - outputs: [] - }; - } - - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - async $deleteCell(viewType: string, uri: UriComponents, index: number): Promise { - let provider = this._notebookProviders.get(viewType); - - if (!provider) { - return false; - } - - let document = this._documents.get(URI.revive(uri).toString()); - - if (document) { - return document.deleteCell(index); - } - - return false; - } - async $saveNotebook(viewType: string, uri: UriComponents): Promise { let provider = this._notebookProviders.get(viewType); let document = this._documents.get(URI.revive(uri).toString()); @@ -732,4 +754,13 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN editor.onDidReceiveMessage.fire(message); } } + + $acceptModelChanged(uriComponents: UriComponents, event: NotebookCellsChangedEvent): void { + let editor = this._editors.get(URI.revive(uriComponents).toString()); + + if (editor) { + editor.editor.document.accpetModelChanged(event); + } + + } } diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 2ebb8a73b0..33d74f8652 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -14,7 +14,9 @@ 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'; +import { Disposable as VSCodeDisposable, EnvironmentVariableMutatorType } from './extHostTypes'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { @@ -35,7 +37,8 @@ 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 + registerLinkHandler(handler: vscode.TerminalLinkHandler): vscode.Disposable; + getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection; } export const IExtHostTerminalService = createDecorator('IExtHostTerminalService'); @@ -333,6 +336,8 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ public abstract $getAvailableShells(): Promise; public abstract $getDefaultShellAndArgs(useAutomationShell: boolean): Promise; public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void; + public abstract getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection; + public abstract $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void; public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options, options.name); @@ -637,6 +642,67 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } } +export class EnvironmentVariableCollection implements vscode.EnvironmentVariableCollection { + readonly map: Map = new Map(); + + protected readonly _onDidChangeCollection: Emitter = new Emitter(); + get onDidChangeCollection(): Event { return this._onDidChangeCollection && this._onDidChangeCollection.event; } + + constructor( + readonly persistent: boolean, + serialized?: ISerializableEnvironmentVariableCollection + ) { + this.map = new Map(serialized); + } + + get size(): number { + return this.map.size; + } + + replace(variable: string, value: string): void { + this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Replace }); + } + + append(variable: string, value: string): void { + this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Append }); + } + + prepend(variable: string, value: string): void { + this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Prepend }); + } + + private _setIfDiffers(variable: string, mutator: vscode.EnvironmentVariableMutator): void { + const current = this.map.get(variable); + if (!current || current.value !== mutator.value || current.type !== mutator.type) { + this.map.set(variable, mutator); + this._onDidChangeCollection.fire(); + } + } + + get(variable: string): vscode.EnvironmentVariableMutator | undefined { + return this.map.get(variable); + } + + forEach(callback: (variable: string, mutator: vscode.EnvironmentVariableMutator, collection: vscode.EnvironmentVariableCollection) => any, thisArg?: any): void { + this.map.forEach((value, key) => callback(key, value, this)); + } + + delete(variable: string): void { + this.map.delete(variable); + this._onDidChangeCollection.fire(); + } + + clear(): void { + this.map.clear(); + this._onDidChangeCollection.fire(); + } + + dispose(): void { + this.map.clear(); + this._onDidChangeCollection.fire(); + } +} + export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { throw new Error('Not implemented'); @@ -669,4 +735,13 @@ export class WorkerExtHostTerminalService extends BaseExtHostTerminalService { public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void { // No-op for web worker ext host as workspace permissions aren't used } + + public getEnvironmentVariableCollection(extension: IExtensionDescription, persistent?: boolean): vscode.EnvironmentVariableCollection { + // This is not implemented so worker ext host extensions cannot influence terminal envs + throw new Error('Not implemented'); + } + + public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void { + // No-op for web worker ext host as collections aren't used + } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 5796f7478f..5d2bc9360d 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -17,6 +17,7 @@ import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remo import type * as vscode from 'vscode'; import { Cache } from './cache'; import { assertIsDefined } from 'vs/base/common/types'; +import { Schemas } from 'vs/base/common/network'; function es5ClassCompat(target: Function): any { ///@ts-ignore @@ -483,6 +484,12 @@ export enum EndOfLine { CRLF = 2 } +export enum EnvironmentVariableMutatorType { + Replace = 1, + Append = 2, + Prepend = 3 +} + @es5ClassCompat export class TextEdit { @@ -2591,22 +2598,22 @@ interface EditState { export class CustomDocument implements vscode.CustomDocument { - readonly #edits = new Cache('edits'); - #editState: EditState; - readonly #viewType: string; readonly #uri: vscode.Uri; + #editState: EditState = { + allEdits: [], + currentIndex: -1, + saveIndex: -1, + }; + #isDisposed = false; + #version = 1; + constructor(viewType: string, uri: vscode.Uri) { this.#viewType = viewType; this.#uri = uri; - this.#editState = { - allEdits: [], - currentIndex: 0, - saveIndex: 0 - }; } //#region Public API @@ -2615,15 +2622,27 @@ export class CustomDocument implements vscode.CustomDocument public get uri(): vscode.Uri { return this.#uri; } + public get fileName(): string { return this.uri.fsPath; } + + public get isUntitled() { return this.uri.scheme === Schemas.untitled; } + #onDidDispose = new Emitter(); public readonly onDidDispose = this.#onDidDispose.event; - get appliedEdits() { + public get isClosed() { return this.#isDisposed; } + + public get version() { return this.#version; } + + public get isDirty() { + return this.#editState.currentIndex !== this.#editState.saveIndex; + } + + public get appliedEdits() { return this.#editState.allEdits.slice(0, this.#editState.currentIndex + 1) .map(id => this._getEdit(id)); } - get savedEdits() { + public get savedEdits() { return this.#editState.allEdits.slice(0, this.#editState.saveIndex + 1) .map(id => this._getEdit(id)); } @@ -2631,11 +2650,13 @@ export class CustomDocument implements vscode.CustomDocument //#endregion /** @internal */ _dispose(): void { + this.#isDisposed = true; this.#onDidDispose.fire(); this.#onDidDispose.dispose(); } /** @internal */ _updateEditState(state: EditState) { + ++this.#version; this.#editState = state; } diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 9e3dfa27a9..b1802f7177 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -24,73 +24,91 @@ import * as extHostTypes from './extHostTypes'; type IconPath = URI | { light: URI, dark: URI }; export class ExtHostWebview implements vscode.Webview { - private _html: string = ''; - private _isDisposed: boolean = false; - private _hasCalledAsWebviewUri = false; - public readonly _onMessageEmitter = new Emitter(); - public readonly onDidReceiveMessage: Event = this._onMessageEmitter.event; + readonly #handle: extHostProtocol.WebviewPanelHandle; + readonly #proxy: extHostProtocol.MainThreadWebviewsShape; + readonly #deprecationService: IExtHostApiDeprecationService; + + readonly #initData: WebviewInitData; + readonly #workspace: IExtHostWorkspace | undefined; + readonly #extension: IExtensionDescription; + + #html: string = ''; + #options: vscode.WebviewOptions; + #isDisposed: boolean = false; + #hasCalledAsWebviewUri = false; constructor( - private readonly _handle: extHostProtocol.WebviewPanelHandle, - private readonly _proxy: extHostProtocol.MainThreadWebviewsShape, - private _options: vscode.WebviewOptions, - private readonly _initData: WebviewInitData, - private readonly _workspace: IExtHostWorkspace | undefined, - private readonly _extension: IExtensionDescription, - private readonly _deprecationService: IExtHostApiDeprecationService, - ) { } + handle: extHostProtocol.WebviewPanelHandle, + proxy: extHostProtocol.MainThreadWebviewsShape, + options: vscode.WebviewOptions, + initData: WebviewInitData, + workspace: IExtHostWorkspace | undefined, + extension: IExtensionDescription, + deprecationService: IExtHostApiDeprecationService, + ) { + this.#handle = handle; + this.#proxy = proxy; + this.#options = options; + this.#initData = initData; + this.#workspace = workspace; + this.#extension = extension; + this.#deprecationService = deprecationService; + } + + /* internal */ readonly _onMessageEmitter = new Emitter(); + public readonly onDidReceiveMessage: Event = this._onMessageEmitter.event; public dispose() { this._onMessageEmitter.dispose(); } public asWebviewUri(resource: vscode.Uri): vscode.Uri { - this._hasCalledAsWebviewUri = true; - return asWebviewUri(this._initData, this._handle, resource); + this.#hasCalledAsWebviewUri = true; + return asWebviewUri(this.#initData, this.#handle, resource); } public get cspSource(): string { - return this._initData.webviewCspSource - .replace('{{uuid}}', this._handle); + return this.#initData.webviewCspSource + .replace('{{uuid}}', this.#handle); } public get html(): string { this.assertNotDisposed(); - return this._html; + return this.#html; } public set html(value: string) { this.assertNotDisposed(); - if (this._html !== value) { - this._html = value; - if (!this._hasCalledAsWebviewUri && /(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { - this._hasCalledAsWebviewUri = true; - this._deprecationService.report('Webview vscode-resource: uris', this._extension, + if (this.#html !== value) { + this.#html = value; + if (!this.#hasCalledAsWebviewUri && /(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { + this.#hasCalledAsWebviewUri = true; + this.#deprecationService.report('Webview vscode-resource: uris', this.#extension, `Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); } - this._proxy.$setHtml(this._handle, value); + this.#proxy.$setHtml(this.#handle, value); } } public get options(): vscode.WebviewOptions { this.assertNotDisposed(); - return this._options; + return this.#options; } public set options(newOptions: vscode.WebviewOptions) { this.assertNotDisposed(); - this._proxy.$setOptions(this._handle, convertWebviewOptions(this._extension, this._workspace, newOptions)); - this._options = newOptions; + this.#proxy.$setOptions(this.#handle, convertWebviewOptions(this.#extension, this.#workspace, newOptions)); + this.#options = newOptions; } public postMessage(message: any): Promise { this.assertNotDisposed(); - return this._proxy.$postMessage(this._handle, message); + return this.#proxy.$postMessage(this.#handle, message); } private assertNotDisposed() { - if (this._isDisposed) { + if (this.#isDisposed) { throw new Error('Webview is disposed'); } } @@ -98,19 +116,18 @@ export class ExtHostWebview implements vscode.Webview { export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPanel { - private readonly _handle: extHostProtocol.WebviewPanelHandle; - private readonly _proxy: extHostProtocol.MainThreadWebviewsShape; - private readonly _viewType: string; - private _title: string; - private _iconPath?: IconPath; + readonly #handle: extHostProtocol.WebviewPanelHandle; + readonly #proxy: extHostProtocol.MainThreadWebviewsShape; + readonly #viewType: string; - readonly #options: vscode.WebviewPanelOptions; readonly #webview: ExtHostWebview; + readonly #options: vscode.WebviewPanelOptions; + #title: string; + #iconPath?: IconPath; #viewColumn: vscode.ViewColumn | undefined = undefined; #visible: boolean = true; #active: boolean = true; - #isDisposed: boolean = false; readonly #onDidDispose = this._register(new Emitter()); @@ -129,12 +146,12 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa webview: ExtHostWebview ) { super(); - this._handle = handle; - this._proxy = proxy; - this._viewType = viewType; + this.#handle = handle; + this.#proxy = proxy; + this.#viewType = viewType; this.#options = editorOptions; this.#viewColumn = viewColumn; - this._title = title; + this.#title = title; this.#webview = webview; } @@ -145,7 +162,7 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa this.#isDisposed = true; this.#onDidDispose.fire(); - this._proxy.$disposeWebview(this._handle); + this.#proxy.$disposeWebview(this.#handle); this.#webview.dispose(); super.dispose(); @@ -158,33 +175,33 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa get viewType(): string { this.assertNotDisposed(); - return this._viewType; + return this.#viewType; } get title(): string { this.assertNotDisposed(); - return this._title; + return this.#title; } set title(value: string) { this.assertNotDisposed(); - if (this._title !== value) { - this._title = value; - this._proxy.$setTitle(this._handle, value); + if (this.#title !== value) { + this.#title = value; + this.#proxy.$setTitle(this.#handle, value); } } get iconPath(): IconPath | undefined { this.assertNotDisposed(); - return this._iconPath; + return this.#iconPath; } set iconPath(value: IconPath | undefined) { this.assertNotDisposed(); - if (this._iconPath !== value) { - this._iconPath = value; + if (this.#iconPath !== value) { + this.#iconPath = value; - this._proxy.$setIconPath(this._handle, URI.isUri(value) ? { light: value, dark: value } : value); + this.#proxy.$setIconPath(this.#handle, URI.isUri(value) ? { light: value, dark: value } : value); } } @@ -227,12 +244,12 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa public postMessage(message: any): Promise { this.assertNotDisposed(); - return this._proxy.$postMessage(this._handle, message); + return this.#proxy.$postMessage(this.#handle, message); } public reveal(viewColumn?: vscode.ViewColumn, preserveFocus?: boolean): void { this.assertNotDisposed(); - this._proxy.$reveal(this._handle, { + this.#proxy.$reveal(this.#handle, { viewColumn: viewColumn ? typeConverters.ViewColumn.from(viewColumn) : undefined, preserveFocus: !!preserveFocus }); diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 7463d8b9dc..c3ac5630c1 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -38,6 +38,7 @@ namespace schema { case 'debug/toolbar': return MenuId.DebugToolBar; case 'debug/toolBar': return MenuId.DebugToolBar; case 'menuBar/file': return MenuId.MenubarFileMenu; + case 'menuBar/webNavigation': return MenuId.MenubarWebNavigationMenu; case 'scm/title': return MenuId.SCMTitle; case 'scm/sourceControl': return MenuId.SCMSourceControl; case 'scm/resourceState/context': return MenuId.SCMResourceContext; @@ -70,6 +71,7 @@ namespace schema { switch (menuId) { case MenuId.StatusBarWindowIndicatorMenu: case MenuId.MenubarFileMenu: + case MenuId.MenubarWebNavigationMenu: return true; } return false; diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 2504cf60bb..27cbc65c82 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -20,14 +20,21 @@ import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostD import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal'; import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; -import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService'; +import { BaseExtHostTerminalService, ExtHostTerminal, EnvironmentVariableCollection } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { dispose } from 'vs/base/common/lifecycle'; +import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared'; +import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; +import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection'; export class ExtHostTerminalService extends BaseExtHostTerminalService { private _variableResolver: ExtHostVariableResolverService | undefined; private _lastActiveWorkspace: IWorkspaceFolder | undefined; + private _environmentVariableCollections: Map = new Map(); + // TODO: Pull this from main side private _isWorkspaceShellAllowed: boolean = false; @@ -191,6 +198,10 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { baseEnv ); + // Apply extension environment variable collections to the environment + const mergedCollection = new MergedEnvironmentVariableCollection(this._environmentVariableCollections); + mergedCollection.applyToProcessEnvironment(env); + this._proxy.$sendResolvedLaunchConfig(id, shellLaunchConfig); // Fork the process and listen for messages this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env); @@ -215,4 +226,50 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void { this._isWorkspaceShellAllowed = isAllowed; } + + public getEnvironmentVariableCollection(extension: IExtensionDescription, persistent: boolean = false): vscode.EnvironmentVariableCollection { + let collection: EnvironmentVariableCollection | undefined; + if (persistent) { + // If persistent is specified, return the current collection if it exists + collection = this._environmentVariableCollections.get(extension.identifier.value); + + // If persistence changed then create a new collection + if (collection && !collection.persistent) { + collection = undefined; + } + } + + if (!collection) { + // If not persistent, clear out the current collection and create a new one + dispose(this._environmentVariableCollections.get(extension.identifier.value)); + collection = new EnvironmentVariableCollection(persistent); + this._setEnvironmentVariableCollection(extension.identifier.value, collection); + } + + return collection; + } + + private _syncEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void { + const serialized = serializeEnvironmentVariableCollection(collection.map); + this._proxy.$setEnvironmentVariableCollection(extensionIdentifier, collection.persistent, serialized.length === 0 ? undefined : serialized); + } + + public $initEnvironmentVariableCollections(collections: [string, ISerializableEnvironmentVariableCollection][]): void { + collections.forEach(entry => { + const extensionIdentifier = entry[0]; + const collection = new EnvironmentVariableCollection(true, entry[1]); + this._setEnvironmentVariableCollection(extensionIdentifier, collection); + }); + } + + private _setEnvironmentVariableCollection(extensionIdentifier: string, collection: EnvironmentVariableCollection): void { + this._environmentVariableCollections.set(extensionIdentifier, collection); + collection.onDidChangeCollection(() => { + // When any collection value changes send this immediately, this is done to ensure + // following calls to createTerminal will be created with the new environment. It will + // result in more noise by sending multiple updates when called but collections are + // expected to be small. + this._syncEnvironmentVariableCollection(extensionIdentifier, collection!); + }); + } } diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts deleted file mode 100644 index 1e36b69aea..0000000000 --- a/src/vs/workbench/browser/actions.ts +++ /dev/null @@ -1,196 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Registry } from 'vs/platform/registry/common/platform'; -import { IAction } from 'vs/base/common/actions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; -import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; - -/** - * The action bar contributor allows to add actions to an actionbar in a given context. - */ -export class ActionBarContributor { - - /** - * Returns true if this contributor has actions for the given context. - */ - hasActions(context: unknown): boolean { - return false; - } - - /** - * Returns an array of primary actions in the given context. - */ - getActions(context: unknown): ReadonlyArray { - return []; - } -} - -/** - * Some predefined scopes to contribute actions to - */ -export const Scope = { - - /** - * Actions inside tree widgets. - */ - VIEWER: 'viewer' -}; - -/** - * The ContributableActionProvider leverages the actionbar contribution model to find actions. - */ -export class ContributableActionProvider implements IActionProvider { - private readonly registry: IActionBarRegistry = Registry.as(Extensions.Actionbar); - - private toContext(tree: ITree, element: unknown): unknown { - return { - viewer: tree, - element: element - }; - } - - hasActions(tree: ITree, element: unknown): boolean { - const context = this.toContext(tree, element); - - const contributors = this.registry.getActionBarContributors(Scope.VIEWER); - return contributors.some(contributor => contributor.hasActions(context)); - } - - getActions(tree: ITree, element: unknown): ReadonlyArray { - const actions: IAction[] = []; - const context = this.toContext(tree, element); - - // Collect Actions - const contributors = this.registry.getActionBarContributors(Scope.VIEWER); - for (const contributor of contributors) { - if (contributor.hasActions(context)) { - actions.push(...contributor.getActions(context)); - } - } - - return prepareActions(actions); - } -} - -// Helper function used in parts to massage actions before showing in action areas -export function prepareActions(actions: IAction[]): IAction[] { - if (!actions.length) { - return actions; - } - - // Clean up leading separators - let firstIndexOfAction = -1; - for (let i = 0; i < actions.length; i++) { - if (actions[i].id === Separator.ID) { - continue; - } - - firstIndexOfAction = i; - break; - } - - if (firstIndexOfAction === -1) { - return []; - } - - actions = actions.slice(firstIndexOfAction); - - // Clean up trailing separators - for (let h = actions.length - 1; h >= 0; h--) { - const isSeparator = actions[h].id === Separator.ID; - if (isSeparator) { - actions.splice(h, 1); - } else { - break; - } - } - - // Clean up separator duplicates - let foundAction = false; - for (let k = actions.length - 1; k >= 0; k--) { - const isSeparator = actions[k].id === Separator.ID; - if (isSeparator && !foundAction) { - actions.splice(k, 1); - } else if (!isSeparator) { - foundAction = true; - } else if (isSeparator) { - foundAction = false; - } - } - - return actions; -} - -export const Extensions = { - Actionbar: 'workbench.contributions.actionbar' -}; - -export interface IActionBarRegistry { - /** - * Registers an Actionbar contributor. It will be called to contribute actions to all the action bars - * that are used in the Workbench in the given scope. - */ - registerActionBarContributor(scope: string, ctor: { new(...services: Services): ActionBarContributor }): void; - - /** - * Returns an array of registered action bar contributors known to the workbench for the given scope. - */ - getActionBarContributors(scope: string): ActionBarContributor[]; - - /** - * Starts the registry by providing the required services. - */ - start(accessor: ServicesAccessor): void; -} - -class ActionBarRegistry implements IActionBarRegistry { - private readonly actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0; }[] = []; - private readonly actionBarContributorInstances: Map = new Map(); - private instantiationService: IInstantiationService | undefined; - - start(accessor: ServicesAccessor): void { - this.instantiationService = accessor.get(IInstantiationService); - - while (this.actionBarContributorConstructors.length > 0) { - const entry = this.actionBarContributorConstructors.shift()!; - this.createActionBarContributor(entry.scope, entry.ctor); - } - } - - private createActionBarContributor(scope: string, ctor: IConstructorSignature0): void { - if (this.instantiationService) { - const instance = this.instantiationService.createInstance(ctor); - let target = this.actionBarContributorInstances.get(scope); - if (!target) { - target = []; - this.actionBarContributorInstances.set(scope, target); - } - target.push(instance); - } - } - - private getContributors(scope: string): ActionBarContributor[] { - return this.actionBarContributorInstances.get(scope) || []; - } - - registerActionBarContributor(scope: string, ctor: IConstructorSignature0): void { - if (!this.instantiationService) { - this.actionBarContributorConstructors.push({ - scope: scope, - ctor: ctor - }); - } else { - this.createActionBarContributor(scope, ctor); - } - } - - getActionBarContributors(scope: string): ActionBarContributor[] { - return this.getContributors(scope).slice(0); - } -} - -Registry.add(Extensions.Actionbar, new ActionBarRegistry()); diff --git a/src/vs/workbench/browser/actions/quickAccessActions.ts b/src/vs/workbench/browser/actions/quickAccessActions.ts new file mode 100644 index 0000000000..424c1beb6b --- /dev/null +++ b/src/vs/workbench/browser/actions/quickAccessActions.ts @@ -0,0 +1,251 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Registry } from 'vs/platform/registry/common/platform'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { Action } from 'vs/base/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { inQuickPickContext, defaultQuickAccessContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; + +//#region Quick access management commands and keys + +const globalQuickAccessKeybinding = { + primary: KeyMod.CtrlCmd | KeyCode.KEY_P, + secondary: [KeyMod.CtrlCmd | KeyCode.KEY_E], + mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: undefined } +}; + +const QUICKACCESS_ACTION_ID = 'workbench.action.quickOpen'; + +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { id: QUICKACCESS_ACTION_ID, title: { value: localize('quickOpen', "Go to File..."), original: 'Go to File...' } } +}); + +KeybindingsRegistry.registerKeybindingRule({ + id: QUICKACCESS_ACTION_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: globalQuickAccessKeybinding.primary, + secondary: globalQuickAccessKeybinding.secondary, + mac: globalQuickAccessKeybinding.mac +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.closeQuickOpen', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickPickContext, + primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + return quickInputService.cancel(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.acceptSelectedQuickOpenItem', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickPickContext, + primary: 0, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + return quickInputService.accept(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.alternativeAcceptSelectedQuickOpenItem', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickPickContext, + primary: 0, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + return quickInputService.accept({ ctrlCmd: true, alt: false }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.focusQuickOpen', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickPickContext, + primary: 0, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.focus(); + } +}); + +const quickAccessNavigateNextInFilePickerId = 'workbench.action.quickOpenNavigateNextInFilePicker'; +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: quickAccessNavigateNextInFilePickerId, + weight: KeybindingWeight.WorkbenchContrib + 50, + handler: getQuickNavigateHandler(quickAccessNavigateNextInFilePickerId, true), + when: defaultQuickAccessContext, + primary: globalQuickAccessKeybinding.primary, + secondary: globalQuickAccessKeybinding.secondary, + mac: globalQuickAccessKeybinding.mac +}); + +const quickAccessNavigatePreviousInFilePickerId = 'workbench.action.quickOpenNavigatePreviousInFilePicker'; +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: quickAccessNavigatePreviousInFilePickerId, + weight: KeybindingWeight.WorkbenchContrib + 50, + handler: getQuickNavigateHandler(quickAccessNavigatePreviousInFilePickerId, false), + when: defaultQuickAccessContext, + primary: globalQuickAccessKeybinding.primary | KeyMod.Shift, + secondary: [globalQuickAccessKeybinding.secondary[0] | KeyMod.Shift], + mac: { + primary: globalQuickAccessKeybinding.mac.primary | KeyMod.Shift, + secondary: undefined + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.quickPickManyToggle', + weight: KeybindingWeight.WorkbenchContrib, + when: inQuickPickContext, + primary: 0, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.toggle(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.quickInputBack', + weight: KeybindingWeight.WorkbenchContrib + 50, + when: inQuickPickContext, + primary: 0, + win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, + mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS }, + handler: accessor => { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.back(); + } +}); + +CommandsRegistry.registerCommand({ + id: QUICKACCESS_ACTION_ID, + handler: async function (accessor: ServicesAccessor, prefix: unknown) { + const quickInputService = accessor.get(IQuickInputService); + + quickInputService.quickAccess.show(typeof prefix === 'string' ? prefix : undefined); + }, + description: { + description: `Quick access`, + args: [{ + name: 'prefix', + schema: { + 'type': 'string' + } + }] + } +}); + +CommandsRegistry.registerCommand('workbench.action.quickOpenPreviousEditor', async function (accessor: ServicesAccessor, prefix: string | null = null) { + const quickInputService = accessor.get(IQuickInputService); + + quickInputService.quickAccess.show('', { itemActivation: ItemActivation.SECOND }); +}); + +//#endregion + +//#region Workbench actions + +export class BaseQuickAccessNavigateAction extends Action { + + constructor( + id: string, + label: string, + private next: boolean, + private quickNavigate: boolean, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IKeybindingService private readonly keybindingService: IKeybindingService + ) { + super(id, label); + } + + async run(): Promise { + const keys = this.keybindingService.lookupKeybindings(this.id); + const quickNavigate = this.quickNavigate ? { keybindings: keys } : undefined; + + this.quickInputService.navigate(this.next, quickNavigate); + } +} + +export class QuickAccessNavigateNextAction extends BaseQuickAccessNavigateAction { + + static readonly ID = 'workbench.action.quickOpenNavigateNext'; + static readonly LABEL = localize('quickNavigateNext', "Navigate Next in Quick Open"); + + constructor( + id: string, + label: string, + @IQuickInputService quickInputService: IQuickInputService, + @IKeybindingService keybindingService: IKeybindingService + ) { + super(id, label, true, true, quickInputService, keybindingService); + } +} + +class QuickAccessNavigatePreviousAction extends BaseQuickAccessNavigateAction { + + static readonly ID = 'workbench.action.quickOpenNavigatePrevious'; + static readonly LABEL = localize('quickNavigatePrevious', "Navigate Previous in Quick Open"); + + constructor( + id: string, + label: string, + @IQuickInputService quickInputService: IQuickInputService, + @IKeybindingService keybindingService: IKeybindingService + ) { + super(id, label, false, true, quickInputService, keybindingService); + } +} + +class QuickAccessSelectNextAction extends BaseQuickAccessNavigateAction { + + static readonly ID = 'workbench.action.quickOpenSelectNext'; + static readonly LABEL = localize('quickSelectNext', "Select Next in Quick Open"); + + constructor( + id: string, + label: string, + @IQuickInputService quickInputService: IQuickInputService, + @IKeybindingService keybindingService: IKeybindingService + ) { + super(id, label, true, false, quickInputService, keybindingService); + } +} + +class QuickAccessSelectPreviousAction extends BaseQuickAccessNavigateAction { + + static readonly ID = 'workbench.action.quickOpenSelectPrevious'; + static readonly LABEL = localize('quickSelectPrevious', "Select Previous in Quick Open"); + + constructor( + id: string, + label: string, + @IQuickInputService quickInputService: IQuickInputService, + @IKeybindingService keybindingService: IKeybindingService + ) { + super(id, label, false, false, quickInputService, keybindingService); + } +} + +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessSelectNextAction, QuickAccessSelectNextAction.ID, QuickAccessSelectNextAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickPickContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Next in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessSelectPreviousAction, QuickAccessSelectPreviousAction.ID, QuickAccessSelectPreviousAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickPickContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Previous in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessNavigateNextAction, QuickAccessNavigateNextAction.ID, QuickAccessNavigateNextAction.LABEL), 'Navigate Next in Quick Open'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessNavigatePreviousAction, QuickAccessNavigatePreviousAction.ID, QuickAccessNavigatePreviousAction.LABEL), 'Navigate Previous in Quick Open'); + +//#endregion diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 9b87c5a53d..68ddcdc590 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -29,7 +29,7 @@ import { FileKind } from 'vs/platform/files/common/files'; import { splitName } from 'vs/base/common/labels'; import { isMacintosh } from 'vs/base/common/platform'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; +import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { IHostService } from 'vs/workbench/services/host/browser/host'; export const inRecentFilesPickerContextKey = 'inRecentFilesPicker'; @@ -165,7 +165,7 @@ export class OpenRecentAction extends BaseOpenRecentAction { } } -class QuickOpenRecentAction extends BaseOpenRecentAction { +class QuickPickRecentAction extends BaseOpenRecentAction { static readonly ID = 'workbench.action.quickOpenRecent'; static readonly LABEL = nls.localize('quickOpenRecent', "Quick Open Recent..."); @@ -270,7 +270,7 @@ const registry = Registry.as(Extensions.WorkbenchActio const fileCategory = nls.localize('file', "File"); registry.registerWorkbenchAction(SyncActionDescriptor.create(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickPickRecentAction, QuickPickRecentAction.ID, QuickPickRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory); const viewCategory = nls.localize('view', "View"); @@ -284,23 +284,23 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(ShowAboutDialogActi // --- Commands/Keybindings Registration -const recentFilesPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(inRecentFilesPickerContextKey)); +const recentFilesPickerContext = ContextKeyExpr.and(inQuickPickContext, ContextKeyExpr.has(inRecentFilesPickerContextKey)); -const quickOpenNavigateNextInRecentFilesPickerId = 'workbench.action.quickOpenNavigateNextInRecentFilesPicker'; +const quickPickNavigateNextInRecentFilesPickerId = 'workbench.action.quickOpenNavigateNextInRecentFilesPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigateNextInRecentFilesPickerId, + id: quickPickNavigateNextInRecentFilesPickerId, weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigateNextInRecentFilesPickerId, true), + handler: getQuickNavigateHandler(quickPickNavigateNextInRecentFilesPickerId, true), when: recentFilesPickerContext, primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }); -const quickOpenNavigatePreviousInRecentFilesPicker = 'workbench.action.quickOpenNavigatePreviousInRecentFilesPicker'; +const quickPickNavigatePreviousInRecentFilesPicker = 'workbench.action.quickOpenNavigatePreviousInRecentFilesPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigatePreviousInRecentFilesPicker, + id: quickPickNavigatePreviousInRecentFilesPicker, weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigatePreviousInRecentFilesPicker, false), + handler: getQuickNavigateHandler(quickPickNavigatePreviousInRecentFilesPicker, false), when: recentFilesPickerContext, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_R } diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index bed352468f..d746705fb1 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -206,7 +206,12 @@ export class WorkbenchContextKeysHandler extends Disposable { if (activeEditorPane) { this.activeEditorContext.set(activeEditorPane.getId()); - this.activeEditorIsReadonly.set(activeEditorPane.input.isReadonly()); + try { + this.activeEditorIsReadonly.set(activeEditorPane.input.isReadonly()); + } catch (error) { + // TODO@ben for https://github.com/microsoft/vscode/issues/93224 + throw new Error(`${error.message}: editor id ${activeEditorPane.getId()}`); + } } else { this.activeEditorContext.reset(); this.activeEditorIsReadonly.reset(); diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index ea4041b969..ab081648b8 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -223,9 +223,7 @@ body.web { .monaco-workbench [tabindex="-1"]:active, .monaco-workbench select:active, .monaco-workbench input[type="button"]:active, -.monaco-workbench input[type="checkbox"]:active, -.monaco-workbench .monaco-tree .monaco-tree-row -.monaco-workbench .monaco-tree.focused.no-focused-item:active:before { +.monaco-workbench input[type="checkbox"]:active { outline: 0 !important; /* fixes some flashing outlines from showing up when clicking */ } @@ -233,12 +231,6 @@ body.web { border-color: transparent; /* outline is a square, but border has a radius, so we avoid this glitch when focused (https://github.com/Microsoft/vscode/issues/26045) */ } -.monaco-workbench .monaco-tree.focused .monaco-tree-row.focused [tabindex="0"]:focus { - outline-width: 1px; /* higher contrast color for focusable elements in a row that shows focus feedback */ - outline-style: solid; -} - -.monaco-workbench .monaco-tree.focused.no-focused-item:focus:before, .monaco-workbench .monaco-list:not(.element-focused):focus:before { position: absolute; top: 0; @@ -267,7 +259,6 @@ body.web { outline: 0 !important; /* outline is not going well with decoration */ } -.monaco-workbench .monaco-tree.focused:focus, .monaco-workbench .monaco-list:focus { outline: 0 !important; /* tree indicates focus not via outline but through the focused item */ } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 2249d282db..69ddf92389 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -40,6 +40,8 @@ import { getMenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWeb } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { getUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; +import { IProductService } from 'vs/platform/product/common/productService'; interface IPlaceholderViewlet { id: string; @@ -109,7 +111,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService + @IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService, + @IProductService private readonly productService: IProductService ) { super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); this.migrateFromOldCachedViewletsValue(); @@ -395,13 +398,16 @@ export class ActivitybarPart extends Part implements IActivityBarService { cssClass: 'codicon-settings-gear' }); - const profileAction = new ActivityAction({ - id: 'workbench.actions.accounts', - name: nls.localize('accounts', "Accounts"), - cssClass: 'codicon-account' - }); + if (getUserDataSyncStore(this.productService, this.configurationService)) { + const profileAction = new ActivityAction({ + id: 'workbench.actions.accounts', + name: nls.localize('accounts', "Accounts"), + cssClass: 'codicon-account' + }); + + this.globalActivityActionBar.push(profileAction); + } - this.globalActivityActionBar.push(profileAction); this.globalActivityActionBar.push(this.globalActivityAction); } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index e972355616..e1ee451e85 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -11,9 +11,8 @@ import * as strings from 'vs/base/common/strings'; import { Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IActionViewItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { prepareActions } from 'vs/workbench/browser/actions'; import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { Part, IPartOptions } from 'vs/workbench/browser/part'; import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index 7a6bedebab..1364e8cd20 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -13,7 +13,7 @@ import { Extensions, IConfigurationRegistry, ConfigurationScope } from 'vs/platf import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; -import { GroupIdentifier } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IEditorPartOptions } from 'vs/workbench/common/editor'; export const IBreadcrumbsService = createDecorator('IEditorBreadcrumbsService'); @@ -72,6 +72,7 @@ export abstract class BreadcrumbsConfig { static readonly SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath'); static readonly SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder'); static readonly Icons = BreadcrumbsConfig._stub('breadcrumbs.icons'); + static readonly TitleScrollbarSizing = BreadcrumbsConfig._stub('workbench.editor.titleScrollbarSizing'); static readonly FileExcludes = BreadcrumbsConfig._stub('files.exclude'); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index a69f99a3f6..50684d86fc 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -29,7 +29,7 @@ import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/file import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ColorIdentifier, ColorFunction } from 'vs/platform/theme/common/colorRegistry'; import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -38,7 +38,7 @@ import { ResourceLabel } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; import { BreadcrumbsPicker, createBreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker'; -import { SideBySideEditorInput } from 'vs/workbench/common/editor'; +import { SideBySideEditorInput, IEditorPartOptions } from 'vs/workbench/common/editor'; import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -134,6 +134,11 @@ export class BreadcrumbsControl { static readonly HEIGHT = 22; + private static readonly SCROLLBAR_SIZES = { + default: 3, + large: 8 + }; + static readonly Payload_Reveal = {}; static readonly Payload_RevealAside = {}; static readonly Payload_Pick = {}; @@ -148,6 +153,7 @@ export class BreadcrumbsControl { private readonly _cfUseQuickPick: BreadcrumbsConfig; private readonly _cfShowIcons: BreadcrumbsConfig; + private readonly _cfTitleScrollbarSizing: BreadcrumbsConfig; readonly domNode: HTMLDivElement; private readonly _widget: BreadcrumbsWidget; @@ -168,7 +174,7 @@ export class BreadcrumbsControl { @IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IThemeService private readonly _themeService: IThemeService, - @IQuickOpenService private readonly _quickOpenService: IQuickOpenService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ITextResourceConfigurationService private readonly _textResourceConfigurationService: ITextResourceConfigurationService, @IFileService private readonly _fileService: IFileService, @@ -180,7 +186,12 @@ export class BreadcrumbsControl { dom.addClass(this.domNode, 'breadcrumbs-control'); dom.append(container, this.domNode); - this._widget = new BreadcrumbsWidget(this.domNode); + this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService); + this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(_configurationService); + this._cfTitleScrollbarSizing = BreadcrumbsConfig.TitleScrollbarSizing.bindTo(_configurationService); + + const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default'; + this._widget = new BreadcrumbsWidget(this.domNode, BreadcrumbsControl.SCROLLBAR_SIZES[sizing]); this._widget.onDidSelectItem(this._onSelectEvent, this, this._disposables); this._widget.onDidFocusItem(this._onFocusEvent, this, this._disposables); this._widget.onDidChangeFocus(this._updateCkBreadcrumbsActive, this, this._disposables); @@ -190,9 +201,6 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService); this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService); - this._cfUseQuickPick = BreadcrumbsConfig.UseQuickPick.bindTo(_configurationService); - this._cfShowIcons = BreadcrumbsConfig.Icons.bindTo(_configurationService); - this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); } @@ -277,6 +285,14 @@ export class BreadcrumbsControl { this._breadcrumbsDisposables.add(listener); this._breadcrumbsDisposables.add(configListener); + const updateScrollbarSizing = () => { + const sizing = this._cfTitleScrollbarSizing.getValue() ?? 'default'; + this._widget.setHorizontalScrollbarSize(BreadcrumbsControl.SCROLLBAR_SIZES[sizing]); + }; + updateScrollbarSizing(); + const updateScrollbarSizeListener = this._cfTitleScrollbarSizing.onDidChange(updateScrollbarSizing); + this._breadcrumbsDisposables.add(updateScrollbarSizeListener); + // close picker on hide/update this._breadcrumbsDisposables.add({ dispose: () => { @@ -343,7 +359,7 @@ export class BreadcrumbsControl { // using quick pick this._widget.setFocused(undefined); this._widget.setSelection(undefined); - this._quickOpenService.show(element instanceof TreeElement ? '@' : ''); + this._quickInputService.quickAccess.show(element instanceof TreeElement ? '@' : ''); return; } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 6e911a75ca..fb1e248487 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -6,8 +6,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as nls from 'vs/nls'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { Action, IAction } from 'vs/base/common/actions'; -import { IEditorQuickOpenEntry, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext, EditorPinnedContext, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -21,15 +19,14 @@ import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textf import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions'; import { SyncActionDescriptor, MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { - CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, OpenToSideFromQuickOpenAction, RevertAndCloseEditorAction, + CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, RevertAndCloseEditorAction, NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, ResetGroupSizesAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, - toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, - QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousEditorFromHistoryAction, ShowAllEditorsByAppearanceAction, ClearEditorHistoryAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, + CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, + QuickAccessPreviousRecentlyUsedEditorInGroupAction, QuickAccessPreviousEditorFromHistoryAction, ShowAllEditorsByAppearanceAction, ClearEditorHistoryAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup, ShowEditorsInActiveGroupByMostRecentlyUsedAction, MoveEditorToLastGroupAction, OpenFirstEditorInGroup, MoveGroupUpAction, MoveGroupDownAction, FocusLastGroupAction, SplitEditorLeftAction, SplitEditorRightAction, @@ -37,16 +34,14 @@ import { JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction, EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, - QuickOpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickOpenNextRecentlyUsedEditorAction as QuickOpenLeastRecentlyUsedEditorAction, - QuickOpenLeastRecentlyUsedEditorInGroupAction + QuickAccessPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { getQuickNavigateHandler, inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen'; +import { inQuickPickContext, getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { isMacintosh } from 'vs/base/common/platform'; -import { AllEditorsByAppearancePicker, ActiveGroupEditorsByMostRecentlyUsedPicker, AllEditorsByMostRecentlyUsedPicker } from 'vs/workbench/browser/parts/editor/editorPicker'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -270,101 +265,10 @@ if (Object.keys(SUPPORTED_ENCODINGS).length > 1) { registry.registerWorkbenchAction(SyncActionDescriptor.create(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL), 'Change File Encoding'); } -export class QuickOpenActionContributor extends ActionBarContributor { - private openToSideActionInstance: OpenToSideFromQuickOpenAction | undefined; - - constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) { - super(); - } - - hasActions(context: unknown): boolean { - const entry = this.getEntry(context); - - return !!entry; - } - - getActions(context: unknown): ReadonlyArray { - const actions: Action[] = []; - - const entry = this.getEntry(context); - if (entry) { - if (!this.openToSideActionInstance) { - this.openToSideActionInstance = this.instantiationService.createInstance(OpenToSideFromQuickOpenAction); - } else { - this.openToSideActionInstance.updateClass(); - } - - actions.push(this.openToSideActionInstance); - } - - return actions; - } - - private getEntry(context: any): IEditorQuickOpenEntry | null { - if (!context || !context.element) { - return null; - } - - return toEditorQuickOpenEntry(context.element); - } -} - -const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); -actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor); - -const editorPickerContextKey = 'inEditorsPicker'; -const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(editorPickerContextKey)); - -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - ActiveGroupEditorsByMostRecentlyUsedPicker, - ActiveGroupEditorsByMostRecentlyUsedPicker.ID, - editorCommands.NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, - editorPickerContextKey, - [ - { - prefix: editorCommands.NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, - needsEditor: false, - description: nls.localize('groupOnePicker', "Show Editors in Active Group By Most Recently Used") - } - ] - ) -); - -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - AllEditorsByAppearancePicker, - AllEditorsByAppearancePicker.ID, - editorCommands.NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, - editorPickerContextKey, - [ - { - prefix: editorCommands.NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, - needsEditor: false, - description: nls.localize('allEditorsPicker', "Show All Opened Editors By Appearance") - } - ] - ) -); - -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - AllEditorsByMostRecentlyUsedPicker, - AllEditorsByMostRecentlyUsedPicker.ID, - editorCommands.NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, - editorPickerContextKey, - [ - { - prefix: editorCommands.NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, - needsEditor: false, - description: nls.localize('allEditorsPickerByMostRecentlyUsed', "Show All Opened Editors By Most Recently Used") - } - ] - ) -); - // Register Editor Quick Access const quickAccessRegistry = Registry.as(QuickAccessExtensions.Quickaccess); +const editorPickerContextKey = 'inEditorsPicker'; +const editorPickerContext = ContextKeyExpr.and(inQuickPickContext, ContextKeyExpr.has(editorPickerContextKey)); quickAccessRegistry.registerQuickAccessProvider({ ctor: ActiveGroupEditorsByMostRecentlyUsedQuickAccess, @@ -469,29 +373,29 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoColu // Register Quick Editor Actions including built in quick navigate support for some -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorAction, QuickOpenPreviousRecentlyUsedEditorAction.ID, QuickOpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Quick Open Previous Recently Used Editor', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenLeastRecentlyUsedEditorAction, QuickOpenLeastRecentlyUsedEditorAction.ID, QuickOpenLeastRecentlyUsedEditorAction.LABEL), 'View: Quick Open Least Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessPreviousRecentlyUsedEditorAction, QuickAccessPreviousRecentlyUsedEditorAction.ID, QuickAccessPreviousRecentlyUsedEditorAction.LABEL), 'View: Quick Open Previous Recently Used Editor', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessLeastRecentlyUsedEditorAction, QuickAccessLeastRecentlyUsedEditorAction.ID, QuickAccessLeastRecentlyUsedEditorAction.LABEL), 'View: Quick Open Least Recently Used Editor', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousRecentlyUsedEditorInGroupAction.ID, QuickOpenPreviousRecentlyUsedEditorInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }), 'View: Quick Open Previous Recently Used Editor in Group', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenLeastRecentlyUsedEditorInGroupAction, QuickOpenLeastRecentlyUsedEditorInGroupAction.ID, QuickOpenLeastRecentlyUsedEditorInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }), 'View: Quick Open Least Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessPreviousRecentlyUsedEditorInGroupAction, QuickAccessPreviousRecentlyUsedEditorInGroupAction.ID, QuickAccessPreviousRecentlyUsedEditorInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }), 'View: Quick Open Previous Recently Used Editor in Group', category); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessLeastRecentlyUsedEditorInGroupAction, QuickAccessLeastRecentlyUsedEditorInGroupAction.ID, QuickAccessLeastRecentlyUsedEditorInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }), 'View: Quick Open Least Recently Used Editor in Group', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousEditorFromHistoryAction, QuickOpenPreviousEditorFromHistoryAction.ID, QuickOpenPreviousEditorFromHistoryAction.LABEL), 'Quick Open Previous Editor from History'); +registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessPreviousEditorFromHistoryAction, QuickAccessPreviousEditorFromHistoryAction.ID, QuickAccessPreviousEditorFromHistoryAction.LABEL), 'Quick Open Previous Editor from History'); -const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; +const quickAccessNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigateNextInEditorPickerId, + id: quickAccessNavigateNextInEditorPickerId, weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigateNextInEditorPickerId, true), + handler: getQuickNavigateHandler(quickAccessNavigateNextInEditorPickerId, true), when: editorPickerContext, primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }); -const quickOpenNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker'; +const quickAccessNavigatePreviousInEditorPickerId = 'workbench.action.quickOpenNavigatePreviousInEditorPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigatePreviousInEditorPickerId, + id: quickAccessNavigatePreviousInEditorPickerId, weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigatePreviousInEditorPickerId, false), + handler: getQuickNavigateHandler(quickAccessNavigatePreviousInEditorPickerId, false), when: editorPickerContext, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index d42e5a247e..4199d1707a 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -30,6 +30,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { highlightModifiedTabs: false, tabCloseButton: 'right', tabSizing: 'fit', + titleScrollbarSizing: 'default', focusRecentEditorAfterClose: true, showIcons: true, enablePreview: true, diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index b63024ab3c..e4f8b28344 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -5,26 +5,22 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { mixin } from 'vs/base/common/objects'; -import { IEditorInput, EditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor'; -import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IEditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection, SaveReason, EditorsOrder, SideBySideEditorInput } from 'vs/workbench/common/editor'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { CLOSE_EDITOR_COMMAND_ID, MOVE_ACTIVE_EDITOR_COMMAND_ID, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { values } from 'vs/base/common/map'; -import { ItemActivation } from 'vs/platform/quickinput/common/quickInput'; +import { ItemActivation, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { AllEditorsByMostRecentlyUsedQuickAccess, ActiveGroupEditorsByMostRecentlyUsedQuickAccess, AllEditorsByAppearanceQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; export class ExecuteCommandAction extends Action { @@ -390,63 +386,6 @@ export class FocusBelowGroup extends BaseFocusGroupAction { } } -export class OpenToSideFromQuickOpenAction extends Action { - - static readonly OPEN_TO_SIDE_ID = 'workbench.action.openToSide'; - static readonly OPEN_TO_SIDE_LABEL = nls.localize('openToSide', "Open to the Side"); - - constructor( - @IEditorService private readonly editorService: IEditorService, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - super(OpenToSideFromQuickOpenAction.OPEN_TO_SIDE_ID, OpenToSideFromQuickOpenAction.OPEN_TO_SIDE_LABEL); - - this.updateClass(); - } - - updateClass(): void { - const preferredDirection = preferredSideBySideGroupDirection(this.configurationService); - - this.class = (preferredDirection === GroupDirection.RIGHT) ? 'codicon-split-horizontal' : 'codicon-split-vertical'; - } - - async run(context: unknown): Promise { - const entry = toEditorQuickOpenEntry(context); - if (entry) { - const input = entry.getInput(); - if (input) { - if (input instanceof EditorInput) { - await this.editorService.openEditor(input, entry.getOptions(), SIDE_GROUP); - return; - } - - const resourceEditorInput = input as IResourceEditorInput; - resourceEditorInput.options = mixin(resourceEditorInput.options, entry.getOptions()); - - await this.editorService.openEditor(resourceEditorInput, SIDE_GROUP); - } - } - } -} - -export function toEditorQuickOpenEntry(element: unknown): IEditorQuickOpenEntry | null { - - // QuickOpenEntryGroup - if (element instanceof QuickOpenEntryGroup) { - const group = element; - if (group.getEntry()) { - element = group.getEntry(); - } - } - - // EditorQuickOpenEntry or EditorQuickOpenEntryGroup both implement IEditorQuickOpenEntry - if (element instanceof EditorQuickOpenEntry || element instanceof EditorQuickOpenEntryGroup) { - return element; - } - - return null; -} - export class CloseEditorAction extends Action { static readonly ID = 'workbench.action.closeActiveEditor'; @@ -1214,56 +1153,68 @@ export class ClearRecentFilesAction extends Action { } } -export class ShowEditorsInActiveGroupByMostRecentlyUsedAction extends QuickOpenAction { +export class ShowEditorsInActiveGroupByMostRecentlyUsedAction extends Action { static readonly ID = 'workbench.action.showEditorsInActiveGroup'; static readonly LABEL = nls.localize('showEditorsInActiveGroup', "Show Editors in Active Group By Most Recently Used"); constructor( - actionId: string, - actionLabel: string, - @IQuickOpenService quickOpenService: IQuickOpenService + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { - super(actionId, actionLabel, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService); + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX); } } -export class ShowAllEditorsByAppearanceAction extends QuickOpenAction { +export class ShowAllEditorsByAppearanceAction extends Action { static readonly ID = 'workbench.action.showAllEditors'; static readonly LABEL = nls.localize('showAllEditors', "Show All Editors By Appearance"); constructor( - actionId: string, - actionLabel: string, - @IQuickOpenService quickOpenService: IQuickOpenService + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { - super(actionId, actionLabel, NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX, quickOpenService); + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(AllEditorsByAppearanceQuickAccess.PREFIX); } } -export class ShowAllEditorsByMostRecentlyUsedAction extends QuickOpenAction { +export class ShowAllEditorsByMostRecentlyUsedAction extends Action { static readonly ID = 'workbench.action.showAllEditorsByMostRecentlyUsed'; static readonly LABEL = nls.localize('showAllEditorsByMostRecentlyUsed', "Show All Editors By Most Recently Used"); constructor( - actionId: string, - actionLabel: string, - @IQuickOpenService quickOpenService: IQuickOpenService + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { - super(actionId, actionLabel, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService); + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(AllEditorsByMostRecentlyUsedQuickAccess.PREFIX); } } -export class BaseQuickOpenEditorAction extends Action { +export class BaseQuickAccessEditorAction extends Action { constructor( id: string, label: string, private prefix: string, private itemActivation: ItemActivation | undefined, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, + @IQuickInputService private readonly quickInputService: IQuickInputService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { super(id, label); @@ -1272,14 +1223,14 @@ export class BaseQuickOpenEditorAction extends Action { async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); - this.quickOpenService.show(this.prefix, { + this.quickInputService.quickAccess.show(this.prefix, { quickNavigateConfiguration: { keybindings }, - autoFocus: this.itemActivation === ItemActivation.LAST ? { autoFocusLastEntry: true } : undefined + itemActivation: this.itemActivation }); } } -export class QuickOpenPreviousRecentlyUsedEditorAction extends BaseQuickOpenEditorAction { +export class QuickAccessPreviousRecentlyUsedEditorAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditor'; static readonly LABEL = nls.localize('quickOpenPreviousRecentlyUsedEditor', "Quick Open Previous Recently Used Editor"); @@ -1287,14 +1238,14 @@ export class QuickOpenPreviousRecentlyUsedEditorAction extends BaseQuickOpenEdit constructor( id: string, label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, undefined, quickOpenService, keybindingService); + super(id, label, AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, undefined, quickInputService, keybindingService); } } -export class QuickOpenNextRecentlyUsedEditorAction extends BaseQuickOpenEditorAction { +export class QuickAccessLeastRecentlyUsedEditorAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenLeastRecentlyUsedEditor'; static readonly LABEL = nls.localize('quickOpenLeastRecentlyUsedEditor', "Quick Open Least Recently Used Editor"); @@ -1302,14 +1253,14 @@ export class QuickOpenNextRecentlyUsedEditorAction extends BaseQuickOpenEditorAc constructor( id: string, label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, undefined, quickOpenService, keybindingService); + super(id, label, AllEditorsByMostRecentlyUsedQuickAccess.PREFIX, undefined, quickInputService, keybindingService); } } -export class QuickOpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorAction { +export class QuickAccessPreviousRecentlyUsedEditorInGroupAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup'; static readonly LABEL = nls.localize('quickOpenPreviousRecentlyUsedEditorInGroup', "Quick Open Previous Recently Used Editor in Group"); @@ -1317,14 +1268,14 @@ export class QuickOpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickO constructor( id: string, label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, undefined, quickOpenService, keybindingService); + super(id, label, ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, undefined, quickInputService, keybindingService); } } -export class QuickOpenLeastRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorAction { +export class QuickAccessLeastRecentlyUsedEditorInGroupAction extends BaseQuickAccessEditorAction { static readonly ID = 'workbench.action.quickOpenLeastRecentlyUsedEditorInGroup'; static readonly LABEL = nls.localize('quickOpenLeastRecentlyUsedEditorInGroup', "Quick Open Least Recently Used Editor in Group"); @@ -1332,14 +1283,14 @@ export class QuickOpenLeastRecentlyUsedEditorInGroupAction extends BaseQuickOpen constructor( id: string, label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService ) { - super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, ItemActivation.LAST, quickOpenService, keybindingService); + super(id, label, ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX, ItemActivation.LAST, quickInputService, keybindingService); } } -export class QuickOpenPreviousEditorFromHistoryAction extends Action { +export class QuickAccessPreviousEditorFromHistoryAction extends Action { static readonly ID = 'workbench.action.openPreviousEditorFromHistory'; static readonly LABEL = nls.localize('navigateEditorHistoryByInput', "Quick Open Previous Editor from History"); @@ -1347,7 +1298,7 @@ export class QuickOpenPreviousEditorFromHistoryAction extends Action { constructor( id: string, label: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, + @IQuickInputService private readonly quickInputService: IQuickInputService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { super(id, label); @@ -1356,7 +1307,7 @@ export class QuickOpenPreviousEditorFromHistoryAction extends Action { async run(): Promise { const keybindings = this.keybindingService.lookupKeybindings(this.id); - this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings } }); + this.quickInputService.quickAccess.show('', { quickNavigateConfiguration: { keybindings } }); } } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 5f0d56fb5d..0894e73047 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -13,7 +13,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { URI } from 'vs/base/common/uri'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IListService } from 'vs/platform/list/browser/listService'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { distinct, coalesce } from 'vs/base/common/arrays'; @@ -22,6 +22,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { ActiveGroupEditorsByMostRecentlyUsedQuickAccess } from 'vs/workbench/browser/parts/editor/editorQuickAccess'; export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors'; export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup'; @@ -46,10 +47,6 @@ export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown'; export const SPLIT_EDITOR_LEFT = 'workbench.action.splitEditorLeft'; export const SPLIT_EDITOR_RIGHT = 'workbench.action.splitEditorRight'; -export const NAVIGATE_ALL_EDITORS_BY_APPEARANCE_PREFIX = 'edt '; -export const NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX = 'edt mru '; -export const NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX = 'edt active '; - export const OPEN_EDITOR_AT_INDEX_COMMAND_ID = 'workbench.action.openEditorAtIndex'; export interface ActiveEditorMoveArguments { @@ -652,7 +649,7 @@ function registerCloseEditorCommands() { primary: undefined, handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => { const editorGroupService = accessor.get(IEditorGroupsService); - const quickOpenService = accessor.get(IQuickOpenService); + const quickInputService = accessor.get(IQuickInputService); const commandsContext = getCommandsContext(resourceOrContext, context); if (commandsContext && typeof commandsContext.groupId === 'number') { @@ -662,7 +659,7 @@ function registerCloseEditorCommands() { } } - return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX); + return quickInputService.quickAccess.show(ActiveGroupEditorsByMostRecentlyUsedQuickAccess.PREFIX); } }); diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 04d3bcfea4..0260b28639 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -23,10 +23,10 @@ export interface IOpenEditorResult { export class EditorControl extends Disposable { - get minimumWidth() { return this._activeEditorPane ? this._activeEditorPane.minimumWidth : DEFAULT_EDITOR_MIN_DIMENSIONS.width; } - get minimumHeight() { return this._activeEditorPane ? this._activeEditorPane.minimumHeight : DEFAULT_EDITOR_MIN_DIMENSIONS.height; } - get maximumWidth() { return this._activeEditorPane ? this._activeEditorPane.maximumWidth : DEFAULT_EDITOR_MAX_DIMENSIONS.width; } - get maximumHeight() { return this._activeEditorPane ? this._activeEditorPane.maximumHeight : DEFAULT_EDITOR_MAX_DIMENSIONS.height; } + get minimumWidth() { return this._activeEditorPane?.minimumWidth ?? DEFAULT_EDITOR_MIN_DIMENSIONS.width; } + get minimumHeight() { return this._activeEditorPane?.minimumHeight ?? DEFAULT_EDITOR_MIN_DIMENSIONS.height; } + get maximumWidth() { return this._activeEditorPane?.maximumWidth ?? DEFAULT_EDITOR_MAX_DIMENSIONS.width; } + get maximumHeight() { return this._activeEditorPane?.maximumHeight ?? DEFAULT_EDITOR_MAX_DIMENSIONS.height; } private readonly _onDidFocus = this._register(new Emitter()); readonly onDidFocus = this._onDidFocus.event; diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts deleted file mode 100644 index 7a3652118a..0000000000 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ /dev/null @@ -1,265 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./media/editorpicker'; -import * as nls from 'vs/nls'; -import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { toResource, SideBySideEditor, IEditorInput, EditorsOrder } from 'vs/workbench/common/editor'; -import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/common/fuzzyScorer'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export class EditorPickerEntry extends QuickOpenEntryGroup { - - constructor( - private editor: IEditorInput, - public readonly group: IEditorGroup, - @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService - ) { - super(); - } - - getLabelOptions(): IIconLabelValueOptions { - return { - extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource()), - italic: !this.group.isPinned(this.editor) - }; - } - - getLabel(): string { - return this.editor.getName(); - } - - getIcon(): string { - return this.editor.isDirty() && !this.editor.isSaving() ? 'codicon codicon-circle-filled' : ''; - } - - getResource() { - return toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER }); - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, editor group picker", this.getLabel()); - } - - getDescription() { - return this.editor.getDescription(); - } - - run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - return this.runOpen(context); - } - - return super.run(mode, context); - } - - private runOpen(context: IEntryRunContext): boolean { - this.group.openEditor(this.editor); - - return true; - } -} - -export abstract class BaseEditorPicker extends QuickOpenHandler { - private scorerCache: ScorerCache; - - constructor( - @IInstantiationService protected instantiationService: IInstantiationService, - @IEditorService protected editorService: IEditorService, - @IEditorGroupsService protected editorGroupService: IEditorGroupsService - ) { - super(); - - this.scorerCache = Object.create(null); - } - - getResults(searchValue: string, token: CancellationToken): Promise { - const editorEntries = this.getEditorEntries(); - if (!editorEntries.length) { - return Promise.resolve(null); - } - - // Prepare search for scoring - const query = prepareQuery(searchValue); - - const entries = editorEntries.filter(e => { - if (!query.value) { - return true; - } - - const itemScore = scoreItem(e, query, true, QuickOpenItemAccessor, this.scorerCache); - if (!itemScore.score) { - return false; - } - - e.setHighlights(itemScore.labelMatch || [], itemScore.descriptionMatch); - - return true; - }); - - // Sorting - if (query.value) { - const groups = this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE); - entries.sort((e1, e2) => { - if (e1.group !== e2.group) { - return groups.indexOf(e1.group) - groups.indexOf(e2.group); // older groups first - } - - return compareItemsByScore(e1, e2, query, true, QuickOpenItemAccessor, this.scorerCache); - }); - } - - // Grouping (for more than one group) - if (this.editorGroupService.count > 1) { - let lastGroup: IEditorGroup; - entries.forEach(e => { - if (!lastGroup || lastGroup !== e.group) { - e.setGroupLabel(e.group.label); - e.setShowBorder(!!lastGroup); - lastGroup = e.group; - } - }); - } - - return Promise.resolve(new QuickOpenModel(entries)); - } - - onClose(canceled: boolean): void { - this.scorerCache = Object.create(null); - } - - protected abstract count(): number; - - protected abstract getEditorEntries(): EditorPickerEntry[]; - - getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { - if (searchValue || !context.quickNavigateConfiguration) { - return { - autoFocusFirstEntry: true - }; - } - - const isShiftNavigate = (context.quickNavigateConfiguration && context.quickNavigateConfiguration.keybindings.some(k => { - const [firstPart, chordPart] = k.getParts(); - if (chordPart) { - return false; - } - - return firstPart.shiftKey; - })); - - if (isShiftNavigate) { - return { - autoFocusLastEntry: true - }; - } - - const editors = this.count(); - return { - autoFocusFirstEntry: editors === 1, - autoFocusSecondEntry: editors > 1 - }; - } -} - -export class ActiveGroupEditorsByMostRecentlyUsedPicker extends BaseEditorPicker { - - static readonly ID = 'workbench.picker.activeGroupEditorsByMostRecentlyUsed'; - - protected count(): number { - return this.group.count; - } - - protected getEditorEntries(): EditorPickerEntry[] { - return this.group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).map(editor => this.instantiationService.createInstance(EditorPickerEntry, editor, this.group)); - } - - private get group(): IEditorGroup { - return this.editorGroupService.activeGroup; - } - - getEmptyLabel(searchString: string): string { - if (searchString) { - return nls.localize('noResultsFoundInGroup', "No matching opened editor found in active editor group"); - } - - return nls.localize('noOpenedEditors', "List of opened editors is currently empty in active editor group"); - } -} - -export abstract class BaseAllEditorsPicker extends BaseEditorPicker { - - constructor( - @IInstantiationService instantiationService: IInstantiationService, - @IEditorService editorService: IEditorService, - @IEditorGroupsService editorGroupService: IEditorGroupsService - ) { - super(instantiationService, editorService, editorGroupService); - } - - protected count(): number { - return this.editorService.count; - } - - getEmptyLabel(searchString: string): string { - if (searchString) { - return nls.localize('noResultsFound', "No matching opened editor found"); - } - - return nls.localize('noOpenedEditorsAllGroups', "List of opened editors is currently empty"); - } - - getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { - if (searchValue) { - return { - autoFocusFirstEntry: true - }; - } - - return super.getAutoFocus(searchValue, context); - } -} - -export class AllEditorsByAppearancePicker extends BaseAllEditorsPicker { - - static readonly ID = 'workbench.picker.editorsByAppearance'; - - protected getEditorEntries(): EditorPickerEntry[] { - const entries: EditorPickerEntry[] = []; - - for (const group of this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)) { - for (const editor of group.getEditors(EditorsOrder.SEQUENTIAL)) { - entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, group)); - } - } - - return entries; - } -} - -export class AllEditorsByMostRecentlyUsedPicker extends BaseAllEditorsPicker { - - static readonly ID = 'workbench.picker.editorsByMostRecentlyUsed'; - - protected getEditorEntries(): EditorPickerEntry[] { - const entries: EditorPickerEntry[] = []; - - for (const { editor, groupId } of this.editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)) { - entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, this.editorGroupService.getGroup(groupId)!)); - } - - return entries; - } -} diff --git a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts index 85e2bca2ff..617555bf13 100644 --- a/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts +++ b/src/vs/workbench/browser/parts/editor/editorQuickAccess.ts @@ -127,10 +127,6 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro iconClasses: getIconClasses(this.modelService, this.modeService, resource), italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor), buttons: (() => { - if (this.pickState.isQuickNavigating) { - return undefined; // no actions when quick navigating - } - return [ { iconClass: isDirty ? 'dirty-editor codicon-circle-filled' : 'codicon-close', diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 40368b13d8..02dad80ca6 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -1212,7 +1212,7 @@ export class ChangeModeAction extends Action { this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target); } - }, 50 /* quick open is sensitive to being opened so soon after another */); + }, 50 /* quick input is sensitive to being opened so soon after another */); } private getFakeResource(lang: string): URI | undefined { @@ -1347,7 +1347,7 @@ export class ChangeEncodingAction extends Action { return; } - await timeout(50); // quick open is sensitive to being opened so soon after another + await timeout(50); // quick input is sensitive to being opened so soon after another const resource = toResource(activeEditorPane.input, { supportSideBySide: SideBySideEditor.MASTER }); if (!resource || (!this.fileService.canHandleResource(resource) && resource.scheme !== Schemas.untitled)) { diff --git a/src/vs/workbench/browser/parts/editor/media/editorpicker.css b/src/vs/workbench/browser/parts/editor/media/editorpicker.css deleted file mode 100644 index 6e3cb19a7a..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/editorpicker.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-workbench .monaco-quick-open-widget .quick-open-tree .quick-open-entry.editor-preview { - font-style: italic; -} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 8a69e3b408..2a57a990e3 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -88,8 +88,8 @@ export class NoTabsTitleControl extends TitleControl { private onTitleLabelClick(e: MouseEvent): void { EventHelper.stop(e, false); - // delayed to let the onTitleClick() come first which can cause a focus change which can close quick open - setTimeout(() => this.quickOpenService.show()); + // delayed to let the onTitleClick() come first which can cause a focus change which can close quick access + setTimeout(() => this.quickInputService.quickAccess.show()); } private onTitleDoubleClick(e: MouseEvent): void { @@ -111,10 +111,10 @@ export class NoTabsTitleControl extends TitleControl { } } else { // TODO@rebornix - // gesture tap should open the quick open + // gesture tap should open the quick access // editorGroupView will focus on the editor again when there are mouse/pointer/touch down events // we need to wait a bit as `GesureEvent.Tap` is generated from `touchstart` and then `touchend` evnets, which are not an atom event. - setTimeout(() => this.quickOpenService.show(), 50); + setTimeout(() => this.quickInputService.quickAccess.show(), 50); } } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 14acbd6c8f..13526247da 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -19,7 +19,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; @@ -61,6 +61,11 @@ type AugmentedLabel = IEditorInputLabel & { editor: IEditorInput }; export class TabsTitleControl extends TitleControl { + private static readonly SCROLLBAR_SIZES = { + default: 3, + large: 10 + }; + private titleContainer: HTMLElement | undefined; private tabsContainer: HTMLElement | undefined; private editorToolbarContainer: HTMLElement | undefined; @@ -89,7 +94,7 @@ export class TabsTitleControl extends TitleControl { @ITelemetryService telemetryService: ITelemetryService, @INotificationService notificationService: INotificationService, @IMenuService menuService: IMenuService, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @@ -97,7 +102,7 @@ export class TabsTitleControl extends TitleControl { @IEditorService private readonly editorService: EditorServiceImpl, @IRemotePathService private readonly remotePathService: IRemotePathService ) { - super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService); + super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickInputService, themeService, extensionService, configurationService, fileService); this.tabResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER)); this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL)); @@ -108,6 +113,16 @@ export class TabsTitleControl extends TitleControl { (async () => this.path = await this.remotePathService.path)(); } + protected registerListeners(): void { + super.registerListeners(); + + this._register(this.accessor.onDidEditorPartOptionsChange(e => { + if (e.oldPartOptions.titleScrollbarSizing !== e.newPartOptions.titleScrollbarSizing) { + this.updateTabsScrollbarSizing(); + } + })); + } + protected create(parent: HTMLElement): void { this.titleContainer = parent; @@ -148,10 +163,10 @@ export class TabsTitleControl extends TitleControl { private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement { const tabsScrollbar = new ScrollableElement(scrollable, { horizontal: ScrollbarVisibility.Auto, + horizontalScrollbarSize: this.getTabsScrollbarSizing(), vertical: ScrollbarVisibility.Hidden, scrollYToX: true, - useShadows: false, - horizontalScrollbarSize: 3 + useShadows: false }); tabsScrollbar.onScroll(e => { @@ -161,6 +176,20 @@ export class TabsTitleControl extends TitleControl { return tabsScrollbar; } + private updateTabsScrollbarSizing(): void { + this.tabsScrollbar?.updateOptions({ + horizontalScrollbarSize: this.getTabsScrollbarSizing() + }); + } + + private getTabsScrollbarSizing(): number { + if (this.accessor.partOptions.titleScrollbarSizing !== 'large') { + return TabsTitleControl.SCROLLBAR_SIZES.default; + } + + return TabsTitleControl.SCROLLBAR_SIZES.large; + } + private updateBreadcrumbsControl(): void { if (this.breadcrumbsControl && this.breadcrumbsControl.update()) { // relayout when we have a breadcrumbs and when update changed diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 53c707cc91..4a74150f1e 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -6,7 +6,7 @@ import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; @@ -23,11 +23,10 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant, Themable } from 'vs/platform/theme/common/themeService'; -import { prepareActions } from 'vs/workbench/browser/actions'; import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceDataTransfers, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; @@ -77,7 +76,7 @@ export abstract class TitleControl extends Themable { // {{SQL CARBON EDIT}} -- need to make the notification service protected @INotificationService protected readonly notificationService: INotificationService, @IMenuService private readonly menuService: IMenuService, - @IQuickOpenService protected quickOpenService: IQuickOpenService, + @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, @IExtensionService private readonly extensionService: IExtensionService, @IConfigurationService protected configurationService: IConfigurationService, @@ -94,7 +93,7 @@ export abstract class TitleControl extends Themable { this.registerListeners(); } - private registerListeners(): void { + protected registerListeners(): void { // Update actions toolbar when extension register that may contribute them this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar())); diff --git a/src/vs/workbench/browser/parts/quickopen/media/quickopen.css b/src/vs/workbench/browser/parts/quickopen/media/quickopen.css deleted file mode 100644 index d3b3e5b34c..0000000000 --- a/src/vs/workbench/browser/parts/quickopen/media/quickopen.css +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.vs .monaco-workbench .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.none, -.vs-dark .monaco-workbench .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.none { - width: 16px; - background: none; -} - -.monaco-workbench .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.dirty { - width: 14px; - height: 18px; -} diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts deleted file mode 100644 index da553df5a5..0000000000 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts +++ /dev/null @@ -1,137 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Registry } from 'vs/platform/registry/common/platform'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { RemoveFromEditorHistoryAction } from 'vs/workbench/browser/parts/quickopen/quickOpenController'; -import { QuickOpenSelectNextAction, QuickOpenSelectPreviousAction, inQuickOpenContext, getQuickNavigateHandler, QuickOpenNavigateNextAction, QuickOpenNavigatePreviousAction, defaultQuickOpenContext, QUICKOPEN_ACTION_ID, QUICKOPEN_ACION_LABEL } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.closeQuickOpen', - weight: KeybindingWeight.WorkbenchContrib, - when: inQuickOpenContext, - primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - handler: accessor => { - const quickOpenService = accessor.get(IQuickOpenService); - quickOpenService.close(); - const quickInputService = accessor.get(IQuickInputService); - return quickInputService.cancel(); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.acceptSelectedQuickOpenItem', - weight: KeybindingWeight.WorkbenchContrib, - when: inQuickOpenContext, - primary: 0, - handler: accessor => { - const quickOpenService = accessor.get(IQuickOpenService); - quickOpenService.accept(); - const quickInputService = accessor.get(IQuickInputService); - return quickInputService.accept(); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.alternativeAcceptSelectedQuickOpenItem', - weight: KeybindingWeight.WorkbenchContrib, - when: inQuickOpenContext, - primary: 0, - handler: accessor => { - const quickInputService = accessor.get(IQuickInputService); - return quickInputService.accept({ ctrlCmd: true, alt: false }); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.focusQuickOpen', - weight: KeybindingWeight.WorkbenchContrib, - when: inQuickOpenContext, - primary: 0, - handler: accessor => { - const quickOpenService = accessor.get(IQuickOpenService); - quickOpenService.focus(); - const quickInputService = accessor.get(IQuickInputService); - quickInputService.focus(); - } -}); - -const registry = Registry.as(ActionExtensions.WorkbenchActions); - -const globalQuickOpenKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_E], mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: undefined } }; - -KeybindingsRegistry.registerKeybindingRule({ - id: QUICKOPEN_ACTION_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: globalQuickOpenKeybinding.primary, - secondary: globalQuickOpenKeybinding.secondary, - mac: globalQuickOpenKeybinding.mac -}); - -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { id: QUICKOPEN_ACTION_ID, title: { value: QUICKOPEN_ACION_LABEL, original: 'Go to File...' } } -}); - -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenSelectNextAction, QuickOpenSelectNextAction.ID, QuickOpenSelectNextAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Next in Quick Open'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenSelectPreviousAction, QuickOpenSelectPreviousAction.ID, QuickOpenSelectPreviousAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Previous in Quick Open'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNavigateNextAction, QuickOpenNavigateNextAction.ID, QuickOpenNavigateNextAction.LABEL), 'Navigate Next in Quick Open'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNavigatePreviousAction, QuickOpenNavigatePreviousAction.ID, QuickOpenNavigatePreviousAction.LABEL), 'Navigate Previous in Quick Open'); -registry.registerWorkbenchAction(SyncActionDescriptor.create(RemoveFromEditorHistoryAction, RemoveFromEditorHistoryAction.ID, RemoveFromEditorHistoryAction.LABEL), 'Remove From History'); - -const quickOpenNavigateNextInFilePickerId = 'workbench.action.quickOpenNavigateNextInFilePicker'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigateNextInFilePickerId, - weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigateNextInFilePickerId, true), - when: defaultQuickOpenContext, - primary: globalQuickOpenKeybinding.primary, - secondary: globalQuickOpenKeybinding.secondary, - mac: globalQuickOpenKeybinding.mac -}); - -const quickOpenNavigatePreviousInFilePickerId = 'workbench.action.quickOpenNavigatePreviousInFilePicker'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigatePreviousInFilePickerId, - weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigatePreviousInFilePickerId, false), - when: defaultQuickOpenContext, - primary: globalQuickOpenKeybinding.primary | KeyMod.Shift, - secondary: [globalQuickOpenKeybinding.secondary[0] | KeyMod.Shift], - mac: { - primary: globalQuickOpenKeybinding.mac.primary | KeyMod.Shift, - secondary: undefined - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.quickPickManyToggle', - weight: KeybindingWeight.WorkbenchContrib, - when: inQuickOpenContext, - primary: 0, - handler: accessor => { - const quickInputService = accessor.get(IQuickInputService); - quickInputService.toggle(); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.quickInputBack', - weight: KeybindingWeight.WorkbenchContrib + 50, - when: inQuickOpenContext, - primary: 0, - win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, - mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, - linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS }, - handler: accessor => { - const quickInputService = accessor.get(IQuickInputService); - quickInputService.back(); - } -}); diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts deleted file mode 100644 index 8184340ffb..0000000000 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ /dev/null @@ -1,910 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./media/quickopen'; -import * as nls from 'vs/nls'; -import * as browser from 'vs/base/browser/browser'; -import * as strings from 'vs/base/common/strings'; -import { URI } from 'vs/base/common/uri'; -import * as resources from 'vs/base/common/resources'; -import * as types from 'vs/base/common/types'; -import { Action } from 'vs/base/common/actions'; -import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, QuickOpenItemAccessorClass } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenWidget, HideReason } from 'vs/base/parts/quickopen/browser/quickOpenWidget'; -import { ContributableActionProvider } from 'vs/workbench/browser/actions'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { EditorInput, IWorkbenchEditorConfiguration, IEditorInput } from 'vs/workbench/common/editor'; -import { Component } from 'vs/workbench/common/component'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { QuickOpenHandler, QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions, EditorQuickOpenEntry, CLOSE_ON_FOCUS_LOST_CONFIG, SEARCH_EDITOR_HISTORY, PRESERVE_INPUT_CONFIG, ENABLE_EXPERIMENTAL_VERSION_CONFIG } from 'vs/workbench/browser/quickopen'; -import * as errors from 'vs/base/common/errors'; -import { IQuickOpenService, IShowOptions } from 'vs/platform/quickOpen/common/quickOpen'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { quickInputBackground, quickInputForeground } from 'vs/platform/theme/common/colorRegistry'; -import { attachQuickOpenStyler } from 'vs/platform/theme/common/styler'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IFileService } from 'vs/platform/files/common/files'; -import { scoreItem, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/common/fuzzyScorer'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; -import { Schemas } from 'vs/base/common/network'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Dimension, addClass } from 'vs/base/browser/dom'; -import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { timeout } from 'vs/base/common/async'; -import { IQuickInputService, IQuickPickItem, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; - -const HELP_PREFIX = '?'; - -type ValueCallback = (value: T | Promise) => void; - -export class QuickOpenController extends Component implements IQuickOpenService { - - private static readonly MAX_SHORT_RESPONSE_TIME = 500; - private static readonly ID = 'workbench.component.quickopen'; - - _serviceBrand: undefined; - - private readonly _onShow: Emitter = this._register(new Emitter()); - readonly onShow: Event = this._onShow.event; - - private readonly _onHide: Emitter = this._register(new Emitter()); - readonly onHide: Event = this._onHide.event; - - private preserveInput: boolean | undefined; - private isQuickOpen: boolean | undefined; - private lastInputValue: string | undefined; - private lastSubmittedInputValue: string | undefined; - private quickOpenWidget: QuickOpenWidget | undefined; - private mapResolvedHandlersToPrefix: Map> = new Map(); - private mapContextKeyToContext: Map> = new Map(); - private handlerOnOpenCalled: Set = new Set(); - private promisesToCompleteOnHide: ValueCallback[] = []; - private previousActiveHandlerDescriptor: QuickOpenHandlerDescriptor | null | undefined; - private actionProvider = new ContributableActionProvider(); - private closeOnFocusLost: boolean | undefined; - private searchInEditorHistory: boolean | undefined; - private editorHistoryHandler: EditorHistoryHandler; - private pendingGetResultsInvocation: CancellationTokenSource | null = null; - - private get useNewExperimentalVersion() { - return this.configurationService.getValue(ENABLE_EXPERIMENTAL_VERSION_CONFIG) === true; - } - - constructor( - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @INotificationService private readonly notificationService: INotificationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService, - @IQuickInputService private readonly quickInputService: IQuickInputService - ) { - super(QuickOpenController.ID, themeService, storageService); - - this.editorHistoryHandler = this.instantiationService.createInstance(EditorHistoryHandler); - - this.updateConfiguration(); - - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration())); - this._register(this.layoutService.onPartVisibilityChange(() => this.positionQuickOpenWidget())); - this._register(browser.onDidChangeZoomLevel(() => this.positionQuickOpenWidget())); - this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); - } - - private updateConfiguration(): void { - if (this.environmentService.args['sticky-quickopen']) { - this.closeOnFocusLost = false; - } else { - this.closeOnFocusLost = this.configurationService.getValue(CLOSE_ON_FOCUS_LOST_CONFIG); - } - this.preserveInput = this.configurationService.getValue(PRESERVE_INPUT_CONFIG); - - this.searchInEditorHistory = this.configurationService.getValue(SEARCH_EDITOR_HISTORY); - } - - navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration): void { - if (this.useNewExperimentalVersion) { - // already handled - } else { - if (this.quickOpenWidget) { - this.quickOpenWidget.navigate(next, quickNavigate); - } - } - } - - accept(): void { - if (this.useNewExperimentalVersion) { - // already handled - } else { - if (this.quickOpenWidget && this.quickOpenWidget.isVisible()) { - this.quickOpenWidget.accept(); - } - } - } - - focus(): void { - if (this.useNewExperimentalVersion) { - // already handled - } else { - if (this.quickOpenWidget && this.quickOpenWidget.isVisible()) { - this.quickOpenWidget.focus(); - } - } - } - - close(): void { - if (this.useNewExperimentalVersion) { - // already handled - } else { - if (this.quickOpenWidget && this.quickOpenWidget.isVisible()) { - this.quickOpenWidget.hide(HideReason.CANCELED); - } - } - } - - private emitQuickOpenVisibilityChange(isVisible: boolean): void { - if (isVisible) { - this._onShow.fire(); - } else { - this._onHide.fire(); - } - } - - show(prefix?: string, options?: IShowOptions): Promise { - if (this.useNewExperimentalVersion) { - this.quickInputService.quickAccess.show(prefix, { - quickNavigateConfiguration: options?.quickNavigateConfiguration, - itemActivation: (() => { - if (options?.autoFocus?.autoFocusSecondEntry) { - return ItemActivation.SECOND; - } - - if (options?.autoFocus?.autoFocusLastEntry) { - return ItemActivation.LAST; - } - - return undefined; - })() - }); - - return Promise.resolve(); - } - - let quickNavigateConfiguration = options ? options.quickNavigateConfiguration : undefined; - let inputSelection = options ? options.inputSelection : undefined; - let autoFocus = options ? options.autoFocus : undefined; - - const promiseCompletedOnHide = new Promise(c => { - this.promisesToCompleteOnHide.push(c); - }); - - // Telemetry: log that quick open is shown and log the mode - const registry = Registry.as(Extensions.Quickopen); - const handlerDescriptor = (prefix ? registry.getQuickOpenHandler(prefix) : undefined) || registry.getDefaultQuickOpenHandler(); - - // Trigger onOpen - this.resolveHandler(handlerDescriptor); - - // Create upon first open - if (!this.quickOpenWidget) { - const quickOpenWidget: QuickOpenWidget = this.quickOpenWidget = this._register(new QuickOpenWidget( - this.layoutService.container, - { - onOk: () => this.onOk(), - onCancel: () => { /* ignore */ }, - onType: (value: string) => this.onType(quickOpenWidget, value || ''), - onShow: () => this.handleOnShow(), - onHide: (reason) => this.handleOnHide(reason), - onFocusLost: () => !this.closeOnFocusLost - }, { - inputPlaceHolder: this.hasHandler(HELP_PREFIX) ? nls.localize('quickOpenInput', "Type '?' to get help on the actions you can take from here") : '', - keyboardSupport: false, - treeCreator: (container, config, opts) => this.instantiationService.createInstance(WorkbenchTree, container, config, opts) - })); - this._register(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: quickInputBackground, foreground: quickInputForeground })); - - const quickOpenContainer = this.quickOpenWidget.create(); - addClass(quickOpenContainer, 'show-file-icons'); - this.positionQuickOpenWidget(); - } - - // Layout - this.quickOpenWidget.layout(this.layoutService.dimension); - - // Show quick open with prefix or editor history - if (!this.quickOpenWidget.isVisible() || quickNavigateConfiguration) { - if (prefix) { - this.quickOpenWidget.show(prefix, { quickNavigateConfiguration, inputSelection, autoFocus }); - } else { - const editorHistory = this.getEditorHistoryWithGroupLabel(); - if (editorHistory.getEntries().length < 2) { - quickNavigateConfiguration = undefined; // If no entries can be shown, default to normal quick open mode - } - - // Compute auto focus - if (!autoFocus) { - if (!quickNavigateConfiguration) { - autoFocus = { autoFocusFirstEntry: true }; - } else { - const autoFocusFirstEntry = this.editorGroupService.activeGroup.count === 0; - autoFocus = { autoFocusFirstEntry, autoFocusSecondEntry: !autoFocusFirstEntry }; - } - } - - // Update context - const registry = Registry.as(Extensions.Quickopen); - this.setQuickOpenContextKey(registry.getDefaultQuickOpenHandler().contextKey); - if (this.preserveInput) { - this.quickOpenWidget.show(editorHistory, { value: this.lastSubmittedInputValue, quickNavigateConfiguration, autoFocus, inputSelection }); - } else { - this.quickOpenWidget.show(editorHistory, { quickNavigateConfiguration, autoFocus, inputSelection }); - } - } - } - - // Otherwise reset the widget to the prefix that is passed in - else { - this.quickOpenWidget.show(prefix || '', { inputSelection }); - } - - return promiseCompletedOnHide; - } - - private positionQuickOpenWidget(): void { - if (this.quickOpenWidget) { - this.quickOpenWidget.getElement().style.top = `${this.layoutService.offset?.top ?? 0}px`; - } - } - - private handleOnShow(): void { - this.emitQuickOpenVisibilityChange(true); - } - - private handleOnHide(reason: HideReason): void { - - // Clear state - this.previousActiveHandlerDescriptor = null; - - // Cancel pending results calls - this.cancelPendingGetResultsInvocation(); - - // Pass to handlers - this.mapResolvedHandlersToPrefix.forEach((promise, prefix) => { - promise.then(handler => { - this.handlerOnOpenCalled.delete(prefix); - - handler.onClose(reason === HideReason.CANCELED); // Don't check if onOpen was called to preserve old behaviour for now - }); - }); - - // Complete promises that are waiting - while (this.promisesToCompleteOnHide.length) { - const callback = this.promisesToCompleteOnHide.pop(); - if (callback) { - callback(true); - } - } - - if (reason !== HideReason.FOCUS_LOST) { - this.editorGroupService.activeGroup.focus(); // focus back to editor group unless user clicked somewhere else - } - - // Reset context keys - this.resetQuickOpenContextKeys(); - - // Events - this.emitQuickOpenVisibilityChange(false); - } - - private cancelPendingGetResultsInvocation(): void { - if (this.pendingGetResultsInvocation) { - this.pendingGetResultsInvocation.cancel(); - this.pendingGetResultsInvocation.dispose(); - this.pendingGetResultsInvocation = null; - } - } - - private resetQuickOpenContextKeys(): void { - this.mapContextKeyToContext.forEach(context => context.reset()); - } - - private setQuickOpenContextKey(id?: string): void { - let key: IContextKey | undefined; - if (id) { - key = this.mapContextKeyToContext.get(id); - if (!key) { - key = new RawContextKey(id, false).bindTo(this.contextKeyService); - this.mapContextKeyToContext.set(id, key); - } - } - - if (key?.get()) { - return; // already active context - } - - this.resetQuickOpenContextKeys(); - - if (key) { - key.set(true); - } - } - - private hasHandler(prefix: string): boolean { - return !!Registry.as(Extensions.Quickopen).getQuickOpenHandler(prefix); - } - - private getEditorHistoryWithGroupLabel(): QuickOpenModel { - const entries: QuickOpenEntry[] = this.editorHistoryHandler.getResults(); - - // Apply label to first entry - if (entries.length > 0) { - entries[0] = new EditorHistoryEntryGroup(entries[0], nls.localize('historyMatches', "recently opened"), false); - } - - return new QuickOpenModel(entries, this.actionProvider); - } - - private onOk(): void { - if (this.isQuickOpen) { - this.lastSubmittedInputValue = this.lastInputValue; - } - } - - private onType(quickOpenWidget: QuickOpenWidget, value: string): void { - - // cancel any pending get results invocation and create new - this.cancelPendingGetResultsInvocation(); - const pendingResultsInvocationTokenSource = new CancellationTokenSource(); - const pendingResultsInvocationToken = pendingResultsInvocationTokenSource.token; - this.pendingGetResultsInvocation = pendingResultsInvocationTokenSource; - - // look for a handler - const registry = Registry.as(Extensions.Quickopen); - const handlerDescriptor = registry.getQuickOpenHandler(value); - const defaultHandlerDescriptor = registry.getDefaultQuickOpenHandler(); - const instantProgress = handlerDescriptor?.instantProgress; - const contextKey = handlerDescriptor ? handlerDescriptor.contextKey : defaultHandlerDescriptor.contextKey; - - // Reset Progress - if (!instantProgress) { - quickOpenWidget.getProgressBar().stop().hide(); - } - - // Reset Extra Class - quickOpenWidget.setExtraClass(null); - - // Update context - this.setQuickOpenContextKey(contextKey); - - // Remove leading and trailing whitespace - const trimmedValue = strings.trim(value); - - // If no value provided, default to editor history - if (!trimmedValue) { - - // Trigger onOpen - this.resolveHandler(handlerDescriptor || defaultHandlerDescriptor); - - quickOpenWidget.setInput(this.getEditorHistoryWithGroupLabel(), { autoFocusFirstEntry: true }); - - // If quickOpen entered empty we have to clear the prefill-cache - this.lastInputValue = ''; - this.isQuickOpen = true; - - return; - } - - let resultPromise: Promise; - let resultPromiseDone = false; - - if (handlerDescriptor) { - this.isQuickOpen = false; - resultPromise = this.handleSpecificHandler(quickOpenWidget, handlerDescriptor, value, pendingResultsInvocationToken); - } - - // Otherwise handle default handlers if no specific handler present - else { - this.isQuickOpen = true; - // Cache the value for prefilling the quickOpen next time is opened - this.lastInputValue = trimmedValue; - resultPromise = this.handleDefaultHandler(quickOpenWidget, defaultHandlerDescriptor, value, pendingResultsInvocationToken); - } - - // Remember as the active one - this.previousActiveHandlerDescriptor = handlerDescriptor; - - // Progress if task takes a long time - setTimeout(() => { - if (!resultPromiseDone && !pendingResultsInvocationToken.isCancellationRequested) { - quickOpenWidget.getProgressBar().infinite().show(); - } - }, instantProgress ? 0 : 800); - - // Promise done handling - resultPromise.then(() => { - resultPromiseDone = true; - - if (!pendingResultsInvocationToken.isCancellationRequested) { - quickOpenWidget.getProgressBar().hide(); - } - - pendingResultsInvocationTokenSource.dispose(); - }, (error: any) => { - resultPromiseDone = true; - - pendingResultsInvocationTokenSource.dispose(); - - errors.onUnexpectedError(error); - this.notificationService.error(types.isString(error) ? new Error(error) : error); - }); - } - - private async handleDefaultHandler(quickOpenWidget: QuickOpenWidget, handler: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise { - - // Fill in history results if matching and we are configured to search in history - let matchingHistoryEntries: QuickOpenEntry[]; - if (value && !this.searchInEditorHistory) { - matchingHistoryEntries = []; - } else { - matchingHistoryEntries = this.editorHistoryHandler.getResults(value, token); - } - - if (matchingHistoryEntries.length > 0) { - matchingHistoryEntries[0] = new EditorHistoryEntryGroup(matchingHistoryEntries[0], nls.localize('historyMatches', "recently opened"), false); - } - - // Resolve - const resolvedHandler = await this.resolveHandler(handler); - - const quickOpenModel = new QuickOpenModel(matchingHistoryEntries, this.actionProvider); - - let inputSet = false; - - // If we have matching entries from history we want to show them directly and not wait for the other results to come in - // This also applies when we used to have entries from a previous run and now there are no more history results matching - const previousInput = quickOpenWidget.getInput(); - const wasShowingHistory = previousInput?.entries?.some(e => e instanceof EditorHistoryEntry || e instanceof EditorHistoryEntryGroup); - if (wasShowingHistory || matchingHistoryEntries.length > 0) { - (async () => { - if (resolvedHandler.hasShortResponseTime()) { - await timeout(QuickOpenController.MAX_SHORT_RESPONSE_TIME); - } - - if (!token.isCancellationRequested && !inputSet) { - quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); - inputSet = true; - } - })(); - } - - // Get results - const result = await resolvedHandler.getResults(value, token); - if (!token.isCancellationRequested) { - - // now is the time to show the input if we did not have set it before - if (!inputSet) { - quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); - inputSet = true; - } - - // merge history and default handler results - const handlerResults = result?.entries || []; - this.mergeResults(quickOpenWidget, quickOpenModel, handlerResults, types.withNullAsUndefined(resolvedHandler.getGroupLabel())); - } - } - - private mergeResults(quickOpenWidget: QuickOpenWidget, quickOpenModel: QuickOpenModel, handlerResults: QuickOpenEntry[], groupLabel: string | undefined): void { - - // Remove results already showing by checking for a "resource" property - const mapEntryToResource = this.mapEntriesToResource(quickOpenModel); - const additionalHandlerResults: QuickOpenEntry[] = []; - for (const result of handlerResults) { - const resource = result.getResource(); - - if (!result.mergeWithEditorHistory() || !resource || !mapEntryToResource[resource.toString()]) { - additionalHandlerResults.push(result); - } - } - - // Show additional handler results below any existing results - if (additionalHandlerResults.length > 0) { - const autoFocusFirstEntry = (quickOpenModel.getEntries().length === 0); // the user might have selected another entry meanwhile in local history (see https://github.com/Microsoft/vscode/issues/20828) - const useTopBorder = quickOpenModel.getEntries().length > 0; - additionalHandlerResults[0] = new QuickOpenEntryGroup(additionalHandlerResults[0], groupLabel, useTopBorder); - quickOpenModel.addEntries(additionalHandlerResults); - quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry }); - } - - // Otherwise if no results are present (even from histoy) indicate this to the user - else if (quickOpenModel.getEntries().length === 0) { - quickOpenModel.addEntries([new PlaceholderQuickOpenEntry(nls.localize('noResultsFound1', "No results found"))]); - quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry: true }); - } - } - - private async handleSpecificHandler(quickOpenWidget: QuickOpenWidget, handlerDescriptor: QuickOpenHandlerDescriptor, value: string, token: CancellationToken): Promise { - const resolvedHandler = await this.resolveHandler(handlerDescriptor); - - // Remove handler prefix from search value - value = value.substr(handlerDescriptor.prefix.length); - - // Return early if the handler can not run in the current environment and inform the user - const canRun = resolvedHandler.canRun(); - if (types.isUndefinedOrNull(canRun) || (typeof canRun === 'boolean' && !canRun) || typeof canRun === 'string') { - const placeHolderLabel = (typeof canRun === 'string') ? canRun : nls.localize('canNotRunPlaceholder', "This quick open handler can not be used in the current context"); - - const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(placeHolderLabel)], this.actionProvider); - this.showModel(quickOpenWidget, model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); - - return; - } - - // Support extra class from handler - const extraClass = resolvedHandler.getClass(); - if (extraClass) { - quickOpenWidget.setExtraClass(extraClass); - } - - // When handlers change, clear the result list first before loading the new results - if (this.previousActiveHandlerDescriptor !== handlerDescriptor) { - this.clearModel(quickOpenWidget); - } - - // Receive Results from Handler and apply - const result = await resolvedHandler.getResults(value, token); - if (!token.isCancellationRequested) { - if (!result || !result.entries.length) { - const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(resolvedHandler.getEmptyLabel(value))]); - this.showModel(quickOpenWidget, model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); - } else { - this.showModel(quickOpenWidget, result, resolvedHandler.getAutoFocus(value, { model: result, quickNavigateConfiguration: quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); - } - } - } - - private showModel(quickOpenWidget: QuickOpenWidget, model: IModel, autoFocus?: IAutoFocus, ariaLabel?: string): void { - - // If the given model is already set in the widget, refresh and return early - if (quickOpenWidget.getInput() === model) { - quickOpenWidget.refresh(model, autoFocus); - - return; - } - - // Otherwise just set it - quickOpenWidget.setInput(model, autoFocus, ariaLabel); - } - - private clearModel(quickOpenWidget: QuickOpenWidget): void { - this.showModel(quickOpenWidget, new QuickOpenModel(), undefined); - } - - private mapEntriesToResource(model: QuickOpenModel): { [resource: string]: QuickOpenEntry; } { - const entries = model.getEntries(); - const mapEntryToPath: { [path: string]: QuickOpenEntry; } = {}; - entries.forEach((entry: QuickOpenEntry) => { - const resource = entry.getResource(); - if (resource) { - mapEntryToPath[resource.toString()] = entry; - } - }); - - return mapEntryToPath; - } - - private async resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { - let result = this.doResolveHandler(handler); - - const id = handler.getId(); - if (!this.handlerOnOpenCalled.has(id)) { - const original = result; - this.handlerOnOpenCalled.add(id); - result = original.then(resolved => { - this.mapResolvedHandlersToPrefix.set(id, original); - resolved.onOpen(); - - return resolved; - }); - - this.mapResolvedHandlersToPrefix.set(id, result); - } - - try { - return await result; - } catch (error) { - this.mapResolvedHandlersToPrefix.delete(id); - - throw new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`); - } - } - - private doResolveHandler(handler: QuickOpenHandlerDescriptor): Promise { - const id = handler.getId(); - - // Return Cached - if (this.mapResolvedHandlersToPrefix.has(id)) { - return this.mapResolvedHandlersToPrefix.get(id)!; - } - - // Otherwise load and create - const result = Promise.resolve(handler.instantiate(this.instantiationService)); - this.mapResolvedHandlersToPrefix.set(id, result); - - return result; - } - - layout(dimension: Dimension): void { - if (this.quickOpenWidget) { - this.quickOpenWidget.layout(dimension); - } - } -} - -class PlaceholderQuickOpenEntry extends QuickOpenEntryGroup { - private placeHolderLabel: string; - - constructor(placeHolderLabel: string) { - super(); - - this.placeHolderLabel = placeHolderLabel; - } - - getLabel(): string { - return this.placeHolderLabel; - } -} - -class EditorHistoryHandler { - private scorerCache: ScorerCache; - - constructor( - @IHistoryService private readonly historyService: IHistoryService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IFileService private readonly fileService: IFileService - ) { - this.scorerCache = Object.create(null); - } - - getResults(searchValue?: string, token?: CancellationToken): QuickOpenEntry[] { - - // Massage search for scoring - const query = prepareQuery(searchValue || ''); - - // Just return all if we are not searching - const history = this.historyService.getHistory(); - if (!query.value) { - return history.map(input => this.instantiationService.createInstance(EditorHistoryEntry, input)); - } - - // Otherwise filter by search value and sort by score. Include matches on description - // in case the user is explicitly including path separators. - const accessor = query.containsPathSeparator ? MatchOnDescription : DoNotMatchOnDescription; - return history - - // For now, only support to match on inputs that provide resource information - .filter(input => { - let resource: URI | undefined; - if (input instanceof EditorInput) { - resource = resourceForEditorHistory(input, this.fileService); - } else { - resource = (input as IResourceEditorInput).resource; - } - - return !!resource; - }) - - // Conver to quick open entries - .map(input => this.instantiationService.createInstance(EditorHistoryEntry, input)) - - // Make sure the search value is matching - .filter(e => { - const itemScore = scoreItem(e, query, false, accessor, this.scorerCache); - if (!itemScore.score) { - return false; - } - - e.setHighlights(itemScore.labelMatch || [], itemScore.descriptionMatch); - - return true; - }) - - // Sort by score and provide a fallback sorter that keeps the - // recency of items in case the score for items is the same - .sort((e1, e2) => compareItemsByScore(e1, e2, query, false, accessor, this.scorerCache)); - } -} - -class EditorHistoryItemAccessorClass extends QuickOpenItemAccessorClass { - - constructor(private allowMatchOnDescription: boolean) { - super(); - } - - getItemDescription(entry: QuickOpenEntry): string | undefined { - return this.allowMatchOnDescription ? entry.getDescription() : undefined; - } -} - -const MatchOnDescription = new EditorHistoryItemAccessorClass(true); -const DoNotMatchOnDescription = new EditorHistoryItemAccessorClass(false); - -export class EditorHistoryEntryGroup extends QuickOpenEntryGroup { - // Marker class -} - -export class EditorHistoryEntry extends EditorQuickOpenEntry { - private input: IEditorInput | IResourceEditorInput; - private resource: URI | undefined; - private label: string; - private description?: string; - private icon: string; - - constructor( - input: IEditorInput | IResourceEditorInput, - @IEditorService editorService: IEditorService, - @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService, - @ITextFileService private readonly textFileService: ITextFileService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @ILabelService labelService: ILabelService, - @IFileService fileService: IFileService, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService - ) { - super(editorService); - - this.input = input; - - if (input instanceof EditorInput) { - this.resource = resourceForEditorHistory(input, fileService); - this.label = input.getName(); - this.description = input.getDescription(); - this.icon = this.getDirtyIndicatorForEditor(input); - } else { - const resourceEditorInput = input as IResourceEditorInput; - this.resource = resourceEditorInput.resource; - this.label = resources.basenameOrAuthority(resourceEditorInput.resource); - this.description = labelService.getUriLabel(resources.dirname(this.resource), { relative: true }); - this.icon = this.getDirtyIndicatorForEditor(resourceEditorInput); - } - } - - private getDirtyIndicatorForEditor(input: EditorInput | IResourceEditorInput): string { - let signalDirty = false; - if (input instanceof EditorInput) { - signalDirty = input.isDirty() && !input.isSaving(); - } else { - signalDirty = this.textFileService.isDirty(input.resource) && this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY; - } - - return signalDirty ? 'codicon codicon-circle-filled' : ''; - } - - getIcon(): string { - return this.icon; - } - - getLabel(): string { - return this.label; - } - - getLabelOptions(): IIconLabelValueOptions { - return { - extraClasses: getIconClasses(this.modelService, this.modeService, this.resource) - }; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, recently opened", this.getLabel()); - } - - getDescription(): string | undefined { - return this.description; - } - - getResource(): URI | undefined { - return this.resource; - } - - getInput(): IEditorInput | IResourceEditorInput { - return this.input; - } - - run(mode: Mode, context: IEntryRunContext): boolean { - if (mode === Mode.OPEN) { - const sideBySide = !context.quickNavigateConfiguration && (context.keymods.alt || context.keymods.ctrlCmd); - const pinned = !this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen || context.keymods.alt; - - if (this.input instanceof EditorInput) { - this.editorService.openEditor(this.input, { pinned }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - } else { - this.editorService.openEditor({ resource: (this.input as IResourceEditorInput).resource, options: { pinned } }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - } - - return true; - } - - return super.run(mode, context); - } -} - -function resourceForEditorHistory(input: EditorInput, fileService: IFileService): URI | undefined { - const resource = input ? input.resource : undefined; - - // For the editor history we only prefer resources that are either untitled or - // can be handled by the file service which indicates they are editable resources. - if (resource && (fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { - return resource; - } - - return undefined; -} - -export class RemoveFromEditorHistoryAction extends Action { - - static readonly ID = 'workbench.action.removeFromEditorHistory'; - static readonly LABEL = nls.localize('removeFromEditorHistory', "Remove From History"); - - constructor( - id: string, - label: string, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IHistoryService private readonly historyService: IHistoryService - ) { - super(id, label); - } - - async run(): Promise { - interface IHistoryPickEntry extends IQuickPickItem { - input: IEditorInput | IResourceEditorInput; - } - - const history = this.historyService.getHistory(); - const picks: IHistoryPickEntry[] = history.map(h => { - const entry = this.instantiationService.createInstance(EditorHistoryEntry, h); - - return { - input: h, - iconClasses: getIconClasses(this.modelService, this.modeService, entry.getResource()), - label: entry.getLabel(), - description: entry.getDescription() - }; - }); - - const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickHistory', "Select an editor entry to remove from history"), matchOnDescription: true }); - if (pick) { - this.historyService.remove(pick.input); - } - } -} - -registerSingleton(IQuickOpenService, QuickOpenController, true); diff --git a/src/vs/workbench/browser/parts/quickopen/quickopen.ts b/src/vs/workbench/browser/parts/quickopen/quickopen.ts deleted file mode 100644 index a48cbd02c7..0000000000 --- a/src/vs/workbench/browser/parts/quickopen/quickopen.ts +++ /dev/null @@ -1,205 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ContextKeyExpr, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { Registry } from 'vs/platform/registry/common/platform'; - -const inQuickOpenKey = 'inQuickOpen'; -export const InQuickOpenContextKey = new RawContextKey(inQuickOpenKey, false); -export const inQuickOpenContext = ContextKeyExpr.has(inQuickOpenKey); -export const defaultQuickOpenContextKey = 'inFilesPicker'; -export const defaultQuickOpenContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(defaultQuickOpenContextKey)); - -export const QUICKOPEN_ACTION_ID = 'workbench.action.quickOpen'; -export const QUICKOPEN_ACION_LABEL = nls.localize('quickOpen', "Go to File..."); - -CommandsRegistry.registerCommand({ - id: QUICKOPEN_ACTION_ID, - handler: async function (accessor: ServicesAccessor, prefix: string | null = null) { - const quickOpenService = accessor.get(IQuickOpenService); - - await quickOpenService.show(typeof prefix === 'string' ? prefix : undefined); - }, - description: { - description: `Quick open`, - args: [{ - name: 'prefix', - schema: { - 'type': 'string' - } - }] - } -}); - -export const QUICKOPEN_FOCUS_SECONDARY_ACTION_ID = 'workbench.action.quickOpenPreviousEditor'; -CommandsRegistry.registerCommand(QUICKOPEN_FOCUS_SECONDARY_ACTION_ID, async function (accessor: ServicesAccessor, prefix: string | null = null) { - const quickOpenService = accessor.get(IQuickOpenService); - - await quickOpenService.show(undefined, { autoFocus: { autoFocusSecondEntry: true } }); -}); - -export class BaseQuickOpenNavigateAction extends Action { - - constructor( - id: string, - label: string, - private next: boolean, - private quickNavigate: boolean, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IKeybindingService private readonly keybindingService: IKeybindingService - ) { - super(id, label); - } - - async run(): Promise { - const keys = this.keybindingService.lookupKeybindings(this.id); - const quickNavigate = this.quickNavigate ? { keybindings: keys } : undefined; - - this.quickOpenService.navigate(this.next, quickNavigate); - this.quickInputService.navigate(this.next, quickNavigate); - } -} - -export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHandler { - return accessor => { - const keybindingService = accessor.get(IKeybindingService); - const quickOpenService = accessor.get(IQuickOpenService); - const quickInputService = accessor.get(IQuickInputService); - - const keys = keybindingService.lookupKeybindings(id); - const quickNavigate = { keybindings: keys }; - - quickOpenService.navigate(!!next, quickNavigate); - quickInputService.navigate(!!next, quickNavigate); - }; -} - -export class QuickOpenNavigateNextAction extends BaseQuickOpenNavigateAction { - - static readonly ID = 'workbench.action.quickOpenNavigateNext'; - static readonly LABEL = nls.localize('quickNavigateNext', "Navigate Next in Quick Open"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IQuickInputService quickInputService: IQuickInputService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, true, true, quickOpenService, quickInputService, keybindingService); - } -} - -export class QuickOpenNavigatePreviousAction extends BaseQuickOpenNavigateAction { - - static readonly ID = 'workbench.action.quickOpenNavigatePrevious'; - static readonly LABEL = nls.localize('quickNavigatePrevious', "Navigate Previous in Quick Open"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IQuickInputService quickInputService: IQuickInputService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, false, true, quickOpenService, quickInputService, keybindingService); - } -} - -export class QuickOpenSelectNextAction extends BaseQuickOpenNavigateAction { - - static readonly ID = 'workbench.action.quickOpenSelectNext'; - static readonly LABEL = nls.localize('quickSelectNext', "Select Next in Quick Open"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IQuickInputService quickInputService: IQuickInputService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, true, false, quickOpenService, quickInputService, keybindingService); - } -} - -export class QuickOpenSelectPreviousAction extends BaseQuickOpenNavigateAction { - - static readonly ID = 'workbench.action.quickOpenSelectPrevious'; - static readonly LABEL = nls.localize('quickSelectPrevious', "Select Previous in Quick Open"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IQuickInputService quickInputService: IQuickInputService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, false, false, quickOpenService, quickInputService, keybindingService); - } -} - -// TODO@Ben delete eventually when quick open is implemented using quick input -export class LegacyQuickInputQuickOpenController extends Disposable { - - private readonly inQuickOpenWidgets: Record = Object.create(null); - private readonly inQuickOpenContext = InQuickOpenContextKey.bindTo(this.contextKeyService); - - constructor( - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IQuickInputService private readonly quickInputService: IQuickInputService - ) { - super(); - - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.quickOpenService.onShow(() => this.inQuickOpen('quickOpen', true))); - this._register(this.quickOpenService.onHide(() => this.inQuickOpen('quickOpen', false))); - - this._register(this.quickOpenService.onShow(() => this.quickInputService.hide(true))); - - this._register(this.quickInputService.onShow(() => { - this.quickOpenService.close(); - this.inQuickOpen('quickInput', true); - })); - - this._register(this.quickInputService.onHide(() => { - this.inQuickOpen('quickInput', false); - })); - } - - private inQuickOpen(widget: 'quickInput' | 'quickOpen', open: boolean) { - if (open) { - this.inQuickOpenWidgets[widget] = true; - } else { - delete this.inQuickOpenWidgets[widget]; - } - - if (Object.keys(this.inQuickOpenWidgets).length) { - if (!this.inQuickOpenContext.get()) { - this.inQuickOpenContext.set(true); - } - } else { - if (this.inQuickOpenContext.get()) { - this.inQuickOpenContext.reset(); - } - } - } -} - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LegacyQuickInputQuickOpenController, LifecyclePhase.Ready); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index fe2542a71f..93c51077aa 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -26,7 +26,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { MenuBar } from 'vs/base/browser/ui/menu/menubar'; +import { MenuBar, IMenuBarOptions } from 'vs/base/browser/ui/menu/menubar'; import { SubmenuAction, Direction } from 'vs/base/browser/ui/menu/menu'; import { attachMenuStyler } from 'vs/platform/theme/common/styler'; import { assign } from 'vs/base/common/objects'; @@ -510,30 +510,11 @@ export class CustomMenubarControl extends MenubarControl { } if (firstTime) { - const webActions = []; - const webMenu = this.menuService.createMenu(MenuId.WebMenuActions, this.contextKeyService); - for (const groups of webMenu.getActions()) { - const [, actions] = groups; - for (const action of actions) { - action.label = mnemonicMenuLabel(this.calculateActionLabel(action)); - webActions.push(action); - } - } - - this.menubar = this._register(new MenuBar( - this.container, { - enableMnemonics: this.currentEnableMenuBarMnemonics, - disableAltFocus: this.currentDisableMenuBarAltFocus, - visibility: this.currentMenubarVisibility, - getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), - compactMode: this.currentCompactMenuMode - }, webActions.length > 0 ? webActions : undefined)); + this.menubar = this._register(new MenuBar(this.container, this.getMenuBarOptions())); this.accessibilityService.alwaysUnderlineAccessKeys().then(val => { this.alwaysOnMnemonics = val; - if (this.menubar) { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); - } + this.menubar?.update(this.getMenuBarOptions()); }); this._register(this.menubar.onFocusStateChange(focused => { @@ -559,9 +540,7 @@ export class CustomMenubarControl extends MenubarControl { this._register(attachMenuStyler(this.menubar, this.themeService)); } else { - if (this.menubar) { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); - } + this.menubar?.update(this.getMenuBarOptions()); } // Update the menu actions @@ -632,6 +611,35 @@ export class CustomMenubarControl extends MenubarControl { } } + private getMenuBarOptions(): IMenuBarOptions { + return { + enableMnemonics: this.currentEnableMenuBarMnemonics, + disableAltFocus: this.currentDisableMenuBarAltFocus, + visibility: this.currentMenubarVisibility, + getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), + alwaysOnMnemonics: this.alwaysOnMnemonics, + compactMode: this.currentCompactMenuMode, + getCompactMenuActions: () => { + if (!isWeb) { + return []; // only for web + } + + const webNavigationActions: IAction[] = []; + const webNavigationMenu = this.menuService.createMenu(MenuId.MenubarWebNavigationMenu, this.contextKeyService); + for (const groups of webNavigationMenu.getActions()) { + const [, actions] = groups; + for (const action of actions) { + action.label = mnemonicMenuLabel(this.calculateActionLabel(action)); + webNavigationActions.push(action); + } + } + webNavigationMenu.dispose(); + + return webNavigationActions; + } + }; + } + protected onDidChangeWindowFocus(hasFocus: boolean): void { super.onDidChangeWindowFocus(hasFocus); @@ -694,8 +702,6 @@ export class CustomMenubarControl extends MenubarControl { this.container.style.height = `${dimension.height}px`; } - if (this.menubar) { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); - } + this.menubar?.update(this.getMenuBarOptions()); } } diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index a297eb6fc6..6c63627666 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -3,28 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* File icon themeable OLD tree style */ -.file-icon-themable-tree .monaco-tree-row .content { - display: flex; -} - -.file-icon-themable-tree .monaco-tree-row .content::before { - background-size: 16px; - background-position: 50% 50%; - background-repeat: no-repeat; - padding-right: 6px; - width: 16px; - height: 22px; - display: inline-block; - vertical-align: top; - content: ' '; -} - -.file-icon-themable-tree.align-icons-and-twisties .monaco-tree-row:not(.has-children) .content::before, -.file-icon-themable-tree.hide-arrows .monaco-tree-row .content::before { - display: none; -} - /* File icons in trees */ .file-icon-themable-tree.align-icons-and-twisties .monaco-tl-twistie:not(.force-twistie):not(.collapsible), diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 692205be66..14d27c5fc9 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -13,9 +13,8 @@ import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, a import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { firstIndex } from 'vs/base/common/arrays'; import { IAction } from 'vs/base/common/actions'; -import { IActionViewItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, ActionsOrientation, Separator, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { Registry } from 'vs/platform/registry/common/platform'; -import { prepareActions } from 'vs/workbench/browser/actions'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 1577a64da4..beef2c2bba 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -3,20 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as DOM from 'vs/base/browser/dom'; import { IAction } from 'vs/base/common/actions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ViewPaneContainer, ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService'; -import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; import { Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -145,30 +141,3 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { abstract getTitle(): string; } - -export class FileIconThemableWorkbenchTree extends WorkbenchTree { - - constructor( - container: HTMLElement, - configuration: ITreeConfiguration, - options: ITreeOptions, - @IContextKeyService contextKeyService: IContextKeyService, - @IListService listService: IListService, - @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService instantiationService: IInstantiationService - ) { - super(container, configuration, { ...options, ...{ showTwistie: false, twistiePixels: 12 } }, contextKeyService, listService, themeService, instantiationService, configurationService); - - DOM.addClass(container, 'file-icon-themable-tree'); - DOM.addClass(container, 'show-file-icons'); - - const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => { - DOM.toggleClass(container, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); - DOM.toggleClass(container, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true); - }; - - this.disposables.push(themeService.onDidFileIconThemeChange(onFileIconThemeChange)); - onFileIconThemeChange(themeService.getFileIconTheme()); - } -} diff --git a/src/vs/workbench/browser/quickaccess.ts b/src/vs/workbench/browser/quickaccess.ts new file mode 100644 index 0000000000..f7e962f1fa --- /dev/null +++ b/src/vs/workbench/browser/quickaccess.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandHandler } from 'vs/platform/commands/common/commands'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; + +export const inQuickPickContextKeyValue = 'inQuickOpen'; +export const InQuickPickContextKey = new RawContextKey(inQuickPickContextKeyValue, false); +export const inQuickPickContext = ContextKeyExpr.has(inQuickPickContextKeyValue); + +export const defaultQuickAccessContextKeyValue = 'inFilesPicker'; +export const defaultQuickAccessContext = ContextKeyExpr.and(inQuickPickContext, ContextKeyExpr.has(defaultQuickAccessContextKeyValue)); + +export interface IWorkbenchQuickAccessConfiguration { + workbench: { + commandPalette: { + history: number; + preserveInput: boolean; + }, + quickOpen: { + enableExperimentalNewVersion: boolean; + preserveInput: boolean; + } + }; +} + +export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHandler { + return accessor => { + const keybindingService = accessor.get(IKeybindingService); + const quickInputService = accessor.get(IQuickInputService); + + const keys = keybindingService.lookupKeybindings(id); + const quickNavigate = { keybindings: keys }; + + quickInputService.navigate(!!next, quickNavigate); + }; +} diff --git a/src/vs/workbench/browser/quickopen.ts b/src/vs/workbench/browser/quickopen.ts deleted file mode 100644 index 2d604c76ae..0000000000 --- a/src/vs/workbench/browser/quickopen.ts +++ /dev/null @@ -1,339 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { mixin, assign } from 'vs/base/common/objects'; -import { first } from 'vs/base/common/arrays'; -import { startsWith } from 'vs/base/common/strings'; -import { isString, assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Action } from 'vs/base/common/actions'; -import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { EditorOptions, EditorInput, IEditorInput } from 'vs/workbench/common/editor'; -import { IResourceEditorInput, IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IConstructorSignature0, IInstantiationService, BrandedService } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -export const CLOSE_ON_FOCUS_LOST_CONFIG = 'workbench.quickOpen.closeOnFocusLost'; -export const PRESERVE_INPUT_CONFIG = 'workbench.quickOpen.preserveInput'; -export const ENABLE_EXPERIMENTAL_VERSION_CONFIG = 'workbench.quickOpen.enableExperimentalNewVersion'; -export const SEARCH_EDITOR_HISTORY = 'search.quickOpen.includeHistory'; - -export interface IWorkbenchQuickOpenConfiguration { - workbench: { - commandPalette: { - history: number; - preserveInput: boolean; - }, - quickOpen: { - enableExperimentalNewVersion: boolean; - preserveInput: boolean; - } - }; -} - -export class QuickOpenHandler { - - /** - * A quick open handler returns results for a given input string. The resolved promise - * returns an instance of quick open model. It is up to the handler to keep and reuse an - * instance of the same model across multiple calls. This helps in situations where the user is - * narrowing down a search and the model is just filtering some items out. - * - * As such, returning the same model instance across multiple searches will yield best - * results in terms of performance when many items are shown. - */ - getResults(searchValue: string, token: CancellationToken): Promise | null> { - return Promise.resolve(null); - } - - /** - * The ARIA label to apply when this quick open handler is active in quick open. - */ - getAriaLabel(): string | null { - return null; - } - - /** - * Extra CSS class name to add to the quick open widget to do custom styling of entries. - */ - getClass(): string | null { - return null; - } - - /** - * Indicates if the handler can run in the current environment. Return a string if the handler cannot run but has - * a good message to show in this case. - */ - canRun(): boolean | string { - return true; - } - - /** - * Hints to the outside that this quick open handler typically returns results fast. - */ - hasShortResponseTime(): boolean { - return false; - } - - /** - * Indicates if the handler wishes the quick open widget to automatically select the first result entry or an entry - * based on a specific prefix match. - */ - getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { - return {}; - } - - /** - * Indicates to the handler that the quick open widget has been opened. - */ - onOpen(): void { - return; - } - - /** - * Indicates to the handler that the quick open widget has been closed. Allows to free up any resources as needed. - * The parameter canceled indicates if the quick open widget was closed with an entry being run or not. - */ - onClose(canceled: boolean): void { - return; - } - - /** - * Allows to return a label that will be placed to the side of the results from this handler or null if none. - */ - getGroupLabel(): string | null { - return null; - } - - /** - * Allows to return a label that will be used when there are no results found - */ - getEmptyLabel(searchString: string): string { - if (searchString.length > 0) { - return localize('noResultsMatching', "No results matching"); - } - return localize('noResultsFound2', "No results found"); - } -} - -export interface QuickOpenHandlerHelpEntry { - prefix: string; - description: string; - needsEditor: boolean; -} - -/** - * A lightweight descriptor of a quick open handler. - */ -export class QuickOpenHandlerDescriptor { - prefix: string; - description?: string; - contextKey?: string; - helpEntries?: QuickOpenHandlerHelpEntry[]; - instantProgress: boolean; - - private id: string; - private ctor: IConstructorSignature0; - - public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, description: string, instantProgress?: boolean): QuickOpenHandlerDescriptor; - public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, helpEntries: QuickOpenHandlerHelpEntry[], instantProgress?: boolean): QuickOpenHandlerDescriptor; - public static create(ctor: { new(...services: Services): QuickOpenHandler }, id: string, prefix: string, contextKey: string | undefined, param: string | QuickOpenHandlerHelpEntry[], instantProgress: boolean = false): QuickOpenHandlerDescriptor { - return new QuickOpenHandlerDescriptor(ctor as IConstructorSignature0, id, prefix, contextKey, param, instantProgress); - } - - private constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, param: string | QuickOpenHandlerHelpEntry[], instantProgress: boolean = false) { - this.ctor = ctor; - this.id = id; - this.prefix = prefix; - this.contextKey = contextKey; - this.instantProgress = instantProgress; - - if (isString(param)) { - this.description = param; - } else { - this.helpEntries = param; - } - } - - getId(): string { - return this.id; - } - - instantiate(instantiationService: IInstantiationService): QuickOpenHandler { - return instantiationService.createInstance(this.ctor); - } -} - -export const Extensions = { - Quickopen: 'workbench.contributions.quickopen' -}; - -export interface IQuickOpenRegistry { - - /** - * Registers a quick open handler to the platform. - */ - registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void; - - /** - * Registers a default quick open handler to fallback to. - */ - registerDefaultQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void; - - /** - * Get all registered quick open handlers - */ - getQuickOpenHandlers(): QuickOpenHandlerDescriptor[]; - - /** - * Get a specific quick open handler for a given prefix. - */ - getQuickOpenHandler(prefix: string): QuickOpenHandlerDescriptor | null; - - /** - * Returns the default quick open handler. - */ - getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor; -} - -class QuickOpenRegistry implements IQuickOpenRegistry { - private handlers: QuickOpenHandlerDescriptor[] = []; - private defaultHandler: QuickOpenHandlerDescriptor | undefined; - - registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void { - this.handlers.push(descriptor); - - // sort the handlers by decreasing prefix length, such that longer - // prefixes take priority: 'ext' vs 'ext install' - the latter should win - this.handlers.sort((h1, h2) => h2.prefix.length - h1.prefix.length); - } - - registerDefaultQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void { - this.defaultHandler = descriptor; - } - - getQuickOpenHandlers(): QuickOpenHandlerDescriptor[] { - return this.handlers.slice(0); - } - - getQuickOpenHandler(text: string): QuickOpenHandlerDescriptor | null { - return text ? (first(this.handlers, h => startsWith(text, h.prefix)) || null) : null; - } - - getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor { - return assertIsDefined(this.defaultHandler); - } -} - -Registry.add(Extensions.Quickopen, new QuickOpenRegistry()); - -export interface IEditorQuickOpenEntry { - - /** - * The editor input used for this entry when opening. - */ - getInput(): IResourceEditorInput | IEditorInput | undefined; - - /** - * The editor options used for this entry when opening. - */ - getOptions(): IEditorOptions | undefined; -} - -/** - * A subclass of quick open entry that will open an editor with input and options when running. - */ -export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuickOpenEntry { - - constructor(private _editorService: IEditorService) { - super(); - } - - get editorService() { - return this._editorService; - } - - getInput(): IResourceEditorInput | IEditorInput | undefined { - return undefined; - } - - getOptions(): IEditorOptions | undefined { - return undefined; - } - - run(mode: Mode, context: IEntryRunContext): boolean { - const hideWidget = (mode === Mode.OPEN); - - if (mode === Mode.OPEN || mode === Mode.OPEN_IN_BACKGROUND) { - const sideBySide = context.keymods.ctrlCmd; - - let openOptions: IEditorOptions | undefined; - if (mode === Mode.OPEN_IN_BACKGROUND) { - openOptions = { pinned: true, preserveFocus: true }; - } else if (context.keymods.alt) { - openOptions = { pinned: true }; - } - - const input = this.getInput(); - if (input instanceof EditorInput) { - let opts = this.getOptions(); - if (opts) { - opts = mixin(opts, openOptions, true); - } else if (openOptions) { - opts = EditorOptions.create(openOptions); - } - - this.editorService.openEditor(input, withNullAsUndefined(opts), sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - } else { - const resourceEditorInput = input; - - if (openOptions) { - resourceEditorInput.options = assign(resourceEditorInput.options || Object.create(null), openOptions); - } - - this.editorService.openEditor(resourceEditorInput, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); - } - } - - return hideWidget; - } -} - -/** - * A subclass of quick open entry group that provides access to editor input and options. - */ -export class EditorQuickOpenEntryGroup extends QuickOpenEntryGroup implements IEditorQuickOpenEntry { - - getInput(): IEditorInput | IResourceEditorInput | undefined { - return undefined; - } - - getOptions(): IEditorOptions | undefined { - return undefined; - } -} - -export class QuickOpenAction extends Action { - private prefix: string; - - constructor( - id: string, - label: string, - prefix: string, - @IQuickOpenService protected readonly quickOpenService: IQuickOpenService - ) { - super(id, label); - - this.prefix = prefix; - } - - async run(): Promise { - this.quickOpenService.show(this.prefix); - } -} diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index a998c4340f..3fd5aec7a8 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -55,7 +55,6 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = const listHighlightForegroundColor = theme.getColor(listHighlightForeground); if (listHighlightForegroundColor) { collector.addRule(` - .monaco-workbench .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, .monaco-workbench .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { color: ${listHighlightForegroundColor}; } @@ -115,7 +114,6 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = .monaco-workbench [tabindex="-1"]:focus, .monaco-workbench .synthetic-focus, .monaco-workbench select:focus, - .monaco-workbench .monaco-tree.focused.no-focused-item:focus:before, .monaco-workbench .monaco-list:not(.element-focused):focus:before, .monaco-workbench input[type="button"]:focus, .monaco-workbench input[type="text"]:focus, @@ -143,11 +141,6 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = outline-width: 1px; } - .hc-black .monaco-tree.focused.no-focused-item:focus:before { - outline-width: 1px; - outline-offset: -2px; - } - .hc-black .synthetic-focus input { background: transparent; /* Search input focus fix when in high contrast */ } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 577057aafc..b0fbf83571 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -17,6 +17,16 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio registry.registerConfiguration({ ...workbenchConfigurationNodeBase, 'properties': { + 'workbench.editor.titleScrollbarSizing': { + type: 'string', + enum: ['default', 'large'], + enumDescriptions: [ + nls.localize('workbench.editor.titleScrollbarSizing.default', "The default size."), + nls.localize('workbench.editor.titleScrollbarSizing.large', "Increases the size, so it can be grabed more easily with the mouse") + ], + description: nls.localize('tabScrollbarHeight', "Controls the height of the scrollbars used for tabs and breadcrumbs in the editor title area."), + default: 'default', + }, 'workbench.editor.showTabs': { 'type': 'boolean', 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), @@ -179,11 +189,6 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio 'description': nls.localize('workbench.quickOpen.preserveInput', "Controls whether the last typed input to Quick Open should be restored when opening it the next time."), 'default': false }, - 'workbench.quickOpen.enableExperimentalNewVersion': { - 'type': 'boolean', - 'description': nls.localize('workbench.quickOpen.enableExperimentalNewVersion', "Will use the new quick open implementation for testing purposes."), - 'default': true - }, 'workbench.settings.openDefaultSettings': { 'type': 'boolean', 'description': nls.localize('openDefaultSettings', "Controls whether opening settings also opens an editor showing all default settings."), diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 5987c63524..63da96c9f6 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -16,7 +16,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { isWindows, isLinux, isWeb, isNative, isMacintosh } from 'vs/base/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; -import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { Position, Parts, IWorkbenchLayoutService, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, WillSaveStateReason, StorageScope } from 'vs/platform/storage/common/storage'; @@ -217,7 +216,6 @@ export class Workbench extends Layout { } private startRegistries(accessor: ServicesAccessor): void { - Registry.as(ActionBarExtensions.Actionbar).start(accessor); Registry.as(WorkbenchExtensions.Workbench).start(accessor); Registry.as(EditorExtensions.EditorInputFactories).start(accessor); Registry.as(LanguageExtensions.LanguageAssociations).start(accessor); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 83b2d5e8ca..4863539364 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1302,6 +1302,7 @@ interface IEditorPartConfiguration { highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; tabSizing?: 'fit' | 'shrink'; + titleScrollbarSizing?: 'default' | 'large'; focusRecentEditorAfterClose?: boolean; showIcons?: boolean; enablePreview?: boolean; diff --git a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts index e76fe92eba..ce5aa3ace1 100644 --- a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts +++ b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts @@ -47,7 +47,8 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return this.onBeforeShutdownWithDirty(reason, dirtyWorkingCopies); } - return false; // no veto (no dirty working copies) + // No dirty working copies + return this.onBeforeShutdownWithoutDirty(); } protected async onBeforeShutdownWithDirty(reason: ShutdownReason, workingCopies: IWorkingCopy[]): Promise { @@ -253,4 +254,21 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return Promise.all(backupsToDiscard.map(workingCopy => this.backupFileService.discardBackup(workingCopy.resource))).then(() => false, () => false); } + + private async onBeforeShutdownWithoutDirty(): Promise { + // If we have proceeded enough that editors and dirty state + // has restored, we make sure that no backups lure around + // given we have no known dirty working copy. This helps + // to clean up stale backups as for example reported in + // https://github.com/microsoft/vscode/issues/92962 + if (this.lifecycleService.phase >= LifecyclePhase.Restored) { + try { + await this.backupFileService.discardBackups(); + } catch (error) { + this.logService.error(`[backup tracker] error discarding backups: ${error}`); + } + } + + return false; // no veto (no dirty) + } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index b650135ef4..13537cbe45 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -497,6 +497,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { private _getSemanticTokenAtPosition(semanticTokens: SemanticTokensResult, pos: Position): ISemanticTokenInfo | null { const tokenData = semanticTokens.tokens.data; + const defaultLanguage = this._model.getLanguageIdentifier().language; let lastLine = 0; let lastCharacter = 0; const posLine = pos.lineNumber - 1, posCharacter = pos.column - 1; // to 0-based position @@ -511,7 +512,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const definitions = {}; const colorMap = this._themeService.getColorTheme().tokenColorMap; const theme = this._themeService.getColorTheme() as ColorThemeData; - const tokenStyle = theme.getTokenStyleMetadata(type, modifiers, true, definitions); + const tokenStyle = theme.getTokenStyleMetadata(type, modifiers, defaultLanguage, true, definitions); let metadata: IDecodedMetadata | undefined = undefined; if (tokenStyle) { @@ -539,7 +540,6 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { } const theme = this._themeService.getColorTheme() as ColorThemeData; - const isTokenStylingRule = (d: any): d is TokenStylingRule => !!d.value; if (Array.isArray(definition)) { const scopesDefinition: TextMateThemingRuleDefinitions = {}; theme.resolveScopes(definition, scopesDefinition); @@ -548,7 +548,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return `${escape(scopesDefinition.scope.join(' '))}
${matchingRule.scope}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; } return ''; - } else if (isTokenStylingRule(definition)) { + } else if (TokenStylingRule.is(definition)) { const scope = theme.getTokenStylingRuleScope(definition); if (scope === 'setting') { return `User settings: ${definition.selector.selectorString} - ${this._renderStyleProperty(definition.style, property)}`; @@ -556,16 +556,9 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return `Color theme: ${definition.selector.selectorString} - ${this._renderStyleProperty(definition.style, property)}`; } return ''; - } else if (typeof definition === 'string') { - const [type, ...modifiers] = definition.split('.'); - const definitions: TokenStyleDefinitions = {}; - const m = theme.getTokenStyleMetadata(type, modifiers, true, definitions); - if (m && definitions.foreground) { - return this._renderTokenStyleDefinition(definitions[property], property); - } - return ''; } else { - return this._renderStyleProperty(definition, property); + const style = theme.resolveTokenStyleValue(definition); + return `Default: ${style ? this._renderStyleProperty(style, property) : ''}`; } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts index fdcf917961..d20edecb39 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts @@ -4,15 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { IKeyMods, IQuickInputService } 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 { 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 { IQuickAccessRegistry, Extensions as QuickaccesExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; +import { Action } from 'vs/base/common/actions'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider { @@ -55,9 +59,32 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv } } -Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ +Registry.as(QuickaccesExtensions.Quickaccess).registerQuickAccessProvider({ ctor: GotoLineQuickAccessProvider, prefix: AbstractGotoLineQuickAccessProvider.PREFIX, placeholder: localize('gotoLineQuickAccessPlaceholder', "Type the line number and optional column to go to (e.g. 42:5 for line 42 and column 5)."), helpEntries: [{ description: localize('gotoLineQuickAccess', "Go to Line/Column"), needsEditor: true }] }); + +export class GotoLineAction extends Action { + + static readonly ID = 'workbench.action.gotoLine'; + static readonly LABEL = localize('gotoLine', "Go to Line/Column..."); + + constructor( + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService + ) { + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(GotoLineQuickAccessProvider.PREFIX); + } +} + +Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction(SyncActionDescriptor.create(GotoLineAction, GotoLineAction.ID, GotoLineAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyCode.KEY_G, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G } +}), 'Go to Line/Column...'); diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts index 1b7d510529..f4f3684882 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IKeyMods, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IKeyMods, IQuickPickSeparator, IQuickInputService } 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 { IQuickAccessRegistry, Extensions as QuickaccessExtensions } from 'vs/platform/quickinput/common/quickAccess'; import { AbstractGotoSymbolQuickAccessProvider, IGotoSymbolQuickPickItem } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; @@ -17,6 +17,11 @@ import { ITextModel } from 'vs/editor/common/model'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { Action } from 'vs/base/common/actions'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { prepareQuery } from 'vs/base/common/fuzzyScorer'; export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { @@ -66,7 +71,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess private static readonly SYMBOL_PICKS_TIMEOUT = 8000; - async getSymbolPicks(model: ITextModel, filter: string, disposables: DisposableStore, token: CancellationToken): Promise> { + async getSymbolPicks(model: ITextModel, filter: string, options: { extraContainerLabel?: string }, disposables: DisposableStore, token: CancellationToken): Promise> { // If the registry does not know the model, we wait for as long as // the registry knows it. This helps in cases where a language @@ -81,7 +86,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess return []; } - return this.doGetSymbolPicks(this.getDocumentSymbols(model, true, token), filter, token); + return this.doGetSymbolPicks(this.getDocumentSymbols(model, true, token), prepareQuery(filter), options, token); } addDecorations(editor: IEditor, range: IRange): void { @@ -95,7 +100,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess //#endregion } -Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ +Registry.as(QuickaccessExtensions.Quickaccess).registerQuickAccessProvider({ ctor: GotoSymbolQuickAccessProvider, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, contextKey: 'inFileSymbolsPicker', @@ -105,3 +110,25 @@ Registry.as(Extensions.Quickaccess).registerQuickAccessPro { description: localize('gotoSymbolByCategoryQuickAccess', "Go to Symbol in Editor by Category"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } ] }); + +export class GotoSymbolAction extends Action { + + static readonly ID = 'workbench.action.gotoSymbol'; + static readonly LABEL = localize('gotoSymbol', "Go to Symbol in Editor..."); + + constructor( + id: string, + label: string, + @IQuickInputService private readonly quickInputService: IQuickInputService + ) { + super(id, label); + } + + async run(): Promise { + this.quickInputService.quickAccess.show(GotoSymbolQuickAccessProvider.PREFIX); + } +} + +Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction(SyncActionDescriptor.create(GotoSymbolAction, GotoSymbolAction.ID, GotoSymbolAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O +}), 'Go to Symbol in Editor...'); diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index 3e79bba0f7..a2ea28eb38 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -23,37 +23,12 @@ import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressStep, IProgress, Progress } from 'vs/platform/progress/common/progress'; -import { IResolvedTextFileEditorModel, ITextFileService, ITextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileSaveParticipant, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { SaveReason } from 'vs/workbench/common/editor'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution, Extensions as WorkbenchContributionsExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; - -/* - * An update participant that ensures any un-tracked changes are synced to the JSON file contents for a - * Notebook before save occurs. While every effort is made to ensure model changes are notified and a listener - * updates the backing model in-place, this is a backup mechanism to hard-update the file before save in case - * some are missed. - */ -class NotebookUpdateParticipant implements ITextFileSaveParticipant { // {{SQL CARBON EDIT}} add notebook participant - - constructor( - @INotebookService private notebookService: INotebookService - ) { - // Nothing - } - - public participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { - let uri = model.resource; - let notebookEditor = this.notebookService.findNotebookEditor(uri); - if (notebookEditor) { - notebookEditor.notebookParams.input.updateModel(); - } - return Promise.resolve(); - } -} export class TrimWhitespaceParticipant implements ITextFileSaveParticipant { @@ -64,7 +39,11 @@ export class TrimWhitespaceParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(model: ITextFileEditorModel, env: { reason: SaveReason; }): Promise { + if (!model.textEditorModel) { + return; + } + if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); } @@ -126,7 +105,11 @@ export class FinalNewLineParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(model: IResolvedTextFileEditorModel, _env: { reason: SaveReason; }): Promise { + async participate(model: ITextFileEditorModel, _env: { reason: SaveReason; }): Promise { + if (!model.textEditorModel) { + return; + } + if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doInsertFinalNewLine(model.textEditorModel); } @@ -160,7 +143,11 @@ export class TrimFinalNewLinesParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(model: ITextFileEditorModel, env: { reason: SaveReason; }): Promise { + if (!model.textEditorModel) { + return; + } + if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO); } @@ -230,9 +217,13 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant { // Nothing } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { - const model = editorModel.textEditorModel; - const overrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }; + async participate(model: ITextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { + if (!model.textEditorModel) { + return; + } + + const textEditorModel = model.textEditorModel; + const overrides = { overrideIdentifier: textEditorModel.getLanguageIdentifier().language, resource: textEditorModel.uri }; if (env.reason === SaveReason.AUTO || !this.configurationService.getValue('editor.formatOnSave', overrides)) { return undefined; @@ -247,7 +238,7 @@ class FormatOnSaveParticipant implements ITextFileSaveParticipant { ) }); }); - const editorOrModel = findEditor(model, this.codeEditorService) || model; + const editorOrModel = findEditor(textEditorModel, this.codeEditorService) || textEditorModel; await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, nestedProgress, token); } } @@ -259,13 +250,17 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { @IInstantiationService private readonly instantiationService: IInstantiationService, ) { } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { + async participate(model: ITextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { + if (!model.textEditorModel) { + return; + } + if (env.reason === SaveReason.AUTO) { return undefined; } - const model = editorModel.textEditorModel; + const textEditorModel = model.textEditorModel; - const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.resource }; + const settingsOverrides = { overrideIdentifier: textEditorModel.getLanguageIdentifier().language, resource: model.resource }; const setting = this.configurationService.getValue<{ [kind: string]: boolean } | string[]>('editor.codeActionsOnSave', settingsOverrides); if (!setting) { return undefined; @@ -304,7 +299,7 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { .map(x => new CodeActionKind(x)); progress.report({ message: localize('codeaction', "Quick Fixes") }); - await this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, progress, token); + await this.applyOnSaveActions(textEditorModel, codeActionsOnSave, excludedActions, progress, token); } private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], progress: IProgress, token: CancellationToken): Promise { @@ -368,7 +363,6 @@ export class SaveParticipantsContribution extends Disposable implements IWorkben this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(FormatOnSaveParticipant))); this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(FinalNewLineParticipant))); this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(TrimFinalNewLinesParticipant))); - this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(NotebookUpdateParticipant))); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts b/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts index f855acb98a..f7ac453d2f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/semanticTokensHelp.ts @@ -6,7 +6,7 @@ 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 { ICodeEditor, isCodeEditor } 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'; @@ -14,6 +14,7 @@ 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'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; /** * Shows a message when semantic tokens are shown the first time. @@ -22,11 +23,14 @@ export class SemanticTokensHelp extends Disposable implements IEditorContributio public static readonly ID = 'editor.contrib.semanticHighlightHelp'; + private static notificationShown = false; + constructor( _editor: ICodeEditor, @INotificationService _notificationService: INotificationService, @IOpenerService _openerService: IOpenerService, - @IWorkbenchThemeService _themeService: IWorkbenchThemeService + @IWorkbenchThemeService _themeService: IWorkbenchThemeService, + @IEditorService _editorService: IEditorService ) { super(); @@ -34,11 +38,21 @@ export class SemanticTokensHelp extends Disposable implements IEditorContributio const localToDispose = toDispose.add(new DisposableStore()); const installChangeTokenListener = (model: ITextModel) => { localToDispose.add(model.onDidChangeTokens((e) => { - if (!e.semanticTokensApplied) { + if (SemanticTokensHelp.notificationShown) { + toDispose.dispose(); return; } - toDispose.dispose(); // uninstall all listeners, makes sure the notification is only shown once per window + if (!e.semanticTokensApplied) { + return; + } + const activeEditorControl = _editorService.activeTextEditorControl; + if (!isCodeEditor(activeEditorControl) || activeEditorControl.getModel() !== model) { + return; // only show if model is in the active code editor + } + + toDispose.dispose(); // uninstall all listeners, make sure the notification is only shown once per window + SemanticTokensHelp.notificationShown = true; const message = nls.localize( { @@ -48,7 +62,7 @@ export class SemanticTokensHelp extends Disposable implements IEditorContributio 'Variable 1 will be a theme name.' ] }, - "Semantic highlighting has been applied to '{0}' as the theme '{1}' has semantic highlighting enabled.", + "Code coloring of '{0}' has been updated as the theme '{1}' has [semantic highlighting](https://go.microsoft.com/fwlink/?linkid=2122588) enabled.", path.basename(model.uri.path), _themeService.getColorTheme().label ); diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index f16daca921..53e0186bcd 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -121,7 +121,7 @@ export class CommentsPanel extends ViewPane { public getActions(): IAction[] { if (!this.collapseAllAction) { - this.collapseAllAction = new Action('vs.tree.collapse', nls.localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); + this.collapseAllAction = new Action('vs.tree.collapse', nls.localize('collapseAll', "Collapse All"), 'collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); this._register(this.collapseAllAction); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index cab96e7e70..c5c8f02aab 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -27,6 +27,8 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { public static typeId = 'workbench.editors.webviewEditor'; private readonly _editorResource: URI; + private readonly _fromBackup: boolean; + get resource() { return this._editorResource; } private _modelRef?: IReference; @@ -36,6 +38,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { viewType: string, id: string, webview: Lazy, + fromBackup: boolean, @IWebviewService webviewService: IWebviewService, @IWebviewWorkbenchService webviewWorkbenchService: IWebviewWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -48,6 +51,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { ) { super(id, viewType, '', webview, webviewService, webviewWorkbenchService); this._editorResource = resource; + this._fromBackup = fromBackup; } public getTypeId(): string { @@ -106,7 +110,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { public isDirty(): boolean { if (!this._modelRef) { - return false; + return this._fromBackup; } return this._modelRef.object.isDirty(); } @@ -173,29 +177,31 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { } move(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { - // See if we can keep using the same custom editor provider const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(newResource)) { - return { editor: this.doMove(group, newResource) }; + // We can keep using the same custom editor provider + + if (!this._moveHandler) { + return { + editor: this.customEditorService.createInput(newResource, this.viewType, group), + }; + } + + this._moveHandler(newResource); + const newEditor = this.instantiationService.createInstance(CustomEditorInput, + newResource, + this.viewType, + this.id, + new Lazy(() => undefined!), // this webview is replaced in the transfer call + this._fromBackup, + ); + this.transfer(newEditor); + newEditor.updateGroup(group); + return { editor: newEditor }; + } else { + // const possible = this.customEditorService.getContributedCustomEditors(newResource); + return { editor: this.editorService.createEditorInput({ resource: newResource, forceFile: true }) }; } - - return { editor: this.editorService.createEditorInput({ resource: newResource, forceFile: true }) }; - } - - private doMove(group: GroupIdentifier, newResource: URI): IEditorInput { - if (!this._moveHandler) { - return this.customEditorService.createInput(newResource, this.viewType, group); - } - - this._moveHandler(newResource); - const newEditor = this.instantiationService.createInstance(CustomEditorInput, - newResource, - this.viewType, - this.id, - new Lazy(() => undefined!)); // this webview is replaced in the transfer call - this.transfer(newEditor); - newEditor.updateGroup(group); - return newEditor; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 24e85ddd37..c044311ee1 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -78,7 +78,7 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { return webview; }); - const customInput = this._instantiationService.createInstance(CustomEditorInput, URI.from((data as any).editorResource), data.viewType, id, webview); + const customInput = this._instantiationService.createInstance(CustomEditorInput, URI.from((data as any).editorResource), data.viewType, id, webview, false); if (typeof data.group === 'number') { customInput.updateGroup(data.group); } @@ -90,12 +90,12 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { const webviewService = accessor.get(IWebviewService); const backupFileService = accessor.get(IBackupFileService); - const backup = await backupFileService.resolve(resource); - if (!backup) { + const backup = await backupFileService.resolve(resource); + if (!backup?.meta) { throw new Error(`No backup found for custom editor: ${resource}`); } - const backupData = backup.meta as CustomDocumentBackupData; + const backupData = backup.meta; const id = backupData.webview.id; const webview = new Lazy(() => { @@ -112,7 +112,7 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { return webview; }); - const editor = instantiationService.createInstance(CustomEditorInput, URI.revive(backupData.editorResource), backupData.viewType, id, webview); + const editor = instantiationService.createInstance(CustomEditorInput, URI.revive(backupData.editorResource), backupData.viewType, id, webview, true); editor.updateGroup(0); return editor; }); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 31d4da74fc..0646c5dfc0 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -269,7 +269,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ const webview = new Lazy(() => { return this.webviewService.createWebviewOverlay(id, { customClasses: options?.customClasses }, {}); }); - const input = this.instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview); + const input = this.instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview, false); if (typeof group !== 'undefined') { input.updateGroup(group); } @@ -325,10 +325,15 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ this._webviewHasOwnEditFunctions.set(possibleEditors.length > 0); } - private async handleMovedFileInOpenedFileEditors(_oldResource: URI, newResource: URI): Promise { + private async handleMovedFileInOpenedFileEditors(oldResource: URI, newResource: URI): Promise { + if (extname(oldResource) === extname(newResource)) { + return; + } + // See if the new resource can be opened in a custom editor - const possibleEditors = this.getAllCustomEditors(newResource); - if (!possibleEditors.allEditors.length) { + if (!this.getAllCustomEditors(newResource).allEditors + .some(editor => editor.priority !== CustomEditorPriority.option) + ) { return; } @@ -354,25 +359,19 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return; } - let viewType: string | undefined; - if (possibleEditors.defaultEditor) { - viewType = possibleEditors.defaultEditor.id; - } else { - // If there is, show a single prompt for all editors to see if the user wants to re-open them - // - // TODO: instead of prompting eagerly, it'd likly be better to replace all the editors with - // ones that would prompt when they first become visible - await new Promise(resolve => setTimeout(resolve, 50)); - viewType = await this.showOpenWithPrompt(newResource); - } - - if (!viewType) { + // If there is, show a single prompt for all editors to see if the user wants to re-open them + // + // TODO: instead of prompting eagerly, it'd likly be better to replace all the editors with + // ones that would prompt when they first become visible + await new Promise(resolve => setTimeout(resolve, 50)); + const pickedViewType = await this.showOpenWithPrompt(newResource); + if (!pickedViewType) { return; } for (const [group, entries] of editorsToReplace) { this.editorService.replaceEditors(entries.map(editor => { - const replacement = this.createInput(newResource, viewType!, group); + const replacement = this.createInput(newResource, pickedViewType, group); return { editor, replacement, diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 41beae82a1..041fba5d33 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -663,10 +663,10 @@ class CallStackDelegate implements IListVirtualDelegate { getHeight(element: CallStackItem): number { if (element instanceof StackFrame && element.presentationHint === 'label') { - return 12; + return 16; } if (element instanceof ThreadAndSessionIds || element instanceof Array) { - return 12; + return 16; } return 22; diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index e60a8a5be5..1ba6dab657 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -27,13 +27,11 @@ import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import * as service from 'vs/workbench/contrib/debug/browser/debugService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, REVERSE_CONTINUE_ID, STEP_BACK_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands'; -import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { isMacintosh } from 'vs/base/common/platform'; import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; -import { DebugQuickOpenHandler } from 'vs/workbench/contrib/debug/browser/debugQuickOpen'; import { DebugStatusContribution } from 'vs/workbench/contrib/debug/browser/debugStatus'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; @@ -169,17 +167,6 @@ registerDebugCommandPaletteItem(RunToCursorAction.ID, RunToCursorAction.LABEL, C registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlineBreakpoint', "Inline Breakpoint")); -// Register Quick Open -(Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - DebugQuickOpenHandler, - DebugQuickOpenHandler.ID, - 'debug ', - 'inLaunchConfigurationsPicker', - nls.localize('debugCommands', "Debug Configuration") - ) -); - // Register Quick Access Registry.as(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({ ctor: StartDebugQuickAccessProvider, diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 3a806ec3da..b5acbafb3e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -10,7 +10,6 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; import { Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; @@ -174,13 +173,13 @@ export class SelectAndStartAction extends AbstractDebugAction { constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(id, label, '', debugService, keybindingService); } - run(): Promise { - return this.quickOpenService.show('debug '); + async run(): Promise { + this.quickInputService.quickAccess.show('debug '); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts deleted file mode 100644 index 514650787d..0000000000 --- a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts +++ /dev/null @@ -1,145 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { IDebugService, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { Mode, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { matchesFuzzy } from 'vs/base/common/filters'; - -class AddConfigEntry extends QuickOpenEntry { - - constructor(private label: string, private launch: ILaunch, private commandService: ICommandService, private contextService: IWorkspaceContextService, highlights: IHighlight[] = []) { - super(highlights); - } - - getLabel(): string { - return this.label; - } - - getDescription(): string { - return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : ''; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, debug", this.getLabel()); - } - - run(mode: Mode): boolean { - if (mode === Mode.PREVIEW) { - return false; - } - this.commandService.executeCommand('debug.addConfiguration', this.launch.uri.toString()); - - return true; - } -} - -class StartDebugEntry extends QuickOpenEntry { - - constructor(private debugService: IDebugService, private contextService: IWorkspaceContextService, private notificationService: INotificationService, private launch: ILaunch, private configurationName: string, highlights: IHighlight[] = []) { - super(highlights); - } - - getLabel(): string { - return this.configurationName; - } - - getDescription(): string { - return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : ''; - } - - getAriaLabel(): string { - return nls.localize('entryAriaLabel', "{0}, debug", this.getLabel()); - } - - run(mode: Mode): boolean { - if (mode === Mode.PREVIEW || !StartAction.isEnabled(this.debugService)) { - return false; - } - // Run selected debug configuration - this.debugService.getConfigurationManager().selectConfiguration(this.launch, this.configurationName); - this.debugService.startDebugging(this.launch).then(undefined, e => this.notificationService.error(e)); - - return true; - } -} - -export class DebugQuickOpenHandler extends QuickOpenHandler { - - static readonly ID = 'workbench.picker.launch'; - - private autoFocusIndex: number | undefined; - - constructor( - @IDebugService private readonly debugService: IDebugService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @ICommandService private readonly commandService: ICommandService, - @INotificationService private readonly notificationService: INotificationService - ) { - super(); - } - - getAriaLabel(): string { - return nls.localize('debugAriaLabel', "Type a name of a launch configuration to run."); - } - - getResults(input: string, token: CancellationToken): Promise { - const configurations: QuickOpenEntry[] = []; - - const configManager = this.debugService.getConfigurationManager(); - const allConfigurations = configManager.getAllConfigurations(); - let lastGroup: string | undefined; - for (let config of allConfigurations) { - const highlights = matchesFuzzy(input, config.name, true); - if (highlights) { - if (config.launch === configManager.selectedConfiguration.launch && config.name === configManager.selectedConfiguration.name) { - this.autoFocusIndex = configurations.length; - } - let entry: QuickOpenEntry = new StartDebugEntry(this.debugService, this.contextService, this.notificationService, config.launch, config.name, highlights); - if (lastGroup !== config.presentation?.group) { - entry = new QuickOpenEntryGroup(entry, undefined, true); - lastGroup = config.presentation?.group; - } - configurations.push(entry); - } - } - - configManager.getLaunches().filter(l => !l.hidden).forEach((l, index) => { - - const label = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration..."); - const entry = new AddConfigEntry(label, l, this.commandService, this.contextService, matchesFuzzy(input, label, true) || undefined); - if (index === 0) { - configurations.push(new QuickOpenEntryGroup(entry, undefined, true)); - } else { - configurations.push(entry); - } - - }); - - return Promise.resolve(new QuickOpenModel(configurations)); - } - - getAutoFocus(input: string): IAutoFocus { - return { - autoFocusFirstEntry: !!input, - autoFocusIndex: this.autoFocusIndex - }; - } - - getEmptyLabel(searchString: string): string { - if (searchString.length > 0) { - return nls.localize('noConfigurationsMatching', "No debug configurations matching"); - } - - return nls.localize('noConfigurationsFound', "No debug configurations found. Please create a 'launch.json' file."); - } -} diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 7d38585e70..5471c5453f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -120,6 +120,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this._register(this.debugService.getViewModel().onDidFocusSession(() => this.updateScheduler.schedule())); this._register(this.debugService.onDidNewSession(() => this.updateScheduler.schedule())); this._register(this.configurationService.onDidChangeConfiguration(e => this.onDidConfigurationChange(e))); + this._register(this.debugToolBarMenu.onDidChange(() => this.updateScheduler.schedule())); this._register(this.actionBar.actionRunner.onDidRun((e: IRunEvent) => { // check for error if (e.error && !errors.isPromiseCanceledError(e.error)) { diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 41044f466b..c4334aabd5 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -670,7 +670,7 @@ export class RawDebugSession implements IDisposable { }); } if (error && error.format && error.showUser) { - this.notificationService.error(error.format); + this.notificationService.error(userMessage); } return new Error(userMessage); diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index f38a14baaa..41bc442c79 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -76,6 +76,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show private static readonly REPL_INPUT_LINE_HEIGHT = 19; + private static readonly URI = uri.parse(`${DEBUG_SCHEME}:replinput`); private history: HistoryNavigator; private tree!: WorkbenchAsyncDataTree; @@ -210,7 +211,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { if (!visible) { dispose(this.model); } else { - this.model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:replinput`), true); + this.model = this.modelService.getModel(Repl.URI) || this.modelService.createModel('', null, Repl.URI, true); this.setMode(); this.replInput.setModel(this.model); this.updateInputDecoration(); diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 3b26f2cd9e..1af4757ab8 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -164,7 +164,7 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments case ShellType.bash: quote = (s: string) => { - s = s.replace(/([\"\\])/g, '\\$1'); + s = s.replace(/(["';\\])/g, '\\$1'); return (s.indexOf(' ') >= 0 || s.length === 0) ? `"${s}"` : s; }; diff --git a/src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts b/src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts index e6ac45a34a..9c1515689c 100644 --- a/src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts @@ -17,6 +17,7 @@ suite('Debug - Utils', () => { assert.strictEqual(formatPII('Foo {0} Bar {1}{2}', false, { '0': 'yes', '1': 'undefined' }), 'Foo yes Bar undefined{2}'); assert.strictEqual(formatPII('Foo {_key0} Bar {key1}{key2}', true, { '_key0': 'yes', 'key1': '5', 'key2': 'false' }), 'Foo yes Bar {key1}{key2}'); assert.strictEqual(formatPII('Foo {_key0} Bar {key1}{key2}', false, { '_key0': 'yes', 'key1': '5', 'key2': 'false' }), 'Foo yes Bar 5false'); + assert.strictEqual(formatPII('Unable to display threads:"{e}"', false, { 'e': 'detached from process' }), 'Unable to display threads:"detached from process"'); }); test('getExactExpressionStartAndEnd', () => { diff --git a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts index 4eb3eb766e..27efc13cc3 100644 --- a/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts +++ b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts @@ -6,10 +6,10 @@ import * as nls from 'vs/nls'; import { registerEditorAction, EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; // import { MenuId } from 'vs/platform/actions/common/actions'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; const EMMET_COMMANDS_PREFIX = '>Emmet: '; @@ -30,10 +30,9 @@ class ShowEmmetCommandsAction extends EditorAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { - const quickOpenService = accessor.get(IQuickOpenService); - quickOpenService.show(EMMET_COMMANDS_PREFIX); - return Promise.resolve(undefined); + public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.quickAccess.show(EMMET_COMMANDS_PREFIX); } } diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index efd5e9e359..a7af651dec 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { ExperimentActionType, ExperimentState, IExperiment, ExperimentService, getCurrentActivationRecord, currentSchemaVersion } from 'vs/workbench/contrib/experiments/common/experimentService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { TestLifecycleService, TestExtensionService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IExtensionManagementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -30,6 +30,7 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWillActivateEvent, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { timeout } from 'vs/base/common/async'; +import { TestExtensionService } from 'vs/workbench/test/common/workbenchTestServices'; interface ExperimentSettings { enabled?: boolean; diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index fea76be9c8..0fc68b6f94 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -24,7 +24,6 @@ import { import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; -import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; @@ -32,7 +31,6 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeymapExtensions } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { GalleryExtensionsHandler, ExtensionsHandler } from 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -60,18 +58,6 @@ registerSingleton(IExtensionTipsService, ExtensionTipsService); Registry.as(OutputExtensions.OutputChannels) .registerChannel({ id: ExtensionsChannelId, label: ExtensionsLabel, log: false }); -// Quickopen -Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - ExtensionsHandler, - ExtensionsHandler.ID, - 'ext ', - undefined, - localize('extensionsCommands', "Manage Extensions"), - true - ) -); - // Quick Access Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ ctor: ManageExtensionsQuickAccessProvider, @@ -494,17 +480,6 @@ class ExtensionsContributions implements IWorkbenchContribution { const canManageExtensions = extensionManagementServerService.localExtensionManagementServer || extensionManagementServerService.remoteExtensionManagementServer; if (canManageExtensions) { - Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpenHandler( - QuickOpenHandlerDescriptor.create( - GalleryExtensionsHandler, - GalleryExtensionsHandler.ID, - 'ext install ', - undefined, - localize('galleryExtensionsCommands', "Install Gallery Extensions"), - true - ) - ); - Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ ctor: InstallExtensionQuickAccessProvider, prefix: InstallExtensionQuickAccessProvider.PREFIX, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 07da944bb1..41a27261a9 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -682,6 +682,7 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, instantiationService: IInstantiationService, extension: IExtension | undefined | null): ExtensionAction[][] { const scopedContextKeyService = contextKeyService.createScoped(); if (extension) { + scopedContextKeyService.createKey('extension', extension.identifier.id); scopedContextKeyService.createKey('isBuiltinExtension', extension.type === ExtensionType.System); scopedContextKeyService.createKey('extensionHasConfiguration', extension.local && !!extension.local.manifest.contributes && !!extension.local.manifest.contributes.configuration); if (extension.state === ExtensionState.Installed) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 905906dd66..fc93f1e3f6 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { append, $, addClass, removeClass, toggleClass } from 'vs/base/browser/dom'; import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; @@ -19,7 +20,9 @@ import { Label, RatingsWidget, /*InstallCountWidget,*/ RecommendationWidget, Rem import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { isLanguagePackExtension, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface IExtensionsViewState { onFocus: Event; @@ -35,6 +38,7 @@ export interface ITemplateData { //installCount: HTMLElement; //ratings: HTMLElement; author: HTMLElement; + syncIgnored: HTMLElement; description: HTMLElement; extension: IExtension | null; disposables: IDisposable[]; @@ -57,7 +61,8 @@ export class Renderer implements IPagedRenderer { @INotificationService private readonly notificationService: INotificationService, @IExtensionService private readonly extensionService: IExtensionService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { } get templateId() { return 'extension'; } @@ -79,6 +84,9 @@ export class Renderer implements IPagedRenderer { const description = append(details, $('.description.ellipsis')); const footer = append(details, $('.footer')); const author = append(footer, $('.author.ellipsis')); + const syncIgnored = append(footer, $('.sync-ignored.ellipsis')); + const syncIgnoredLabel = new CodiconLabel(syncIgnored); + syncIgnoredLabel.text = '$(eye-closed) ' + localize('extensionSyncIgnoredLabel', 'Sync: Ignored'); const actionbar = new ActionBar(footer, { animated: false, actionViewItemProvider: (action: IAction) => { @@ -121,8 +129,7 @@ export class Renderer implements IPagedRenderer { const disposables = combinedDisposable(...actions, ...widgets, actionbar, extensionContainers, extensionTooltipAction); return { - // {{SQL CARBON EDIT}} - root, element, icon, name, /*installCount, ratings,*/ author, description, disposables: [disposables], actionbar, + root, element, icon, name, /*installCount,*/ syncIgnored, /*ratings,*/ author, description, disposables: [disposables], actionbar, extensionDisposables: [], set extension(extension: IExtension) { extensionContainers.extension = extension; @@ -202,9 +209,25 @@ export class Renderer implements IPagedRenderer { data.actionbar.viewItems.forEach(item => (item).setFocus(false)); } }, this, data.extensionDisposables); + + + this.updateExtensionIgnored(extension, data); + this.configurationService.onDidChangeConfiguration(e => { + if (e.affectedKeys.includes('sync.ignoredExtensions')) { + this.updateExtensionIgnored(extension, data); + } + }, data.extensionDisposables); } disposeTemplate(data: ITemplateData): void { data.disposables = dispose(data.disposables); } + + private updateExtensionIgnored(extension: IExtension, data: ITemplateData): void { + data.syncIgnored.style.display = this.extensionIsIgnored(extension.identifier) ? 'block' : 'none'; + } + + private extensionIsIgnored(identifier: IExtensionIdentifier): boolean { + return this.configurationService.getValue('sync.ignoredExtensions').some(id => areSameExtensions({ id }, identifier)); + } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts deleted file mode 100644 index ddee94ab5d..0000000000 --- a/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts +++ /dev/null @@ -1,139 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { IAutoFocus, Mode, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; -import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { IExtensionsViewPaneContainer, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CancellationToken } from 'vs/base/common/cancellation'; - -class SimpleEntry extends QuickOpenEntry { - - constructor(private label: string, private action: Function) { - super(); - } - - getLabel(): string { - return this.label; - } - - getAriaLabel(): string { - return this.label; - } - - run(mode: Mode): boolean { - if (mode === Mode.PREVIEW) { - return false; - } - - this.action(); - - return true; - } -} - -export class ExtensionsHandler extends QuickOpenHandler { - - public static readonly ID = 'workbench.picker.extensions'; - - constructor(@IViewletService private readonly viewletService: IViewletService) { - super(); - } - - getResults(text: string, token: CancellationToken): Promise> { - const label = nls.localize('manage', "Press Enter to manage your extensions."); - const action = () => { - this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search(''); - viewlet.focus(); - }); - }; - - return Promise.resolve(new QuickOpenModel([new SimpleEntry(label, action)])); - } - - getEmptyLabel(input: string): string { - return ''; - } - - getAutoFocus(searchValue: string): IAutoFocus { - return { autoFocusFirstEntry: true }; - } -} - -export class GalleryExtensionsHandler extends QuickOpenHandler { - - public static readonly ID = 'workbench.picker.gallery'; - - constructor( - @IViewletService private readonly viewletService: IViewletService, - @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @IExtensionManagementService private readonly extensionsService: IExtensionManagementService, - @INotificationService private readonly notificationService: INotificationService - ) { - super(); - } - - getResults(text: string, token: CancellationToken): Promise> { - if (/\./.test(text)) { - return this.galleryService.query({ names: [text], pageSize: 1 }, token) - .then(galleryResult => { - const entries: SimpleEntry[] = []; - const galleryExtension = galleryResult.firstPage[0]; - - if (!galleryExtension) { - const label = nls.localize('notfound', "Extension '{0}' not found in the Marketplace.", text); - entries.push(new SimpleEntry(label, () => null)); - - } else { - const label = nls.localize('install', "Press Enter to install '{0}' from the Marketplace.", text); - const action = () => { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => viewlet.search(`@id:${text}`)) - .then(() => this.extensionsService.installFromGallery(galleryExtension)) - .then(undefined, err => this.notificationService.error(err)); - }; - - entries.push(new SimpleEntry(label, action)); - } - - return new QuickOpenModel(entries); - }); - } - - const entries: SimpleEntry[] = []; - - if (text) { - const label = nls.localize('searchFor', "Press Enter to search for '{0}' in the Marketplace.", text); - const action = () => { - this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer) - .then(viewlet => { - viewlet.search(text); - viewlet.focus(); - }); - }; - - entries.push(new SimpleEntry(label, action)); - } - - return Promise.resolve(new QuickOpenModel(entries)); - } - - getEmptyLabel(input: string): string { - return nls.localize('noExtensionsToInstall', "Type an extension name"); - } - - getAutoFocus(searchValue: string): IAutoFocus { - return { autoFocusFirstEntry: true }; - } -} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index b18bc0acdf..2edfa62d41 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -10,7 +10,7 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Event as EventOf, Emitter } from 'vs/base/common/event'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -28,7 +28,6 @@ import { IExtensionManagementService } from 'vs/platform/extensionManagement/com import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; -import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; @@ -58,6 +57,7 @@ import { RemoteNameContext } from 'vs/workbench/browser/contextkeys'; import { ILabelService } from 'vs/platform/label/common/label'; import { MementoObject } from 'vs/workbench/common/memento'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; const NonEmptyWorkspaceContext = new RawContextKey('nonEmptyWorkspace', false); const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); @@ -360,7 +360,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IPreferencesService private readonly preferencesService: IPreferencesService ) { super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); @@ -611,7 +612,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE if (/ECONNREFUSED/.test(message)) { const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), { actions: [ - this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL) + new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openGlobalSettings()) ] }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 424a2d4676..67413de721 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -23,7 +23,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; @@ -40,7 +39,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Action } from 'vs/base/common/actions'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; @@ -51,6 +50,7 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; // Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result']; @@ -113,6 +113,7 @@ export class ExtensionsListView extends ViewPane { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IMenuService private readonly menuService: IMenuService, @IOpenerService openerService: IOpenerService, + @IPreferencesService private readonly preferencesService: IPreferencesService, ) { super({ ...(options as IViewPaneOptions), showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.server = options.server; @@ -851,7 +852,7 @@ export class ExtensionsListView extends ViewPane { if (/ECONNREFUSED/.test(message)) { const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), { actions: [ - this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL) + new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openGlobalSettings()) ] }); @@ -972,9 +973,10 @@ export class ServerExtensionsView extends ExtensionsListView { @IMenuService menuService: IMenuService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @IPreferencesService preferencesService: IPreferencesService, ) { options.server = server; - super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService, viewDescriptorService, menuService, openerService); + super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService, viewDescriptorService, menuService, openerService, preferencesService); this._register(onDidChangeTitle(title => this.updateTitle(title))); } diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index 4abdb19dbf..5f7466e43f 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -254,6 +254,15 @@ font-weight: 600; } +.extensions-viewlet > .extensions .extension > .details > .footer > .sync-ignored { + font-size: 11px; +} + +.extensions-viewlet > .extensions .extension > .details > .footer > .sync-ignored > .codicon { + font-size: 14px; + vertical-align: text-top; +} + .extensions-viewlet > .extensions .selected .extension > .details > .footer > .author, .extensions-viewlet > .extensions .selected.focused .extension > .details > .footer > .author { opacity: 1; diff --git a/src/vs/workbench/contrib/externalTerminal/node/TerminalHelper.scpt b/src/vs/workbench/contrib/externalTerminal/node/TerminalHelper.scpt index 1130091f6000e2e47167d1ac3054fbbdad827578..145345e002542006dddfcc4871306c35a7679093 100644 GIT binary patch delta 6270 zcmZ`-2Ygk<)17lu2=``FgiumQ0Ko(VLYEGPCZHe!R!R~=k&r<02%>1Hc13)mf+#39 zPz1z^0*Z>Ls93OrsMrf4NO$Hx`!*2u^W(>E_T4)>Gw1BtxqI((SKd5*?LD#Bw0>#q zA`|6NR=7>=R`@VnP^oXSAv zP+~3x(@V=HO)MEddhEn9>*NpTWE;IjM-%9%b#g43j!`squ`YghE;NkH4rF$9cu%e) zf?P}F(NEfr(+Q(pRI!eC(oUHrm2wSamNlx>PJda`6WabFHrhpBqF#5>ZzLeOT4ov& z^14T^eFnfYfD68pK9Z{fxr*X*DXyrbVsu4*S-Iv8&*lCReTt|AL}~7bPDZr7rLhzq z?V_I$gII%@O2_Li`VKV^XHeth@4cuA-=L<8wTt%1m4RGY4b;GQVfN5B2oez3F!9NV zSDuTggenZ;+}yqNt;`5y#{bDPR_`kZ_Mco4$Q9MjJ-ONvG}BVUNEWEUC zW>IYi>32GSlmw($NOh7?$1-=CWA3n$Ul;WZ>N=th(+Si^szH6{?=T&csew$b#$u4_ zZXKiJG9{2HHd19hcg;dC4WzW%+>?+6qzmaGNn0Ay4bmdeb;)9+L?%lyq{JGj%pt|r zGF@8+8W?1dH(0O6;CW;s%OI05Nq+};4h=mtRI^5CY%sEs^B5qINpiVNgjh4V+;$VK z$Hv-DM-zk7Bf!-|12n}M22IH;A?WW^WFyBQo1)IqS@B4f31}wc1DO!sd{$1Xhi2-c zInFd_9s#Q+$ucgGap5&tS;KLr-fba81})rOxj=mPjvLqQ3IWYP6lV$lz<%M$Ytn^E|ANJ;?bE>C^4(7Xj*wuR;Z|K z^2CySTQMjBU91SZCZj7gpe!oWy)})>wa=mu6@@3HCRBD(-`&vNpxa5`ZO}u*_A=<< z)-FduosB$$vt6<|x=xq+aw)|H1*N4Gp|WYZ40(24BmdAFeGGaNS}Rd4&{{6>JbH$c(lToHvf1`cM&EFUv}WnuHM4$lkwHJJ5f5$A zQ!Ye*oa4)d;hAX}J^DLeZP7tS1Tvy}7sTQmijv_NAj5nauCXVLo2an|IQvfMDi;KD zfn9>0u`hP;;=& zdq^^dgfB`zv-`O^=X?w`IN$M?r}h_Mn85|sM-1{X0K?^c8DcQpA=4`|7$f97Uj|!b zokqA!^Uzn$4dmQvWS*R7<#)dAhS+q6MJz4xGANKiR>FfYR5Q2`%G`xEqNmvomVq)r z&VdZHGY8mCx1EQJv|Wr#3@&!~tYv>(ipwDVExt>`*JQM*d6~sGG8rSo&u0uPxJ2iS z!f1n04&Pw49)o;?F%F+1L4ov>z6J#mRcQPXD8yKULZ^zMinWg~eXJ^UpBjvHcn4#s z^bVxAU6~R{KbvM>i!#3&WnLh8|GmjES`#b6ID;aW*hoymcuX)D?~2Yy{WTGn8%%UX zXB5WE*_b4~0y#T;TZh^sJgs&filwK4ZZkb0D9%FZ5lD~!jwn5?Y@|8OY9%cVu5p|$!0n1}w!tiy@B-W>EdpslNx2js zK{kDKXksY5wr#XhJlj!x8y3o$zMQF4O)f7PZ&2xq!XiA0IUeR{+_{)%FxN4^2=`$= zt~HqNrZ2*s(%hHk|0s~xMpngcR9uJa4X$&mvKaSDGhdo%sFcvyLS4S=U7~klvE&4j zV|Vmq%+ruJ;6{TRT*zg36gS~!gPUB)WmqoRzGPR09G73LhnAbGL*9a04Q_EESKtvj z!18E$-zilmjn%!BQ-(9%d;4YWn zYOIq+zBID=O$=$>zT3@Sh1Jq9kcOIH+R)RjvRYXww6vpKtB2Ow7K;$GlT=8$ot|d9 zR2yxnwtI1(!M&tU2f_xth-C)LoYxI_LFz|IeQSB2w&haKV7c?T5gVnh)R7cOUF%Ly zh>#SUS3NBa9_6V0{dmCO{)oNRjMSD|avG$zHLYd4)2!zQw5`B{1}n(Za~Jsv>mfX> zU1TpuW+v-$Ck2vRb*y?sr#y<429HKqjhG|`k{F(yol@yxrQTj8dR|^tecJ?L?D0D~ z-yW)>^CKs!1efmv#Hh4U8mek9m@dVZwJV8R2RUjo&M zD`?NTl*koiZdQkQjB4p>8+2s~OZlq63Lm`s!A`;2<`l;n*Zm*le)LL2O4u z@REm@H0;aRV(_v<*p52k6})QjibL3rI^s2KHF(V-Y)>7<^To3}A@ZEu>VmeT_M%5` zP`zC83#XNp6_tcS`32$gnk6K@S`C*1q418DDV19^?&~ZDuRFM%HJxpEgPH#_*hZc{ zW2rOs!<%@^;7u#K82#N1+xY|^H`wk7u~**4I|gsN5S^(D9}D=HEv2!c2}M>M_OHsr z;2mevg}U<5%7BmlOF!TfDq=ov_4Aes*pvF;T|UBx4c@i2+ar5r2i`N-;ppi}d3YZm z7`*T3=|y>b$mc^!PrX7CZ4 z%VC?#8}_XoUR71}tdhTYAvb~nXYhw)an`eL-sxu`)t z|E~YlMWH^f4AwXtItd>q;$v5}R_g+P!ut$9aruwYz4)oYr_Oc^<@0Yo|E50d+l9yb zG#Vb%_8C4m`0P}b;a~X|{u%tM#rTWuezumMYx@Gb48CwxCV`T%8($jiuBtKt5&wie z{A0jB*@KkUkv%%+EB?XYD+gH>#@`40{l9eJ@edk-f3)0xd9sf5$JZXdRtMi;ufaDb z>qsAbi|-7+Jy}P3<9q%N8sd9*r4IGLkNhouGNbXM^Oa4V@w11Y)z>fh)!-Mm@3N@{ zeuL8Wn_IVRYL4Hr-#|ai=*dD0aC6@4^WMn1SvBu>AhIckzX|voyXJ}es)2nS@Yj)z z_NxZ^gTFG+CAS}9T59|QILLcg**f5K-jZ75Pu|VS=bvt4*(-;jxnVpc6wo>VP8#N8CiM9sF6qpQ+3mCG#F@ z{H0xjuT*^eZA8ub<;lLw)u(hE#W4f@er461O6mNm&!3)B^D*bKKBe*}0e@nJ8x`<( zRRaE@TEHLs{P8IPe^)KwRd%-8{n6a#?TW>oEq#8gYLuWk)=1*lRC}-TD_)Ej%UgVY&CafTmE!E< zw`_cc&G8kRMT`z!=9dC~`EU0Eeu-joNndc(B37%35kFEa9j5DPGS{@;YA2YxoJSvzj00$9NU5^m%ht zx&d#}V_XV0hNrf#jTgh)+h*2!!K(3Z9}|7v5YFtJ(L$fHeqm|B<$*@G>E-K5MU#m3 z%R^;T0}X!a`)j~YR}uA;eF<#n^LjP2Rm_Lbh}wfef8W_(^_@96_5 z;3t&eI#_MrnOgY#xL&u11D_wW7J9(;c~xXoKE>yicBDtlWZr_!;a@v!imNl4YUfi* zlkl33$5T`QN&F}t=12HpuZ9=L4+Z?Frq?`G4^h$lpjR`BwTdQqHB~XZf*%NYg*`?! z5BPz9>`VOu#ShyhsX?)PKQHHHd>`M-O9Q^&+NT7({9pC~FRR*4_u2D?3g=$knrZrX zX?RDcl+JM!;A6grm+)e~oA2T~c@f{iVZPnxdlZy?RQS9^N0Ukmm8Qk^nl6CPciRQ& zc9;HkwA`s9dv@@7QDhXF;qx6jvJX_B!**0!svd6FkzIylekeSn^Z1l#%gsW3$+z(W z&-VhpHQrUpv^v z4i*%9$NJf@V?pc%6%++UocU+oMdHWjpX4Tcce7KTotd56pb|hz0dLc#gs;Z|~R8E~-R#8pl=oc?z1HDMQVzkR-{9czy zzf&^xGZEXp2$dpdMRFFU7E)5_%rc@t&Lx5@)e(KK*B`XoDM^~4!JplGE!Qb!h0-d`zDrErv8x6J{b*oXe?_<~pSK$y?|%SrEyB|FJW<9>n>P z%&(Un$e9Ms83xTB3J{@@Lv)fyaVu?=(`25^g`8$w^Nh?@Ry5MfA?Dybid)t7bY#dJ zsdY&AC~l?g$V6j@Os{+^{U9}w)WrKX%AbP9-lZRC8=Az>#9V5ciKYf>hDUA(?LjkS zIW+S~?V#V$94#E0dwDx(r&LE$UGI!T3;${-?ULD%%r;eZ$f|dKRwT3P`2(3_9@Uyh zF}4Yq*7+;KVzyiqH7(XrCvG6b;zOMXnhJih&<#wICS!_ z9U4n{B<1mAS~YJ!MJAr6I!0MTb{(H%V;x_h&7kS~)XnQY1q zcj%Fb5agqcObTTZC5M%BX3TIn*u&f&Md%rzrz+}&-VVLYB%nRo%gHzteIO?jHNv6s z$t|do_L#4s#gfjcAV!a#AQK zQNyUDs%ma+^_+5t!)$dDKNyT54ueT;BVC9ta$+bas=>7-HEQq>?_LLVl!=i{qhC}3p zNKUAaRH){Wp1&s!m2r`bGhhd$$Z@vf6O5c<%YKN*bD*x! z;Q>tV5g6rggvVwe2FkHG5=TLfH9U`uPs+_tJId@Hor%%$CApmkjM6zrV~oSm9=(Ao z;TVi{IL2f&z(DokSQ#tFI2`Le(6s=@jB-uoa}ItcW@NOV-hAiO!5wn!dRRl zBV~lcDT$y^{bNy#5{F_R6r(X#hKDlTf}*x|zC($3coasTi z=l3*RfSIV20n*>;mT223rlCrIXE|*FtP9Zl=~5I)kqXGJbC~5*Yr4+r7fQc<`CaLK ztHx5yMzzCik7zX(p$59W)ObV}Vv+O>rLWFAd}!;|4z(VGYAnPY=$<&oV^D*I(g*Y8 z&`A1FW+AzG6_sUG@w(kp>RZ$slyLqqpGL!%ls4%PK& z^o*pZmDsvK%wOqm@nz9B9}67j`$$}dWzs{s%OQ{+#?;-&A;!5tuhZpVhtoZV%QOQQ z;tVMit(yxyhRbjf&V-_MrZ;dImP^-2x>80VHA;9gZ*py2MeU4ov$M`ax*Qiumr%MW zG&5@|r#hVBQ+x&1Vo`uas&_HYa#-w75ug)pOGZg9z>UFB$4$HjG>v6Lbgi?@DS)=i_+^bxL>*c^m4m6eR0-UER zFT_O-7y3kBjn&ct7vmC02TSyeUdv)f0xZmlwPU0gWi*m3I4>+vzA(Tbg+ElK0crXz{)SR-VQ7DZxgnX@A?@i94 z<`P8`QNGr-_>xZf=?~QV5=Iin7Zv0U2ynmZdq~n89`fyauA1?%E>uHFb$FNpEnJjK zt?(!wb9mH?$fZ0Kg*+hX%I<+_}YTzb&q%_+tP7(T$1sG!{gq`9yA6| z26$4HK82?pp7Kuipgwp8&pJHgo$Nuq@f6T0ioVp3|BU!gRnbJt@vHu6KPuwg z5%1nFX25@G81o*BnU}qWK{O1n@gMxV!)sRB1F|Qt;|+(`JsyK-2;Rg-hc`VQhtUw; z74j~{qhwBvwhSA+&OwxN81IaDXMAJVydiHSQj&(yQ2s6C-~Lgv!GD+$znc*+CeoOW zpd;~?!&_d^5j2W_jrdoUlhZh0F>bb4SAZf(Hu1fm@V2$50=$iP9Ns4D7pVgk@Q#po zB-X09#Niz;vORU+Un2e`zNk~1#(^}nWKK1aX(rO?<8}w$<3*%W$Up0!iYV3GbXem( z(J6R09q;;*b(b#Yd%WG@J@3IOntJa$yzhBWp<><^@-~%W-v$A1S6#SCuMe=v;e&l8 zhJWH8`3LY%=Hrh>elX5WdVPqG96t0VCPta~7@s(Nytl+eM7$N7`TK~sS_4%VV6)Eo zl)rQM)Vr);@wX9wyI%+d{GDpx?+yJ={w^CMu_eG374R88claz(Hgx_le1R_=zWBRp z48>Rc4Ze2x%0J1Xf%t~M#NTUGM{bbojxqTLE># zPuS-0laHy6)QP_e`K!dbSsS{|J5fL#`OApEv^7uvvEH#SBL2dXYV=lBw4K%H?LMix zs{Wtx3xCF29Dep8-jxo<4*rxkJM8dDWKVv@Zw|kDkzJ`9e-iO0mPGo6rw2yb7g9GW zBJuz#EO}P2GgRZx{$~5$Tx*QF;a?Q5L@rt)To`fFKBx z_*MQ7713b&CP)q%@GJZ>zr-){3;aB9;OD@v6J24@YDPhKgmz<NDt>hLPIyw&C#&-l z|I#=`{P_Q#3r75y9^T}&jrdUui+>!tMf^zp*I~pDTeqhgAF{8C3h;UZ;6Wn~7+GiJ zek1o8x!1@&ii`q-`+}6rAcb0J0G8=N+Fbo#tH1N9h6?q!R}#OLToemHsTsUh6K4(I z9i#>g`L2-HnrC%usG+qA)l=a&c$#+D2=Re*>dnbMj)qz~Z|B?i zR^2*Q(_p@ZZ|0l$Mmm{qP{m7V1>H>d&@=QJeZ$x5nd&Y*U`j(1T*cS%wY-W~@-=)l zU&U8)JnR$ox?Mxbn*4}w`&Vpl{Wok^8@A0NzGXjbL%uowa*w<&H(88Vm7E%>U1qo* z>4a)ta#&MaJv&mf_8r1Td|hIBueD`p6Y{FPksb0%9ohLu$k*t|&Lcv;TEF#kT*z11 zJKf_WzS6=1SJ>ip3Hfq;ZVgVzml=nCPlOy#jA~|ue5sAJgU;l;@K~ICKAGIOn3}~m z^!l?|U~9Ajx9}x=agY{-d{M+Jv?=UZ7voDPNahQJD2XqmAQnWL6ug|5MZEl98^ef~ z?N^8bzStHnjZ*jmKA+Fyb9pH*iTHy5Xa4#Bn4fAbWW?v~Ehy(&L#ENX)b?Eczr-qG zQAQ9_gm?KIKAX?t#k`2?_)I>77xL*LpQB#e1{w0%I+|WpsyLoy&vbo4UTo{tf05pM z-0E~>-BHMACPuaMLq0=C) zUc?J*#~Tyl`3B(JAeO{)39%ri^^@mBJm(*7CK_cv&0x|dm20_%t63wZc0ZnoYxW{h zy%&MmhCr{3z)?f|!LzuED|sf*;OVU1%~C79?^QbTzE|3_gzq!W_f|9X-g~S^5s9b@ z`BWWQLy*by;ywF~ieKD4t4@!=n+EBzAf1w01Q}ex(_%peC1-15P30+E9`aO^tVk#E zl;on2%e9x9KBao52ex9bnk1fPAT%&(nXw?#q&4O;E{z3^DacYE6#J6Bl0q)Cky@K{ pDC?OG_!TQaFB^Hu$csi^F!H>S4Mv_LDy}K5R+Jm<-n@Cu{{c~#@~Qv; diff --git a/src/vs/workbench/contrib/externalTerminal/node/iTermHelper.scpt b/src/vs/workbench/contrib/externalTerminal/node/iTermHelper.scpt index 3f490f9d1aeaee6098ff5103c1fc6e49a5f3c4f6..6ef0afa62045b05821a0012384db2c2840031dca 100644 GIT binary patch delta 1944 zcmZWqS#VTU5Iy}S8}H>NGe8oOAtqs8bu71F2%xeEgneH_NJ0oC6PSb~ti!%n2{rKP zkH40s7N1sW6;&ct`r!vqE(Id8iAdOF3COYUJj$`~qffp2-nrd~WB(yyKJ92(@A`dD>=9V`5ae6xQYzaq7J17m;Ak_@C^?IJRV8} zdxqnu@hz_9;F_gh58*n}P+`dze6l*^L5~MhfgBzIgdYG7eo*@zH;h7{NuST+EDvx$ z_X!VJ(tZp3Ec1riAGm20(iW@Jv$%ynaf|W2++*~~8IyeZ6S{EQC?r*$#X0WwxEt9K zWYtI8tJ`a0tyb!7U+Ovhh&wsBV{^Y7!d?667Mz&6I5^e2 z-7nNbJe5qzGLq@Ldx&ms^0+Cb#YmR_sT)tY(c?xNDV~v%-s!P3MR&N>>e*uTJcA=W z#5K|*sayl6vxDv2AndTDb_*MnDXy9)*+x7#GxTk`yd_3*j2OsTU3>DT&{Ik_juHnuY6vMup*QFw|_C&-~nbtI(LQ+gTcnba;JDU#ljXQa1HCtcV1lF#*A zXC&Wur*GK8D4SijBtFh7jz_ICJ$AzN7S`EH=B1=Jd2C8fMYO)7x~)E@OhzPQ z0$)p5bMGUCM*8^cGI>K22^%T$gASF~xrU9>*X0`Yd3kbMthqJX)*0Q<)Rd?SXUBz& zK!)^1fUBjL4K7zB&7E7{*=D3TnTw2+2v>Prm1;u<8*Hv&RjM5?lR@$l>se>yCCIBP zMc=U2WvzNzP}>?6)?0n{qLD)1XIOWv^tf`!ivp<)(cUCSO&gUZCAm@}tFsbPFf|b@ z$UIS1&`09gT7^<6Gg9jJzFf*#!xgL+*4R)hEL7Wim8tcU{zm#is`PESye$Ka4DbV# z%P1}naJgmnR~yJ>Mh5ymE2M%;xrB>_ORc*l7WDdYxJ(a~18sl`A8ce$lCR?rF5*Hi z5H7ORg%%cA&x6&5aK4ctDds%RtfJsQ$hkgIhdh+Ry z|CU;OT4EJar5}C>C00=dksTxm2ndM4@%DR^V_~Z9o!8yB@4e^r>CVr?KO6q#Ct-Q_ z__6RYBk%^t-jJWXWwGT8e#8Z|8FitR0KUZ!_!Sq8x|icqxP;3_oizIECvhc;E0C{n zHHNFmLkczMX>b{`P6JNjJ05j;)Eik+G2j%w$F(S~+34#rTt@`&+1R!qR>OJ3TL@F886Z3I(<4mg<&drU@{(6JR=nv$&g$;;wD;UJUo_+HHg~n(Lf4 z{Ri$Fbv_^G6z2mxH0oZCFXB%;GV1(V=Ww2TUG7DG6~d{;8k_1-Q0BaH`Z$Wmw#t(j zo+x#-NTbEzq3+FUv5UCGJudhBuhIKCzIMCZol73#0ZYw(OU*5{zwp$k^GR$+J9lv> zv%+0AXs2~rCWNPIf8&`^=abkjK;?71;0|s#cpk9Wt~^k*eFnuX%iCOT^LjanI+aiW z>Nf6*L`4*ogklo1cwYq6J-{;wOTy$4zWyi~iW4R_j_cQVP^MiescB zLNjz$qiludgu6Bxo(Pg7AnsjYzb#rY-Z)!+lYNc#2QX0UFE37UO z*A{|tLWW40bTv{IRPHBLT+205?r^PNIn&sbZr)@ed7CQ=RYq%oJSj(*^-{sr4(kzd z=GJa%Hd5jDA_L_$*14?9olqWE+g`gWT-8WIdP_I1Vy%&GkRb|1XISH~Ml&r)HKm2C zEWTR1Cm!UCOF2_6QAcDo8x7Yb(Dqf(!)si zKo&z~C|5Gc6+(UQgh}gGSiX9w^^{&ldP1h^>`-|}dK>8-6c{RRvpURb8`(>(50@M1 z6XYBw!?=t~xkR{3H*%?UOYD&?_wQR=e)-AM|`>FNk z0weu%k@Go^bA|J5&^+tr+RO`psHz@C#R%((%#i_7DVzfo%K(j;?QpgpV$z!xj>n@? ziTq;DLRZe@3_YbmobGUz_f6MCb|wm{5J_h0?A4%NhhiD1AJaJ1<+T5-xtxj)Rgiic zF$ldlg_Ai6gE$dYoWSuM#}6@uA0UY)WbrXR#TWP*XZgPVv$&>j>tj8m1jlj=M{^WM za)iSvh!i*~GL_6^FIisNWfF?2;I^*ItgTPiRoB$3*j-MvuRy8G39meh4##^Z$`gaf zsdiH9R<3ci0o$RTsH1&EoTlc?23PC<`xHAI>lIWa#*KL??{sTxI(K__`nT8R=vRd1 waMVk6IvlBAR`Cu;cxx&W;psBmJ6v%hQj$ceH?v!7VX`TefuQhdTifUV06K{J8UO$Q diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index 50752bdf27..40d67d198c 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -21,6 +21,8 @@ import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } f import { IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; import { IProductService } from 'vs/platform/product/common/productService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; export interface IFeedback { feedback: string; @@ -118,6 +120,12 @@ export class FeedbackDropdown extends Dropdown { closeBtn.setAttribute('role', 'button'); closeBtn.title = nls.localize('close', "Close"); + disposables.add(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, keyboardEvent => { + const standardKeyboardEvent = new StandardKeyboardEvent(keyboardEvent); + if (standardKeyboardEvent.keyCode === KeyCode.Escape) { + this.hide(); + } + })); disposables.add(dom.addDisposableListener(closeBtn, dom.EventType.MOUSE_OVER, () => { const theme = this.themeService.getColorTheme(); let darkenFactor: number | undefined; diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index 3d1ce3bdb8..c6c119b246 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -14,6 +14,7 @@ import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; class TwitterFeedbackService implements IFeedbackDelegate { @@ -63,7 +64,14 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben if (productService.sendASmile) { this.entry = this._register(statusbarService.addEntry(this.getStatusEntry(), 'status.feedback', localize('status.feedback', "Tweet Feedback"), StatusbarAlignment.RIGHT, -100 /* towards the end of the right hand side */)); - CommandsRegistry.registerCommand('_feedback.open', () => this.toggleFeedback()); + CommandsRegistry.registerCommand('help.tweetFeedback', () => this.toggleFeedback()); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: 'help.tweetFeedback', + category: localize('help', "Help"), + title: localize('status.feedback', "Tweet Feedback") + } + }); } } @@ -96,9 +104,8 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben return { text: '$(feedback)', tooltip: localize('status.feedback', "Tweet Feedback"), - command: '_feedback.open', + command: 'help.tweetFeedback', showBeak }; } - } diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 8735b95ad3..d9ba1d32ea 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -18,7 +18,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IFileService } from 'vs/platform/files/common/files'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; @@ -44,7 +44,7 @@ import { triggerDownload, asDomUri } from 'vs/base/browser/dom'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { sequence } from 'vs/base/common/async'; +import { sequence, timeout } from 'vs/base/common/async'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { once } from 'vs/base/common/functional'; @@ -488,7 +488,7 @@ export class GlobalCompareResourcesAction extends Action { constructor( id: string, label: string, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, + @IQuickInputService private readonly quickInputService: IQuickInputService, @IEditorService private readonly editorService: IEditorService, @INotificationService private readonly notificationService: INotificationService, ) { @@ -520,10 +520,13 @@ export class GlobalCompareResourcesAction extends Action { return undefined; }); - once(this.quickOpenService.onHide)((() => toDispose.dispose())); + once(this.quickInputService.onHide)((async () => { + await timeout(0); // prevent race condition with editor + toDispose.dispose(); + })); - // Bring up quick open - await this.quickOpenService.show('', { autoFocus: { autoFocusSecondEntry: true } }); + // Bring up quick access + this.quickInputService.quickAccess.show('', { itemActivation: ItemActivation.SECOND }); } else { this.notificationService.info(nls.localize('openFileToCompare', "Open a file first to compare it with another file.")); } diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index e0778014ca..47438153c3 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -45,7 +45,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } private registerCommonContributions(): void { - this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Sync"), this.environmentService.userDataSyncLogResource); + this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Preferences Sync"), this.environmentService.userDataSyncLogResource); this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); } diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index 8817cec72d..f9f1697cc8 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -22,9 +22,15 @@ export const EXECUTE_CELL_COMMAND_ID = 'workbench.notebook.cell.execute'; // Cell sizing related export const CELL_MARGIN = 20; export const CELL_RUN_GUTTER = 32; // TODO should be dynamic based on execution order width, and runnable enablement + +export const EDITOR_TOOLBAR_HEIGHT = 22; + +// Top margin of editor +export const EDITOR_TOP_MARGIN = 8; + +// Top and bottom padding inside the monaco editor in a cell, which are included in `cell.editorHeight` export const EDITOR_TOP_PADDING = 8; export const EDITOR_BOTTOM_PADDING = 8; -export const EDITOR_TOOLBAR_HEIGHT = 22; // Cell context keys export const NOTEBOOK_CELL_TYPE_CONTEXT_KEY = 'notebookCellType'; // code, markdown diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts index 41e83b04f0..db11fdd926 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts @@ -321,7 +321,7 @@ registerAction2(class extends Action2 { }); } - async run(accessor: ServicesAccessor): Promise { + async run(accessor: ServicesAccessor, context?: INotebookCellActionContext): Promise { return changeActiveCellToKind(CellKind.Markdown, accessor); } }); @@ -384,7 +384,7 @@ async function changeActiveCellToKind(kind: CellKind, accessor: ServicesAccessor return; } - editor.focusNotebookCell(newCell, false); + editor.focusNotebookCell(newCell, true); editor.deleteNotebookCell(activeCell); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index d9c036517b..1bf2f684e5 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -76,11 +76,6 @@ Registry.as(EditorInputExtensions.EditorInputFactor if (!data || !URI.isUri(resource) || typeof name !== 'string' || typeof viewType !== 'string') { return undefined; } - // TODO@joh,peng this is disabled because the note-editor isn't fit for being - // restorted (as it seems) - if ('true') { - return undefined; - } return instantiationService.createInstance(NotebookEditorInput, resource, name, viewType); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.css b/src/vs/workbench/contrib/notebook/browser/notebook.css index d6c448af59..b3952122ca 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/notebook.css @@ -64,7 +64,7 @@ .monaco-workbench .part.editor > .content .notebook-editor .output .multi-mimetype-output { position: absolute; top: 4px; - left: -28px; + left: -32px; width: 16px; height: 16px; cursor: pointer; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index cf29920f86..1d75ceb764 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -40,6 +40,7 @@ export interface CodeCellLayoutInfo { readonly editorHeight: number; readonly editorWidth: number; readonly totalHeight: number; + readonly outputContainerOffset: number; readonly outputTotalHeight: number; readonly indicatorHeight: number; } @@ -154,7 +155,7 @@ export interface INotebookEditor { /** * Layout the cell with a new height */ - layoutNotebookCell(cell: ICellViewModel, height: number): void; + layoutNotebookCell(cell: ICellViewModel, height: number): Promise; /** * Render the output in webview layer diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index b15fd505da..54b6d6b0e2 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -198,6 +198,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { multipleSelectionSupport: false, enableKeyboardNavigation: true, additionalScrollHeight: 0, + styleController: (_suffix: string) => { return this.list!; }, overrideStyles: { listBackground: editorBackground, listActiveSelectionBackground: editorBackground, @@ -243,13 +244,6 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } this.list?.splice(0, this.list?.length); - - if (this.notebookViewModel && !this.notebookViewModel.isDirty()) { - this.notebookService.destoryNotebookDocument(this.notebookViewModel.viewType!, this.notebookViewModel!.notebookDocument); - this.notebookViewModel.dispose(); - this.notebookViewModel = undefined; - } - super.onHide(); } @@ -335,12 +329,20 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } this.eventDispatcher = new NotebookEventDispatcher(); - this.notebookViewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model, this.eventDispatcher); + this.notebookViewModel = this.instantiationService.createInstance(NotebookViewModel, input.viewType!, model, this.eventDispatcher, this.getLayoutInfo()); this.editorEditable?.set(!!this.notebookViewModel.metadata?.editable); this.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); const viewState = this.loadTextEditorViewState(input); this.notebookViewModel.restoreEditorViewState(viewState); + if (viewState?.scrollPosition !== undefined) { + this.list!.scrollTop = viewState!.scrollPosition.top; + this.list!.scrollLeft = viewState!.scrollPosition.left; + } else { + this.list!.scrollTop = 0; + this.list!.scrollLeft = 0; + } + this.localStore.add(this.eventDispatcher.onDidChangeMetadata((e) => { this.editorEditable?.set(e.source.editable); })); @@ -348,11 +350,27 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { this.localStore.add(this.notebookViewModel.onDidChangeViewCells((e) => { if (e.synchronous) { e.splices.reverse().forEach((diff) => { + // remove output in the webview + for (let i = diff[0]; i < diff[0] + diff[1]; i++) { + const cell = this.list?.element(i); + cell?.cell.outputs.forEach(output => { + this.removeInset(output); + }); + } + this.list?.splice(diff[0], diff[1], diff[2]); }); } else { DOM.scheduleAtNextAnimationFrame(() => { e.splices.reverse().forEach((diff) => { + // remove output in the webview + for (let i = diff[0]; i < diff[0] + diff[1]; i++) { + const cell = this.list?.element(i); + cell?.cell.outputs.forEach(output => { + this.removeInset(output); + }); + } + this.list?.splice(diff[0], diff[1], diff[2]); }); }); @@ -399,6 +417,10 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { private saveTextEditorViewState(input: NotebookEditorInput): void { if (this.group && this.notebookViewModel) { const state = this.notebookViewModel.saveEditorViewState(); + if (this.list) { + state.scrollPosition = { left: this.list.scrollLeft, top: this.list.scrollTop }; + } + this.editorMemento.saveEditorState(this.group, input.resource, state); } } @@ -542,7 +564,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { //#endregion //#region Cell operations - layoutNotebookCell(cell: ICellViewModel, height: number) { + async layoutNotebookCell(cell: ICellViewModel, height: number): Promise { let relayout = (cell: ICellViewModel, height: number) => { let index = this.notebookViewModel!.getViewCellIndex(cell); if (index >= 0) { @@ -550,9 +572,13 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { } }; + let r: () => void; DOM.scheduleAtNextAnimationFrame(() => { relayout(cell, height); + r(); }); + + return new Promise(resolve => { r = resolve; }); } async insertNotebookCell(cell: ICellViewModel, type: CellKind, direction: 'above' | 'below', initialText: string = ''): Promise { @@ -560,47 +586,52 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { const language = newLanguages && newLanguages.length ? newLanguages[0] : 'markdown'; const index = this.notebookViewModel!.getViewCellIndex(cell); const insertIndex = direction === 'above' ? index : index + 1; - const newModeCell = await this.notebookService.createNotebookCell(this.notebookViewModel!.viewType, this.notebookViewModel!.uri, insertIndex, language, type); - newModeCell!.source = initialText.split(/\r?\n/g); - const newCell = this.notebookViewModel!.insertCell(insertIndex, newModeCell!, true); + const newCell = this.notebookViewModel!.createCell(insertIndex, initialText.split(/\r?\n/g), language, type, true); this.list?.setFocus([insertIndex]); if (type === CellKind.Markdown) { newCell.editState = CellEditState.Editing; } + let r: () => void; DOM.scheduleAtNextAnimationFrame(() => { this.list?.revealInCenterIfOutsideViewport(insertIndex); + r(); }); + + return new Promise(resolve => { r = resolve; }); } async deleteNotebookCell(cell: ICellViewModel): Promise { (cell as CellViewModel).save(); const index = this.notebookViewModel!.getViewCellIndex(cell); - await this.notebookService.deleteNotebookCell(this.notebookViewModel!.viewType, this.notebookViewModel!.uri, index); this.notebookViewModel!.deleteCell(index, true); } - moveCellDown(cell: ICellViewModel): void { + async moveCellDown(cell: ICellViewModel): Promise { const index = this.notebookViewModel!.getViewCellIndex(cell); const newIdx = index + 1; - this.moveCellToIndex(cell, index, newIdx); + return this.moveCellToIndex(index, newIdx); } - moveCellUp(cell: ICellViewModel): void { + async moveCellUp(cell: ICellViewModel): Promise { const index = this.notebookViewModel!.getViewCellIndex(cell); const newIdx = index - 1; - this.moveCellToIndex(cell, index, newIdx); + return this.moveCellToIndex(index, newIdx); } - private moveCellToIndex(cell: ICellViewModel, index: number, newIdx: number): void { + private async moveCellToIndex(index: number, newIdx: number): Promise { if (!this.notebookViewModel!.moveCellToIdx(index, newIdx, true)) { return; } + let r: () => void; DOM.scheduleAtNextAnimationFrame(() => { this.list?.revealInCenterIfOutsideViewport(index + 1); + r(); }); + + return new Promise(resolve => { r = resolve; }); } editNotebookCell(cell: CellViewModel): void { @@ -626,7 +657,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor { async executeNotebookCell(cell: ICellViewModel): Promise { try { cell.runState = CellRunState.Running; - const provider = this.notebookService.getContributedNotebookProviders(cell.uri)[0]; + const provider = this.notebookService.getContributedNotebookProviders(this.viewModel!.uri)[0]; if (provider) { const viewType = provider.id; const notebookUri = CellUri.parse(cell.uri)?.notebook; @@ -794,10 +825,11 @@ registerThemingParticipant((theme, collector) => { } // Cell Margin - collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell { margin: 8px ${CELL_MARGIN}px 8px ${CELL_MARGIN}px; }`); - collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 8px ${CELL_MARGIN}px 8px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell { margin: 8px ${CELL_MARGIN}px 0px ${CELL_MARGIN}px; }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`); collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .cell-editor-container { width: calc(100% - ${CELL_RUN_GUTTER}px); }`); + collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .markdown-editor-container { margin-left: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .run-button-container { width: ${CELL_RUN_GUTTER}px; }`); }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts index 34b61144ca..6c2d9c2769 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorInput.ts @@ -10,6 +10,7 @@ import { ICell, NotebookCellsSplice } from 'vs/workbench/contrib/notebook/common import { URI } from 'vs/base/common/uri'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { isEqual } from 'vs/base/common/resources'; export class NotebookEditorModel extends EditorModel { private _dirty = false; @@ -53,8 +54,7 @@ export class NotebookEditorModel extends EditorModel { let notebook = this.getNotebook(); if (notebook) { - let mainCell = new NotebookCellTextModel(URI.revive(cell.uri), cell.handle, cell.source, cell.language, cell.cellKind, cell.outputs, cell.metadata); - this.notebook.insertNewCell(index, mainCell); + this.notebook.insertNewCell(index, [cell as NotebookCellTextModel]); this._dirty = true; this._onDidChangeDirty.fire(); @@ -137,4 +137,23 @@ export class NotebookEditorInput extends EditorInput { return this.promise; } + + matches(otherInput: unknown): boolean { + if (this === otherInput) { + return true; + } + if (otherInput instanceof NotebookEditorInput) { + return this.viewType === otherInput.viewType + && isEqual(this.resource, otherInput.resource); + } + return false; + } + + dispose() { + if (this.textModel) { + this.notebookService.destoryNotebookDocument(this.textModel!.notebook.viewType, this.textModel!.notebook); + } + + super.dispose(); + } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookService.ts b/src/vs/workbench/contrib/notebook/browser/notebookService.ts index 34a89f713d..39606fc8bf 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookService.ts @@ -10,11 +10,10 @@ import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from ' import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookTextModel, ICell, INotebookMimeTypeSelector, INotebookRendererInfo, CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, INotebookMimeTypeSelector, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { Iterable } from 'vs/base/common/iterator'; -import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; function MODEL_ID(resource: URI): string { @@ -26,8 +25,6 @@ export const INotebookService = createDecorator('notebookServi export interface IMainNotebookController { resolveNotebook(viewType: string, uri: URI): Promise; executeNotebook(viewType: string, uri: URI): Promise; - createRawCell(uri: URI, index: number, language: string, type: CellKind): Promise; - deleteCell(uri: URI, index: number): Promise onDidReceiveMessage(uri: URI, message: any): void; executeNotebookCell(uri: URI, handle: number): Promise; destoryNotebookDocument(notebook: INotebookTextModel): Promise; @@ -49,8 +46,6 @@ export interface INotebookService { getContributedNotebookProviders(resource: URI): readonly NotebookProviderInfo[]; getNotebookProviderResourceRoots(): URI[]; - createNotebookCell(viewType: string, resource: URI, index: number, language: string, type: CellKind): Promise; - deleteNotebookCell(viewType: string, resource: URI, index: number): Promise; destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; updateActiveNotebookDocument(viewType: string, resource: URI): void; save(viewType: string, resource: URI): Promise; @@ -131,7 +126,7 @@ export class NotebookService extends Disposable implements INotebookService { private readonly _models: { [modelId: string]: ModelData; }; private _onDidChangeActiveEditor = new Emitter<{ viewType: string, uri: URI }>(); onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }> = this._onDidChangeActiveEditor.event; - private _resolvePool = new Map void>(); + private _resolvePool = new Map void)[]>(); constructor( @IExtensionService private readonly extensionService: IExtensionService @@ -180,16 +175,22 @@ export class NotebookService extends Disposable implements INotebookService { let resolve: () => void; const promise = new Promise(r => { resolve = r; }); - this._resolvePool.set(viewType, resolve!); + if (!this._resolvePool.has(viewType)) { + this._resolvePool.set(viewType, []); + } + + let resolves = this._resolvePool.get(viewType)!; + resolves.push(resolve!); + this._resolvePool.set(viewType, resolves); return promise; } registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController) { this._notebookProviders.set(viewType, { extensionData, controller }); - let resolve = this._resolvePool.get(viewType); - if (resolve) { - resolve(); + let resolves = this._resolvePool.get(viewType); + if (resolves) { + resolves.forEach(resolve => resolve()); this._resolvePool.delete(viewType); } } @@ -241,26 +242,6 @@ export class NotebookService extends Disposable implements INotebookService { return modelData.model; } - async createNotebookCell(viewType: string, resource: URI, index: number, language: string, type: CellKind): Promise { - let provider = this._notebookProviders.get(viewType); - - if (provider) { - return provider.controller.createRawCell(resource, index, language, type); - } - - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - async deleteNotebookCell(viewType: string, resource: URI, index: number): Promise { - let provider = this._notebookProviders.get(viewType); - - if (provider) { - return provider.controller.deleteCell(resource, index); - } - - return false; - } - async executeNotebook(viewType: string, uri: URI): Promise { let provider = this._notebookProviders.get(viewType); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index a4cf215b35..d93a7fd48f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -19,8 +19,9 @@ import { Range } from 'vs/editor/common/core/range'; import { CellRevealType, CellRevealPosition, CursorAtBoundary } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; +import { IStyleController, IListStyles } from 'vs/base/browser/ui/list/listWidget'; -export class NotebookCellList extends WorkbenchList implements IDisposable { +export class NotebookCellList extends WorkbenchList implements IDisposable, IStyleController { get onWillScroll(): Event { return this.view.onWillScroll; } get rowsContainer(): HTMLElement { @@ -28,7 +29,7 @@ export class NotebookCellList extends WorkbenchList implements ID } private _previousSelectedElements: CellViewModel[] = []; private _localDisposableStore = new DisposableStore(); - + private styleElement?: HTMLStyleElement; constructor( private listUser: string, container: HTMLElement, @@ -305,6 +306,10 @@ export class NotebookCellList extends WorkbenchList implements ID } private _revealInternal(index: number, ignoreIfInsideViewport: boolean, revealPosition: CellRevealPosition) { + if (index >= this.view.length) { + return; + } + const scrollTop = this.view.getScrollTop(); const wrapperBottom = scrollTop + this.view.renderHeight; const elementTop = this.view.elementTop(index); @@ -345,6 +350,126 @@ export class NotebookCellList extends WorkbenchList implements ID } } + + style(styles: IListStyles) { + const selectorSuffix = this.view.domId; + if (!this.styleElement) { + this.styleElement = DOM.createStyleSheet(this.view.domNode); + } + const suffix = selectorSuffix && `.${selectorSuffix}`; + const content: string[] = []; + + if (styles.listBackground) { + if (styles.listBackground.isOpaque()) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows { background: ${styles.listBackground}; }`); + } else if (!isMacintosh) { // subpixel AA doesn't exist in macOS + console.warn(`List with id '${selectorSuffix}' was styled with a non-opaque background color. This will break sub-pixel antialiasing.`); + } + } + + if (styles.listFocusBackground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; }`); + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused:hover { background-color: ${styles.listFocusBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listFocusForeground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { color: ${styles.listFocusForeground}; }`); + } + + if (styles.listActiveSelectionBackground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { background-color: ${styles.listActiveSelectionBackground}; }`); + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected:hover { background-color: ${styles.listActiveSelectionBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listActiveSelectionForeground) { + content.push(`.monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { color: ${styles.listActiveSelectionForeground}; }`); + } + + if (styles.listFocusAndSelectionBackground) { + content.push(` + .monaco-drag-image, + .monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; } + `); + } + + if (styles.listFocusAndSelectionForeground) { + content.push(` + .monaco-drag-image, + .monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; } + `); + } + + if (styles.listInactiveFocusBackground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { background-color: ${styles.listInactiveFocusBackground}; }`); + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused:hover { background-color: ${styles.listInactiveFocusBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listInactiveSelectionBackground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { background-color: ${styles.listInactiveSelectionBackground}; }`); + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected:hover { background-color: ${styles.listInactiveSelectionBackground}; }`); // overwrite :hover style in this case! + } + + if (styles.listInactiveSelectionForeground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { color: ${styles.listInactiveSelectionForeground}; }`); + } + + if (styles.listHoverBackground) { + content.push(`.monaco-list${suffix}:not(.drop-target) > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover:not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`); + } + + if (styles.listHoverForeground) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover:not(.selected):not(.focused) { color: ${styles.listHoverForeground}; }`); + } + + if (styles.listSelectionOutline) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.selected { outline: 1px dotted ${styles.listSelectionOutline}; outline-offset: -1px; }`); + } + + if (styles.listFocusOutline) { + content.push(` + .monaco-drag-image, + .monaco-list${suffix}:focus > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } + `); + } + + if (styles.listInactiveFocusOutline) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row.focused { outline: 1px dotted ${styles.listInactiveFocusOutline}; outline-offset: -1px; }`); + } + + if (styles.listHoverOutline) { + content.push(`.monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows > .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); + } + + if (styles.listDropBackground) { + content.push(` + .monaco-list${suffix}.drop-target, + .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-rows.drop-target, + .monaco-list${suffix} > div.monaco-scrollable-element > .monaco-list-row.drop-target { background-color: ${styles.listDropBackground} !important; color: inherit !important; } + `); + } + + if (styles.listFilterWidgetBackground) { + content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`); + } + + if (styles.listFilterWidgetOutline) { + content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`); + } + + if (styles.listFilterWidgetNoMatchesOutline) { + content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`); + } + + if (styles.listMatchesShadow) { + content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`); + } + + const newStyles = content.join('\n'); + if (newStyles !== this.styleElement.innerHTML) { + this.styleElement.innerHTML = newStyles; + } + } + dispose() { this._localDisposableStore.dispose(); super.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index ae1f6e2021..a1ecd52860 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -105,6 +105,7 @@ export class BackLayerWebView extends Disposable { const loader = URI.file(path.join(environmentSerice.appRoot, '/out/vs/loader.js')).with({ scheme: WebviewResourceScheme }); + const outputNodePadding = 8; let content = /* html */` @@ -112,8 +113,8 @@ export class BackLayerWebView extends Disposable {