From d15a3fcc98a5404033f3c9b0f315bf73342089ca Mon Sep 17 00:00:00 2001 From: Chris LaFreniere <40371649+chlafreniere@users.noreply.github.com> Date: Mon, 10 Jun 2019 18:27:09 -0700 Subject: [PATCH] Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d (#5949) * Merge from vscode 81d7885dc2e9dc617e1522697a2966bc4025a45d * Fix vs unit tests and hygiene issue * Fix strict null check issue --- .gitignore | 3 + .vscode/launch.json | 2 +- .yarnrc | 2 +- CODE_OF_CONDUCT.md | 1 - build/.nativeignore | 4 + build/azure-pipelines/common/installDistro.ts | 20 - .../common/installDistroDependencies.ts | 38 + build/azure-pipelines/common/symbols.ts | 3 +- .../darwin/product-build-darwin.yml | 3 +- build/azure-pipelines/linux/build-arm.sh | 3 + build/azure-pipelines/linux/prebuild-arm.sh | 3 + .../linux/product-build-linux-arm.yml | 65 + .../linux/product-build-linux.yml | 6 +- build/azure-pipelines/linux/publish-arm.sh | 3 + build/azure-pipelines/linux/publish.sh | 6 +- build/azure-pipelines/product-build.yml | 14 +- .../win32/product-build-win32.yml | 3 +- build/gulpfile.reh.js | 103 +- build/gulpfile.vscode.js | 7 +- build/gulpfile.vscode.linux.js | 5 +- build/lib/electron.js | 2 +- build/lib/i18n.resources.json | 4 - build/lib/i18n.ts | 2 +- build/lib/watch/package.json | 1 + build/package.json | 3 +- build/win32/i18n/messages.en.isl | 2 +- build/yarn.lock | 8 +- cgmanifest.json | 29 +- extensions/bat/package.json | 1 + extensions/configuration-editing/package.json | 3 +- .../schemas/devContainer.schema.json | 52 +- extensions/configuration-editing/yarn.lock | 8 +- .../server/src/test/links.test.ts | 79 + .../server/test/linksTestFixtures/.gitignore | 1 + .../node_modules/foo/package.json | 0 extensions/docker/package.json | 1 + extensions/extension-editing/package.json | 3 +- extensions/extension-editing/yarn.lock | 8 +- extensions/git-ui/.vscodeignore | 8 + extensions/git-ui/README.md | 7 + extensions/git-ui/cgmanifest.json | 4 + extensions/git-ui/extension.webpack.config.js | 17 + extensions/git-ui/package.json | 28 + extensions/git-ui/package.nls.json | 4 + extensions/git-ui/resources/icons/git.png | Bin 0 -> 2383 bytes extensions/git-ui/src/main.ts | 57 + extensions/git-ui/src/typings/refs.d.ts | 8 + extensions/git-ui/tsconfig.json | 13 + extensions/git-ui/yarn.lock | 8 + extensions/git/package.json | 7 +- extensions/git/src/api/git.d.ts | 2 +- extensions/git/src/commands.ts | 8 +- extensions/git/src/git.ts | 21 +- extensions/git/src/repository.ts | 37 +- extensions/git/src/test/git.test.ts | 11 + extensions/git/yarn.lock | 13 +- .../json-language-features/package.json | 3 +- .../json-language-features/server/README.md | 9 +- .../server/package.json | 2 +- .../json-language-features/server/yarn.lock | 8 +- extensions/json-language-features/yarn.lock | 8 +- extensions/json/package.json | 1 + extensions/markdown-basics/package.json | 1 + .../media/markdown.css | 5 +- .../markdown-language-features/package.json | 5 +- .../src/features/documentLinkProvider.ts | 28 +- .../markdown-language-features/yarn.lock | 16 +- extensions/merge-conflict/package.json | 18 +- extensions/merge-conflict/package.nls.json | 6 +- .../merge-conflict/src/commandHandler.ts | 42 +- .../merge-conflict/src/contentProvider.ts | 22 +- extensions/merge-conflict/yarn.lock | 8 +- .../objective-c/build/update-grammars.js | 11 + .../test/colorize-fixtures/test.mm | 52 + .../test/colorize-results/test_mm.json | 3093 +++++++++++++++++ extensions/package.json | 2 +- extensions/powershell/package.json | 1 + extensions/python/package.json | 2 + extensions/r/package.json | 1 + extensions/sql/package.json | 1 + extensions/theme-abyss/package.json | 1 + .../theme-abyss/themes/abyss-color-theme.json | 4 +- extensions/theme-defaults/package.json | 1 + .../theme-defaults/themes/dark_plus.json | 6 +- .../theme-defaults/themes/hc_black.json | 8 +- .../theme-defaults/themes/light_plus.json | 8 +- extensions/theme-kimbie-dark/package.json | 1 + .../themes/kimbie-dark-color-theme.json | 4 +- extensions/theme-monokai-dimmed/package.json | 1 + .../themes/dimmed-monokai-color-theme.json | 4 +- extensions/theme-monokai/package.json | 1 + .../themes/monokai-color-theme.json | 2 +- extensions/theme-quietlight/package.json | 1 + .../themes/quietlight-color-theme.json | 2 + extensions/theme-red/package.json | 1 + extensions/theme-seti/package.json | 1 + extensions/theme-solarized-dark/package.json | 1 + .../themes/solarized-dark-color-theme.json | 4 +- extensions/theme-solarized-light/package.json | 1 + .../themes/solarized-light-color-theme.json | 12 +- .../theme-tomorrow-night-blue/package.json | 1 + .../themes/tomorrow-night-blue-theme.json | 2 +- extensions/vscode-colorize-tests/package.json | 3 +- extensions/vscode-colorize-tests/yarn.lock | 8 +- extensions/vscode-test-resolver/package.json | 2 +- .../vscode-test-resolver/src/extension.ts | 55 +- extensions/vscode-test-resolver/yarn.lock | 8 +- extensions/xml/package.json | 1 + extensions/yaml/package.json | 1 + extensions/yarn.lock | 8 +- package.json | 24 +- remote/installDevModules.sh | 20 - remote/installDevPackagesAsRoot.sh | 16 - remote/launchDevMode.sh | 17 - remote/package.json | 6 +- remote/yarn.lock | 71 +- resources/linux/rpm/dependencies.json | 78 - resources/win32/bin/code.sh | 3 +- scripts/code-web.js | 24 - scripts/code.sh | 2 +- scripts/test-integration.bat | 4 + scripts/test-integration.sh | 4 + src/main.js | 4 +- src/sql/azdata.proposed.d.ts | 4 +- src/sql/base/browser/ui/button/button.ts | 2 +- .../browser/ui/dropdownList/dropdownList.ts | 6 +- .../scrollableSplitview.ts | 12 +- .../table/plugins/autoSizeColumns.plugin.ts | 2 +- src/sql/base/browser/ui/taskbar/actionbar.ts | 5 +- .../accounts/browser/accountDialog.ts | 2 +- .../common/connectionManagementService.ts | 6 +- .../query/common/queryModelService.ts | 8 + .../extensionHost.contribution.common.ts | 23 + .../mainThreadAccountManagement.ts | 0 .../mainThreadBackgroundTaskManagement.ts | 0 .../mainThreadConnectionManagement.ts | 0 .../mainThreadCredentialManagement.ts | 0 .../mainThreadDashboard.ts | 0 .../mainThreadDashboardWebview.ts | 0 .../mainThreadDataProtocol.ts | 0 .../mainThreadExtensionManagement.ts | 0 .../mainThreadModalDialog.ts | 0 .../{node => browser}/mainThreadModelView.ts | 0 .../mainThreadModelViewDialog.ts | 0 .../{node => browser}/mainThreadNotebook.ts | 0 .../mainThreadNotebookDocumentsAndEditors.ts | 0 .../mainThreadObjectExplorer.ts | 0 .../mainThreadQueryEditor.ts | 0 .../mainThreadResourceProvider.ts | 0 .../mainThreadSerializationProvider.ts | 0 .../mainThreadTasks.ts | 0 .../extensionHost.contribution.ts} | 2 +- .../workbench/api/node/sqlExtHost.api.impl.ts | 8 +- .../api/node/sqlExtHost.contribution.ts | 46 - .../browser/parts/views/customView.ts | 6 +- .../modelComponents/diffeditor.component.ts | 4 +- .../modelComponents/editor.component.ts | 4 +- .../browser/connection.contribution.ts | 2 + .../connection/browser/connectionStatus.ts | 2 +- .../common/connectionGlobalStatus.ts | 12 +- .../widgets/explorer/explorerTree.ts | 14 +- .../explorer/explorerWidget.component.ts | 7 +- .../browser/connectionViewletPanel.ts | 1 - .../electron-browser/nodeCommands.ts | 4 +- .../parts/editData/common/editDataInput.ts | 8 +- .../notebook/cellViews/code.component.ts | 4 +- .../parts/notebook/notebookStyles.ts | 2 +- .../browser/treeSelectionHandler.ts | 4 +- .../profiler/browser/profilerTableEditor.ts | 2 +- .../parts/query/browser/flavorStatus.ts | 2 +- .../parts/query/browser/queryEditor.ts | 2 +- .../parts/query/browser/queryStatus.ts | 2 +- .../parts/query/browser/rowCountStatus.ts | 2 +- .../parts/query/browser/timeElapsedStatus.ts | 2 +- .../browser/accountManagementService.ts | 2 + .../commandLine/common/commandLineService.ts | 20 +- .../browser/connectionDialogWidget.ts | 4 - .../test/common/insightsUtils.test.ts | 3 +- .../api/extHostAccountManagement.test.ts | 2 +- .../api/extHostCredentialManagement.test.ts | 2 +- ...mainThreadBackgroundTaskManagement.test.ts | 2 +- .../api/mainThreadModelViewDialog.test.ts | 2 +- .../workbench/api/mainThreadNotebook.test.ts | 2 +- src/typings/electron.d.ts | 1059 ++++-- src/typings/onigasm-umd.d.ts | 33 + src/typings/require.d.ts | 7 +- .../vscode-windows-ca-certs.d.ts} | 4 +- .../vsda.d.ts} | 6 +- src/typings/xterm-addon-search.d.ts | 69 + src/typings/xterm-addon-web-links.d.ts | 71 + src/typings/{vscode-xterm.d.ts => xterm.d.ts} | 81 +- src/vs/base/browser/contextmenu.ts | 2 +- src/vs/base/browser/dom.ts | 32 +- src/vs/base/browser/globalMouseMoveMonitor.ts | 40 +- src/vs/base/browser/hash.ts | 15 - src/vs/base/browser/htmlContentRenderer.ts | 2 +- src/vs/base/browser/ui/actionbar/actionbar.ts | 13 +- .../ui/breadcrumbs/breadcrumbsWidget.ts | 10 +- .../browser/ui/centered/centeredViewLayout.ts | 14 +- src/vs/base/browser/ui/checkbox/checkbox.ts | 13 +- .../browser/ui/contextview/contextview.ts | 19 +- src/vs/base/browser/ui/dropdown/dropdown.ts | 12 +- src/vs/base/browser/ui/grid/grid.ts | 15 +- src/vs/base/browser/ui/inputbox/inputBox.ts | 22 +- src/vs/base/browser/ui/menu/menu.css | 2 +- src/vs/base/browser/ui/menu/menu.ts | 36 +- src/vs/base/browser/ui/menu/menubar.ts | 22 +- .../ui/octiconLabel/octicons/octicons.css | 22 +- .../ui/octiconLabel/octicons/octicons.svg | 38 +- .../ui/octiconLabel/octicons/octicons.ttf | Bin 36908 -> 37504 bytes src/vs/base/browser/ui/sash/sash.ts | 17 +- .../browser/ui/scrollbar/abstractScrollbar.ts | 6 + src/vs/base/browser/ui/selectBox/selectBox.ts | 5 +- .../browser/ui/selectBox/selectBoxCustom.ts | 65 +- .../browser/ui/selectBox/selectBoxNative.ts | 18 +- src/vs/base/browser/ui/splitview/panelview.ts | 51 +- src/vs/base/browser/ui/splitview/splitview.ts | 10 +- src/vs/base/browser/ui/toolbar/toolbar.ts | 8 +- src/vs/base/common/actions.ts | 22 +- src/vs/base/common/async.ts | 32 +- src/vs/base/common/cache.ts | 4 +- src/vs/base/common/errorsWithActions.ts | 4 +- src/vs/base/common/event.ts | 15 +- src/vs/base/common/glob.ts | 6 +- src/vs/base/common/lifecycle.ts | 147 +- src/vs/base/common/uri.ts | 143 +- src/vs/base/common/uriIpc.ts | 49 + src/vs/base/common/worker/simpleWorker.ts | 5 +- src/vs/base/node/config.ts | 23 +- src/vs/base/node/pfs.ts | 24 +- src/vs/base/node/processes.ts | 9 +- src/vs/base/parts/ipc/common/ipc.net.ts | 7 +- src/vs/base/parts/ipc/common/ipc.ts | 2 +- src/vs/base/parts/ipc/node/ipc.net.ts | 197 +- src/vs/base/parts/tree/browser/tree.ts | 6 +- src/vs/base/parts/tree/browser/treeModel.ts | 4 +- src/vs/base/test/browser/hash.test.ts | 16 - src/vs/base/test/common/event.test.ts | 23 +- src/vs/base/test/common/lifecycle.test.ts | 3 +- src/vs/base/test/common/uri.test.ts | 71 +- src/vs/code/browser/workbench/workbench.html | 7 +- src/vs/code/browser/workbench/workbench.js | 9 +- .../issue/issueReporterMain.ts | 4 +- .../contrib/languagePackCachedDataCleaner.ts | 21 +- .../sharedProcess/sharedProcessMain.ts | 21 +- .../electron-browser/workbench/workbench.html | 2 +- .../electron-browser/workbench/workbench.js | 10 +- src/vs/code/electron-main/app.ts | 455 ++- src/vs/code/electron-main/keyboard.ts | 32 - src/vs/code/electron-main/logUploader.ts | 4 +- src/vs/code/electron-main/main.ts | 561 +-- src/vs/code/electron-main/sharedProcess.ts | 9 +- src/vs/code/electron-main/theme.ts | 44 - src/vs/code/electron-main/window.ts | 15 +- src/vs/code/electron-main/windows.ts | 153 +- src/vs/code/node/cli.ts | 5 +- src/vs/code/node/cliProcessMain.ts | 66 +- src/vs/code/node/shellEnv.ts | 8 +- src/vs/editor/browser/core/editorState.ts | 4 +- src/vs/editor/browser/view/viewImpl.ts | 4 +- .../browser/viewParts/minimap/minimap.ts | 7 +- .../editor/browser/widget/codeEditorWidget.ts | 3 +- src/vs/editor/browser/widget/diffNavigator.ts | 21 +- .../common/config/commonEditorConfig.ts | 2 +- src/vs/editor/common/core/uint.ts | 6 +- .../editor/common/model/indentationGuesser.ts | 30 +- src/vs/editor/common/modes.ts | 20 +- .../editor/common/view/editorColorRegistry.ts | 18 +- .../common/viewModel/splitLinesCollection.ts | 5 +- .../editor/contrib/codeAction/codeAction.ts | 30 +- .../contrib/codeAction/codeActionCommands.ts | 134 +- .../contrib/codeAction/codeActionModel.ts | 134 +- .../contrib/codeAction/codeActionTrigger.ts | 5 + .../contrib/codeAction/codeActionWidget.ts | 24 +- .../contrib/codeAction/lightBulbWidget.ts | 12 +- .../codeAction/test/codeAction.test.ts | 106 +- .../codeAction/test/codeActionModel.test.ts | 81 +- .../editor/contrib/codelens/codeLensCache.ts | 45 +- src/vs/editor/contrib/codelens/codelens.ts | 59 +- .../contrib/codelens/codelensController.ts | 100 +- .../editor/contrib/codelens/codelensWidget.ts | 20 +- .../contrib/colorPicker/colorDetector.ts | 22 +- .../editor/contrib/contextmenu/contextmenu.ts | 4 +- src/vs/editor/contrib/dnd/dnd.ts | 23 +- .../editor/contrib/find/simpleFindWidget.ts | 12 +- src/vs/editor/contrib/folding/folding.ts | 43 +- src/vs/editor/contrib/format/formatActions.ts | 45 +- .../goToDefinition/goToDefinitionCommands.ts | 5 +- .../goToDefinitionResultsNavigation.ts | 44 +- .../contrib/gotoError/gotoErrorWidget.ts | 29 +- .../gotoError/media/gotoErrorWidget.css | 26 +- .../gotoError/media/status-error-inverse.svg | 1 - .../contrib/gotoError/media/status-error.svg | 1 - .../gotoError/media/status-info-inverse.svg | 1 - .../contrib/gotoError/media/status-info.svg | 1 - .../media/status-warning-inverse.svg | 1 - .../gotoError/media/status-warning.svg | 1 - src/vs/editor/contrib/hover/hover.ts | 27 +- src/vs/editor/contrib/hover/hoverWidgets.ts | 5 +- .../editor/contrib/hover/modesContentHover.ts | 54 +- src/vs/editor/contrib/links/getLinks.ts | 16 +- src/vs/editor/contrib/links/links.ts | 17 + .../contrib/markdown/markdownRenderer.ts | 2 +- .../editor/contrib/multicursor/multicursor.ts | 35 +- .../parameterHints/parameterHintsWidget.ts | 35 +- .../referenceSearch/media/peekViewWidget.css | 8 - .../contrib/referenceSearch/peekViewWidget.ts | 14 +- .../referenceSearch/referencesController.ts | 14 +- .../referenceSearch/referencesModel.ts | 27 +- .../contrib/referenceSearch/referencesTree.ts | 53 +- .../referenceSearch/referencesWidget.ts | 46 +- src/vs/editor/contrib/rename/rename.ts | 4 +- .../editor/contrib/rename/renameInputField.ts | 20 +- .../contrib/smartSelect/bracketSelections.ts | 12 +- .../editor/contrib/smartSelect/smartSelect.ts | 4 +- .../smartSelect/test/smartSelect.test.ts | 23 +- .../contrib/smartSelect/wordSelections.ts | 8 +- .../editor/contrib/snippet/snippetSession.ts | 30 +- .../snippet/test/snippetController2.test.ts | 18 + src/vs/editor/contrib/suggest/suggest.ts | 45 +- .../editor/contrib/suggest/suggestWidget.ts | 29 +- .../editor/contrib/suggest/wordContextKey.ts | 11 +- .../editor/contrib/zoneWidget/zoneWidget.ts | 21 +- .../iPadShowKeyboard/iPadShowKeyboard.ts | 22 +- .../standalone/browser/simpleServices.ts | 36 +- .../browser/standaloneCodeEditor.ts | 20 +- .../standalone/browser/standaloneLanguages.ts | 6 +- .../standalone/browser/standaloneServices.ts | 6 +- .../test/common/model/textModel.test.ts | 51 +- src/vs/loader.js | 246 +- src/vs/monaco.d.ts | 21 +- src/vs/platform/actions/common/menuService.ts | 22 +- .../backup/electron-main/backupMainService.ts | 18 +- .../common/configurationRegistry.ts | 12 +- .../configuration/node/configuration.ts | 56 - .../node/configurationService.ts | 62 +- .../test/common/configuration.test.ts | 16 +- .../test/common/configurationModels.test.ts | 4 +- .../test/node/configurationService.test.ts | 15 +- .../contextview/browser/contextMenuHandler.ts | 4 +- .../credentials/common/credentials.ts | 16 + .../credentials/node/credentialsService.ts | 41 + src/vs/platform/dialogs/common/dialogs.ts | 2 +- .../driver/electron-browser/driver.ts | 2 +- .../platform/driver/electron-main/driver.ts | 2 +- src/vs/platform/editor/common/editor.ts | 10 +- .../environment/common/environment.ts | 4 +- src/vs/platform/environment/node/argv.ts | 3 +- .../environment/node/environmentService.ts | 13 +- .../common/extensionManagementUtil.ts | 21 +- .../node/extensionGalleryService.ts | 2 +- .../node/extensionManagementService.ts | 68 +- .../node/extensionsManifestCache.ts | 6 +- .../platform/extensions/common/extensions.ts | 19 - src/vs/platform/files/common/files.ts | 4 +- .../electron-main/historyMainService.ts | 20 +- .../common/abstractKeybindingService.ts | 40 +- .../common/abstractKeybindingService.test.ts | 23 +- .../launch/electron-main/launchService.ts | 6 +- src/vs/platform/lifecycle/common/lifecycle.ts | 4 +- .../lifecycle/electron-main/lifecycleMain.ts | 107 +- src/vs/platform/list/browser/listService.ts | 43 +- src/vs/platform/log/common/log.ts | 2 +- src/vs/platform/log/node/spdlogService.ts | 103 +- src/vs/platform/markers/common/markers.ts | 9 + .../notification/common/notification.ts | 40 +- .../test/common/testNotificationService.ts | 19 +- src/vs/platform/product/common/product.ts | 7 +- src/vs/platform/product/node/product.ts | 1 - .../platform/product/node/productService.ts | 10 +- src/vs/platform/progress/common/progress.ts | 45 +- src/vs/platform/registry/common/platform.ts | 14 +- .../browser/remoteAuthorityResolverService.ts | 3 - .../platform/request/node/requestService.ts | 8 +- .../severityIcon/common/severityIcon.ts | 73 + src/vs/platform/state/node/stateService.ts | 29 +- src/vs/platform/statusbar/common/statusbar.ts | 25 +- .../node/workbenchCommonProperties.ts | 23 +- src/vs/platform/theme/common/colorRegistry.ts | 12 + src/vs/platform/theme/common/styler.ts | 2 +- .../theme/electron-main/themeMainService.ts | 67 + .../electron-main/updateService.win32.ts | 4 +- src/vs/platform/windows/common/windows.ts | 6 +- .../platform/windows/electron-main/windows.ts | 1 - .../windows/electron-main/windowsService.ts | 15 +- src/vs/platform/workspace/common/workspace.ts | 6 +- src/vs/vscode.d.ts | 32 +- src/vs/vscode.proposed.d.ts | 55 +- .../extensionHost.contribution.ts | 80 +- .../api/browser/mainThreadClipboard.ts | 2 +- .../api/browser/mainThreadCodeInsets.ts | 144 + .../api/browser/mainThreadCommands.ts | 12 +- .../api/browser/mainThreadComments.ts | 48 +- .../browser/mainThreadDocumentsAndEditors.ts | 4 +- .../workbench/api/browser/mainThreadKeytar.ts | 36 +- .../api/browser/mainThreadLanguageFeatures.ts | 82 +- .../api/browser/mainThreadMessageService.ts | 3 +- .../api/browser/mainThreadProgress.ts | 6 +- .../api/browser/mainThreadSaveParticipant.ts | 10 +- .../api/browser/mainThreadStatusBar.ts | 9 +- .../api/browser/mainThreadTerminalService.ts | 5 +- .../mainThreadWebview.ts | 138 +- .../api/browser/mainThreadWorkspace.ts | 6 +- .../workbench/api/common/extHost.protocol.ts | 59 +- .../api/common/extHostApiCommands.ts | 2 +- .../workbench/api/common/extHostCodeInsets.ts | 130 + .../workbench/api/common/extHostCommands.ts | 30 + .../workbench/api/common/extHostComments.ts | 31 +- .../api/common/extHostLanguageFeatures.ts | 230 +- .../workbench/api/common/extHostStatusBar.ts | 20 +- .../api/common/extHostTypeConverters.ts | 8 +- src/vs/workbench/api/common/extHostTypes.ts | 2 + src/vs/workbench/api/common/extHostWebview.ts | 6 +- .../api/common/menusExtensionPoint.ts | 50 +- src/vs/workbench/api/node/extHost.api.impl.ts | 40 +- .../api/node/extHostExtensionService.ts | 8 +- .../api/node/extHostOutputService.ts | 2 +- src/vs/workbench/api/node/extHostSearch.ts | 11 +- src/vs/workbench/api/node/extHostTask.ts | 2 +- .../api/node/extHostTerminalService.ts | 9 +- src/vs/workbench/browser/actions.ts | 13 +- .../browser/actions/layoutActions.ts | 43 +- src/vs/workbench/browser/composite.ts | 6 +- src/vs/workbench/browser/contextkeys.ts | 24 +- src/vs/workbench/browser/labels.ts | 16 +- src/vs/workbench/browser/layout.ts | 64 +- src/vs/workbench/browser/legacyLayout.ts | 4 +- src/vs/workbench/browser/media/icons.css | 1221 +++++++ .../media/images/activitybar/debug-alt1.svg | 18 + .../images/activitybar/extensions-alt1.svg | 3 + .../media/images/activitybar/files-alt1.svg | 4 + .../media/images/activitybar/git-alt1.svg | 15 + .../media/images/activitybar/more-alt1.svg | 5 + .../media/images/activitybar/search-alt1.svg | 10 + .../images/activitybar/settings-alt1.svg | 3 + .../browser/media/images/debug/add-alt1.svg | 3 + .../images/debug/breakpoint-activate-alt1.svg | 3 + .../media/images/debug/breakpoint-alt1.svg | 3 + .../debug/breakpoint-conditional-alt1.svg | 3 + .../images/debug/breakpoint-function-alt1.svg | 3 + .../breakpoint-function-disabled-alt1.svg | 3 + .../breakpoint-function-unverified-alt1.svg | 3 + .../images/debug/breakpoint-log-alt1.svg | 3 + .../debug/breakpoint-log-unverified-alt1.svg | 3 + .../debug/breakpoint-unverified-alt1.svg | 3 + .../browser/media/images/debug/close-alt1.svg | 5 + .../media/images/debug/continue-alt1.svg | 4 + .../debug/current-and-breakpoint-alt1.svg | 4 + .../media/images/debug/current-arrow-alt1.svg | 3 + .../media/images/debug/disconnect-alt1.svg | 3 + .../browser/media/images/debug/drag-alt1.svg | 8 + .../browser/media/images/debug/gear-alt1.svg | 4 + .../browser/media/images/debug/pause-alt1.svg | 3 + .../browser/media/images/debug/repl-alt1.svg | 5 + .../media/images/debug/restart-alt1.svg | 5 + .../debug/stackframe-and-breakpoint-alt1.svg | 4 + .../images/debug/stackframe-arrow-alt1.svg | 3 + .../browser/media/images/debug/start-alt1.svg | 3 + .../media/images/debug/step-into-alt1.svg | 11 + .../media/images/debug/step-out-alt1.svg | 11 + .../media/images/debug/step-over-alt1.svg | 9 + .../browser/media/images/debug/stop-alt1.svg | 3 + .../media/images/editor/open-change-alt1.svg | 8 + .../images/editor/split-horizontal-alt1.svg | 4 + .../images/editor/split-vertical-alt1.svg | 4 + .../media/images/explorer/add-file-alt1.svg | 5 + .../media/images/explorer/add-folder-alt1.svg | 5 + .../media/images/explorer/close-alt1.svg | 5 + .../media/images/explorer/collapse-alt1.svg | 4 + .../media/images/explorer/layout-alt1.svg | 3 + .../media/images/explorer/more-alt1.svg | 5 + .../media/images/explorer/refresh-alt1.svg | 3 + .../media/images/explorer/save-alt1.svg | 6 + .../media/images/extensions/clear-alt1.svg | 7 + .../media/images/extensions/download-alt1.svg | 4 + .../media/images/extensions/gear-alt1.svg | 4 + .../images/extensions/star-empty-alt1.svg | 4 + .../images/extensions/star-full-alt1.svg | 4 + .../images/extensions/star-half-alt1.svg | 3 + .../browser/media/images/find/close-alt1.svg | 4 + .../media/images/find/collapse-alt1.svg | 3 + .../browser/media/images/find/expand-alt1.svg | 3 + .../browser/media/images/find/next-alt1.svg | 3 + .../media/images/find/previous-alt1.svg | 3 + .../media/images/find/replace-all-alt1.svg | 9 + .../media/images/find/replace-alt1.svg | 6 + .../media/images/find/selection-alt1.svg | 3 + .../browser/media/images/git/check-alt1.svg | 3 + .../browser/media/images/git/clean-alt1.svg | 3 + .../browser/media/images/git/close-alt1.svg | 4 + .../media/images/git/gotofile-alt1.svg | 5 + .../browser/media/images/git/initialze.svg | 3 + .../browser/media/images/git/next-alt1.svg | 3 + .../media/images/git/previous-alt1.svg | 3 + .../browser/media/images/git/refresh-alt1.svg | 3 + .../browser/media/images/git/stage-alt1.svg | 3 + .../browser/media/images/git/unstage-alt1.svg | 4 + .../media/images/git/whitespace-alt1.svg | 3 + .../media/images/intellisense/array-alt1.svg | 4 + .../images/intellisense/boolean-alt1.svg | 5 + .../media/images/intellisense/class-alt1.svg | 3 + .../media/images/intellisense/close-alt1.svg | 4 + .../media/images/intellisense/color-alt1.svg | 3 + .../images/intellisense/constant-alt1.svg | 3 + .../images/intellisense/enum-member-alt1.svg | 4 + .../images/intellisense/enumerator-alt1.svg | 4 + .../media/images/intellisense/event-alt1.svg | 3 + .../media/images/intellisense/field-alt1.svg | 3 + .../media/images/intellisense/file-alt1.svg | 3 + .../media/images/intellisense/folder-alt1.svg | 3 + .../media/images/intellisense/info-alt1.svg | 3 + .../images/intellisense/interface-alt1.svg | 5 + .../media/images/intellisense/key-alt1.svg | 8 + .../images/intellisense/keyword-alt1.svg | 3 + .../images/intellisense/lightbulb-alt1.svg | 3 + .../intellisense/lightbulb-autofix-alt1.svg | 4 + .../media/images/intellisense/method-alt1.svg | 3 + .../images/intellisense/namespace-alt1.svg | 4 + .../images/intellisense/numeric-alt1.svg | 6 + .../images/intellisense/operator-alt1.svg | 8 + .../images/intellisense/parameter-alt1.svg | 5 + .../images/intellisense/property-alt1.svg | 3 + .../images/intellisense/reference-alt1.svg | 5 + .../media/images/intellisense/ruler-alt1.svg | 3 + .../images/intellisense/snippet-alt1.svg | 9 + .../media/images/intellisense/string-alt1.svg | 4 + .../images/intellisense/structure-alt1.svg | 3 + .../images/intellisense/variable-alt1.svg | 4 + .../browser/media/images/misc/clear-alt1.svg | 7 + .../browser/media/images/misc/fold-alt1.svg | 3 + .../media/images/misc/gotofile-alt1.svg | 5 + .../browser/media/images/misc/json-alt1.svg | 14 + .../media/images/misc/keyboard-alt1.svg | 15 + .../browser/media/images/misc/more-alt1.svg | 5 + .../media/images/misc/precedence-alt1.svg | 7 + .../media/images/misc/preview-alt1.svg | 4 + .../media/images/misc/preview-icon-alt1.svg | 7 + .../browser/media/images/misc/split-alt1.svg | 4 + .../browser/media/images/misc/unfold-alt1.svg | 3 + .../media/images/notifications/close-alt1.svg | 4 + .../images/notifications/closeall-alt1.svg | 5 + .../images/notifications/configure-alt1.svg | 4 + .../media/images/notifications/down-alt1.svg | 10 + .../media/images/notifications/error-alt1.svg | 3 + .../media/images/notifications/info-alt1.svg | 5 + .../media/images/notifications/up-alt1.svg | 10 + .../images/notifications/warning-alt1.svg | 5 + .../browser/media/images/panel/add-alt1.svg | 3 + .../browser/media/images/panel/clear-alt1.svg | 7 + .../media/images/panel/close-all-alt1.svg | 5 + .../browser/media/images/panel/close-alt1.svg | 4 + .../media/images/panel/collapse-all-alt1.svg | 4 + .../browser/media/images/panel/down-alt1.svg | 10 + .../browser/media/images/panel/gear-alt1.svg | 4 + .../media/images/panel/gotofile-alt1.svg | 5 + .../browser/media/images/panel/kill-alt1.svg | 8 + .../media/images/panel/output-lock-alt1.svg | 3 + .../media/images/panel/output-unlock-alt1.svg | 3 + .../images/panel/split-horizontal-alt1.svg | 4 + .../images/panel/split-vertical-alt1.svg | 4 + .../browser/media/images/panel/up-alt1.svg | 10 + .../images/search/case-sensitive-alt1.svg | 4 + .../media/images/search/clear-alt1.svg | 7 + .../media/images/search/collapse-all-alt1.svg | 4 + .../media/images/search/collapse-alt1.svg | 3 + .../media/images/search/exclude-alt1.svg | 5 + .../media/images/search/expand-alt1.svg | 3 + .../browser/media/images/search/more-alt1.svg | 5 + .../media/images/search/refresh-alt1.svg | 3 + .../media/images/search/regex-alt1.svg | 4 + .../media/images/search/remove-alt1.svg | 4 + .../media/images/search/replace-all-alt1.svg | 9 + .../media/images/search/replace-alt1.svg | 6 + .../browser/media/images/search/stop-alt1.svg | 3 + .../media/images/search/whole-word-alt1.svg | 7 + .../browser/media/images/tree/close-alt1.svg | 4 + .../media/images/tree/collapse-alt1.svg | 3 + .../browser/media/images/tree/dirty-alt1.svg | 3 + .../browser/media/images/tree/expand-alt1.svg | 3 + .../parts/activitybar/activitybarPart.ts | 25 +- .../workbench/browser/parts/compositeBar.ts | 2 +- .../browser/parts/compositeBarActions.ts | 4 +- .../workbench/browser/parts/compositePart.ts | 66 +- .../browser/parts/editor/baseEditor.ts | 3 +- .../browser/parts/editor/binaryEditor.ts | 10 +- .../browser/parts/editor/breadcrumbs.ts | 2 +- .../parts/editor/breadcrumbsControl.ts | 26 +- .../parts/editor/editor.contribution.ts | 24 +- .../browser/parts/editor/editorControl.ts | 6 +- .../browser/parts/editor/editorDropTarget.ts | 8 +- .../browser/parts/editor/editorGroupView.ts | 78 +- .../browser/parts/editor/editorPart.ts | 5 +- .../browser/parts/editor/editorStatus.ts | 809 +++-- .../parts/editor/media/editorstatus.css | 19 - .../parts/editor/media/resourceviewer.css | 16 +- .../parts/editor/noTabsTitleControl.ts | 2 +- .../browser/parts/editor/resourceViewer.ts | 66 +- .../browser/parts/editor/sideBySideEditor.ts | 4 +- .../browser/parts/editor/tabsTitleControl.ts | 110 +- .../parts/editor/textResourceEditor.ts | 2 +- .../browser/parts/editor/titleControl.ts | 22 +- .../notifications/notificationsActions.ts | 4 +- .../parts/notifications/notificationsList.ts | 2 +- .../notifications/notificationsStatus.ts | 137 +- .../browser/parts/panel/panelActions.ts | 25 +- .../browser/parts/panel/panelPart.ts | 18 +- .../browser/parts/quickinput/quickInput.ts | 287 +- .../browser/parts/quickinput/quickInputBox.ts | 13 +- .../parts/quickopen/quickOpenController.ts | 73 +- .../browser/parts/sidebar/sidebarPart.ts | 2 +- .../parts/statusbar/media/statusbarpart.css | 61 +- .../browser/parts/statusbar/statusbar.ts | 35 +- .../browser/parts/statusbar/statusbarPart.ts | 679 +++- .../parts/titlebar/media/titlebarpart.css | 3 +- .../browser/parts/titlebar/menubarControl.ts | 168 +- .../browser/parts/titlebar/titlebarPart.ts | 63 +- .../browser/parts/views/customView.ts | 15 +- .../browser/parts/views/panelViewlet.ts | 22 +- .../browser/parts/views/viewsViewlet.ts | 7 +- src/vs/workbench/browser/style.ts | 1 + src/vs/workbench/browser/web.main.ts | 122 +- src/vs/workbench/browser/web.resources.ts | 98 + .../workbench/browser/web.simpleservices.ts | 456 ++- .../browser/workbench.contribution.ts | 6 + src/vs/workbench/browser/workbench.ts | 1 + src/vs/workbench/common/actions.ts | 10 +- src/vs/workbench/common/component.ts | 4 +- src/vs/workbench/common/composite.ts | 6 +- src/vs/workbench/common/editor.ts | 26 +- .../common/editor/resourceEditorInput.ts | 2 +- src/vs/workbench/common/memento.ts | 26 +- src/vs/workbench/common/notifications.ts | 97 +- src/vs/workbench/common/panel.ts | 1 + src/vs/workbench/common/theme.ts | 9 +- src/vs/workbench/common/views.ts | 16 +- .../browser/callHierarchyPeek.ts | 26 +- .../codeEditor/browser/toggleMinimap.ts | 22 +- .../browser/toggleRenderControlCharacter.ts | 22 +- .../browser/toggleRenderWhitespace.ts | 22 +- .../contrib/codeinset/common/codeInset.ts | 70 - .../codeInset.contribution.ts | 353 -- .../electron-browser/codeInsetWidget.ts | 193 - .../comments/browser/commentFormActions.ts | 34 +- .../contrib/comments/browser/commentNode.ts | 41 +- .../comments/browser/commentService.ts | 20 + .../comments/browser/commentThreadWidget.ts | 72 +- .../comments/browser/commentsTreeViewer.ts | 4 +- .../contrib/comments/browser/media/review.css | 4 +- .../contrib/comments/common/commentModel.ts | 2 +- .../contrib/debug/browser/baseDebugView.ts | 2 +- .../contrib/debug/browser/breakpointWidget.ts | 16 +- .../contrib/debug/browser/breakpointsView.ts | 9 +- .../contrib/debug/browser/callStackView.ts | 16 +- .../debug/browser/debugANSIHandling.ts | 116 +- .../debug/browser/debugActionViewItems.ts | 8 +- .../contrib/debug/browser/debugCommands.ts | 27 +- .../debug/browser/debugEditorModelManager.ts | 6 +- .../contrib/debug/browser/debugHover.ts | 4 +- .../contrib/debug/browser/debugStatus.ts | 125 +- .../contrib/debug/browser/debugToolBar.ts | 2 +- .../contrib/debug/browser/debugViewlet.ts | 15 +- .../contrib/debug/browser/exceptionWidget.ts | 6 +- .../contrib/debug/browser/linkDetector.ts | 2 +- .../debug/browser/loadedScriptsView.ts | 56 +- .../debug/browser/media/breakpointWidget.css | 3 +- .../browser/media/debug.contribution.css | 20 +- .../debug/browser/media/debugHover.css | 5 +- .../debug/browser/media/debugViewlet.css | 4 - .../contrib/debug/browser/media/repl.css | 55 - .../workbench/contrib/debug/browser/repl.ts | 53 +- .../debug/browser/statusbarColorProvider.ts | 4 +- .../contrib/debug/browser/variablesView.ts | 12 +- .../debug/browser/watchExpressionsView.ts | 14 +- .../debug/common/abstractDebugAdapter.ts | 5 +- .../workbench/contrib/debug/common/debug.ts | 7 +- .../contrib/debug/common/debugModel.ts | 24 +- .../contrib/debug/common/debugProtocol.d.ts | 108 +- .../contrib/debug/common/debugSchemas.ts | 2 +- .../contrib/debug/common/debugSource.ts | 19 +- .../electron-browser/debug.contribution.ts | 17 +- .../debugConfigurationManager.ts | 3 +- .../debug/electron-browser/debugService.ts | 24 +- .../debug/electron-browser/debugSession.ts | 21 +- .../debug/electron-browser/rawDebugSession.ts | 4 +- .../workbench/contrib/debug/node/terminals.ts | 8 +- .../test/browser/debugANSIHandling.test.ts | 3 + .../contrib/debug/test/common/mockDebug.ts | 2 +- .../test/electron-browser/debugModel.test.ts | 5 +- .../electron-browser/experimentalPrompt.ts | 11 +- .../experiments/node/experimentService.ts | 4 +- .../extensions/browser/extensionsViewer.ts | 2 +- .../extensions/common/extensionsUtils.ts | 23 +- .../electron-browser/extensionEditor.ts | 13 +- .../extensionProfileService.ts | 137 +- .../electron-browser/extensionTipsService.ts | 425 +-- .../extensions.contribution.ts | 32 + .../electron-browser/extensionsActions.ts | 207 +- .../extensionsActivationProgress.ts | 4 +- .../electron-browser/extensionsList.ts | 6 +- .../electron-browser/extensionsViewlet.ts | 7 +- .../electron-browser/extensionsViews.ts | 132 +- .../media/extensionActions.css | 16 - .../media/extensionsViewlet.css | 9 +- .../media/runtimeExtensionsEditor.css | 17 - .../media/status-info-inverse.svg | 1 - .../electron-browser/media/status-info.svg | 1 - .../media/status-warning-inverse.svg | 1 - .../electron-browser/media/status-warning.svg | 1 - .../node/extensionsWorkbenchService.ts | 43 +- .../electron-browser/extensionsViews.test.ts | 2 +- .../extensionsWorkbenchService.test.ts | 6 +- .../electron-browser/feedback.contribution.ts | 20 +- .../feedback/electron-browser/feedback.ts | 12 +- .../electron-browser/feedbackStatusbarItem.ts | 65 +- .../electron-browser/media/feedback.css | 11 +- .../files/browser/editors/binaryFileEditor.ts | 6 +- .../contrib/files/browser/explorerViewlet.ts | 20 +- .../contrib/files/browser/fileActions.ts | 80 +- .../files/browser/files.contribution.ts | 2 +- .../contrib/files/browser/saveErrorHandler.ts | 38 +- .../contrib/files/browser/views/emptyView.ts | 9 +- .../files/browser/views/explorerView.ts | 85 +- .../files/browser/views/explorerViewer.ts | 32 +- .../files/browser/views/openEditorsView.ts | 38 +- .../contrib/files/common/explorerModel.ts | 24 +- .../contrib/files/common/explorerService.ts | 20 +- .../format/browser/formatActionsMultiple.ts | 4 +- .../contrib/logs/common/logs.contribution.ts | 39 +- .../markers/browser/markers.contribution.ts | 89 +- .../contrib/markers/browser/markersPanel.ts | 21 +- .../markers/browser/markersTreeViewer.ts | 23 +- .../contrib/markers/browser/media/markers.css | 25 - .../browser/media/status-error-inverse.svg | 1 - .../markers/browser/media/status-error.svg | 1 - .../browser/media/status-info-inverse.svg | 1 - .../markers/browser/media/status-info.svg | 1 - .../browser/media/status-warning-inverse.svg | 1 - .../markers/browser/media/status-warning.svg | 1 - .../contrib/markers/browser/messages.ts | 2 +- .../contrib/outline/browser/outlinePanel.ts | 21 +- .../contrib/output/browser/outputActions.ts | 8 +- .../contrib/output/browser/outputServices.ts | 2 +- .../electron-browser/perfviewEditor.ts | 69 +- .../preferences/browser/keybindingsEditor.ts | 6 +- .../browser/keybindingsEditorContribution.ts | 9 +- .../media/check-inverse.svg | 0 .../media/check.svg | 0 .../media/configure-inverse.svg | 0 .../media/configure.svg | 0 .../media/edit-json-inverse.svg | 0 .../media/edit-json.svg | 0 .../preferences/browser/media/info.svg | 1 - .../preferences/browser/media/keybindings.css | 2 - .../media/preferences-editor-inverse.svg | 0 .../media/preferences-editor.svg | 0 .../preferences/browser/media/preferences.css | 9 - .../media/settingsEditor2.css | 0 .../browser/media/status-error.svg | 1 - .../preferences.contribution.ts | 34 +- .../preferences/browser/preferencesActions.ts | 8 +- .../preferences/browser/preferencesEditor.ts | 15 +- .../browser/preferencesRenderers.ts | 22 +- .../preferences/browser/preferencesSearch.ts | 219 ++ .../preferences/browser/preferencesWidgets.ts | 6 +- .../settingsEditor2.ts | 12 +- .../preferences/browser/settingsTree.ts | 45 +- .../preferences/browser/settingsTreeModels.ts | 13 +- .../preferences/browser/settingsWidgets.ts | 2 +- .../contrib/preferences/browser/tocTree.ts | 9 +- .../common/preferencesContribution.ts | 10 +- .../electron-browser/preferencesSearch.ts | 146 +- .../quickopen/browser/commandsHandler.ts | 39 +- .../quickopen/browser/gotoSymbolHandler.ts | 10 +- .../electron-browser/remote.contribution.ts | 6 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 102 +- .../contrib/scm/browser/scmActivity.ts | 23 +- .../contrib/scm/browser/scmViewlet.ts | 115 +- .../contrib/search/browser/openFileHandler.ts | 5 +- .../search/browser/openSymbolHandler.ts | 43 +- .../contrib/search/browser/searchView.ts | 38 +- .../contrib/search/common/queryBuilder.ts | 2 +- .../workbench/contrib/search/common/search.ts | 19 +- .../snippets/browser/snippets.contribution.ts | 6 +- .../snippets/browser/snippetsService.ts | 17 +- .../partsSplash.contribution.ts | 19 +- .../contrib/stats/node/workspaceStats.ts | 2 +- .../contrib/tasks/browser/quickOpen.ts | 2 +- .../contrib/tasks/common/jsonSchema_v2.ts | 17 +- .../tasks/common/media/status-error.svg | 1 - .../tasks/common/media/status-info.svg | 1 - .../tasks/common/media/status-warning.svg | 1 - .../tasks/common/media/task.contribution.css | 74 - .../contrib/tasks/common/media/task.svg | 1 - .../contrib/tasks/common/problemCollectors.ts | 59 +- .../contrib/tasks/common/problemMatcher.ts | 59 +- .../contrib/tasks/common/taskConfiguration.ts | 23 +- .../contrib/tasks/common/taskSystem.ts | 2 +- .../workbench/contrib/tasks/common/tasks.ts | 18 +- .../electron-browser/task.contribution.ts | 317 +- .../electron-browser/terminalTaskSystem.ts | 33 +- .../contrib/terminal/browser/media/xterm.css | 2 +- .../terminal/browser/terminal.contribution.ts | 10 +- .../contrib/terminal/browser/terminal.ts | 9 +- .../terminal/browser/terminalActions.ts | 10 +- .../browser/terminalCommandTracker.ts | 2 +- .../terminal/browser/terminalConfigHelper.ts | 2 +- .../terminal/browser/terminalInstance.ts | 76 +- .../terminal/browser/terminalLinkHandler.ts | 57 +- .../browser/terminalProcessManager.ts | 8 - .../terminal/browser/terminalService.ts | 28 + .../contrib/terminal/browser/terminalTab.ts | 19 +- .../browser/terminalTypeAheadAddon.ts | 136 - .../contrib/terminal/common/terminal.ts | 15 +- .../terminal/common/terminalColorRegistry.ts | 2 +- .../common/terminalProcessExtHostProxy.ts | 55 +- .../terminal/common/terminalService.ts | 31 +- .../terminal/common/terminalShellConfig.ts | 45 + .../electron-browser/terminal.contribution.ts | 31 +- .../terminalInstanceService.ts | 39 +- .../electron-browser/terminalService.ts | 5 - .../contrib/terminal/node/terminalProcess.ts | 12 +- .../terminal/node/windowsShellHelper.ts | 2 +- .../terminalCommandTracker.test.ts | 2 +- .../terminalLinkHandler.test.ts | 41 +- .../test/node/terminalEnvironment.test.ts | 4 +- .../electron-browser/update.contribution.ts | 9 +- .../contrib/update/electron-browser/update.ts | 59 +- .../contrib/watermark/browser/watermark.ts | 18 +- .../contrib/webview/browser/webviewEditor.ts | 14 +- .../webview/browser/webviewEditorInput.ts | 2 +- .../contrib/webview/browser/webviewService.ts | 15 + .../contrib/webview/common/webview.ts | 5 +- .../electron-browser/webviewElement.ts | 72 +- .../welcome/overlay/browser/welcomeOverlay.ts | 14 +- .../welcome/page/browser/welcomePage.ts | 17 +- .../walkThrough/browser/walkThroughPart.ts | 18 +- .../walkThrough/common/walkThroughInput.ts | 6 +- .../electron-browser/main.contribution.ts | 5 +- src/vs/workbench/electron-browser/main.ts | 10 +- src/vs/workbench/electron-browser/window.ts | 44 +- .../services/backup/node/backupFileService.ts | 2 +- .../services/broadcast/common/broadcast.ts | 30 - .../electron-browser/broadcastService.ts | 51 - .../configuration/browser/configuration.ts | 14 +- .../browser/configurationCache.ts | 22 + .../common/configurationEditingService.ts | 2 +- .../common/jsonEditingService.ts | 39 +- .../configurationEditingService.test.ts | 6 +- .../configurationService.test.ts | 45 +- .../electron-browser/contextmenuService.ts | 2 +- .../decorations/browser/decorationsService.ts | 10 +- .../dialogs/browser/fileDialogService.ts | 98 +- .../dialogs/browser/remoteFileDialog.ts | 89 +- .../services/editor/browser/editorService.ts | 13 +- .../node/extensionEnablementService.ts | 6 +- .../extensionEnablementService.test.ts | 5 +- .../extensions/browser/extensionService.ts | 101 + .../common/abstractExtensionService.ts | 498 +++ .../common/extensionHostProcessManager.ts | 5 +- .../common/extensionHostProtocol.ts | 1 + .../extensions/common/extensionPoints.ts | 63 + .../services/extensions/common/extensions.ts | 3 +- .../extensions/common/extensionsRegistry.ts | 13 + .../{node => common}/extensionsUtil.ts | 6 +- .../common/inactiveExtensionUrlHandler.ts | 6 +- .../remoteExtensionHostClient.ts | 46 +- .../cachedExtensionScanner.ts | 26 +- .../electron-browser/extensionHost.ts | 39 +- .../electron-browser/extensionService.ts | 630 +--- .../extensions/node/extensionHostMain.ts | 8 +- .../extensions/node/extensionHostProcess.ts | 7 +- .../node/extensionHostProcessSetup.ts | 99 +- .../extensions/node/extensionPoints.ts | 39 +- .../node/multiExtensionManagement.ts | 14 +- .../services/extensions/node/proxyResolver.ts | 13 +- .../services/files/common/fileService.ts | 18 +- .../files/test/node/diskFileService.test.ts | 53 +- .../electron-browser/keybindingService.ts | 23 +- .../keybindingEditing.test.ts | 3 +- .../services/layout/browser/layoutService.ts | 15 + .../common/notificationService.ts | 42 +- .../output/common/outputChannelModel.ts | 18 +- .../preferences/browser/preferencesService.ts | 4 +- .../progress/browser/localProgressService.ts | 306 ++ ...ogressService2.css => progressService.css} | 4 +- .../progress/browser/progressService.ts | 499 +-- .../progress/browser/progressService2.ts | 344 -- ...e.test.ts => localProgressService.test.ts} | 2 +- .../services/search/node/searchService.ts | 6 +- .../electron-browser/telemetryService.ts | 2 +- .../browser/abstractTextMateService.ts | 519 +++ .../textMate/browser/textMateService.ts | 66 + .../cgmanifest.json | 0 .../electron-browser/textMateService.ts | 506 +-- .../textfile/common/textFileEditorModel.ts | 67 +- .../textfile/common/textFileService.ts | 6 +- .../services/textfile/common/textfiles.ts | 2 +- .../textfile/test/textFileService.io.test.ts | 10 +- .../common/textModelResolverService.ts | 4 +- .../themes/browser/workbenchThemeService.ts | 11 +- .../services/themes/common/colorThemeData.ts | 2 +- .../themes/common/fileIconThemeData.ts | 2 +- .../themes/common/fileIconThemeStore.ts | 10 +- .../timer/electron-browser/timerService.ts | 34 +- .../services/viewlet/browser/viewlet.ts | 4 +- .../workspaceEditingService.ts | 146 +- .../test/common/notifications.test.ts | 94 +- .../api/extHostLanguageFeatures.test.ts | 15 +- .../api/extHostMessagerService.test.ts | 15 +- .../api/extHostSearch.test.ts | 10 +- .../electron-browser/api/extHostTypes.test.ts | 2 + .../api/extHostWebview.test.ts | 2 +- .../api/mainThreadEditors.test.ts | 2 +- .../workbench/test/workbenchTestServices.ts | 3 + src/vs/workbench/workbench.main.ts | 22 +- src/vs/workbench/workbench.web.main.ts | 24 +- test/smoke/README.md | 5 +- test/smoke/package.json | 4 +- test/smoke/src/application.ts | 10 +- test/smoke/src/areas/editor/peek.ts | 2 +- .../src/areas/extensions/extensions.test.ts | 4 + .../src/areas/multiroot/multiroot.test.ts | 3 +- test/smoke/src/areas/statusbar/statusbar.ts | 2 +- test/smoke/src/vscode/code.ts | 8 +- test/smoke/yarn.lock | 26 +- yarn.lock | 144 +- 926 files changed, 19436 insertions(+), 11290 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md delete mode 100644 build/azure-pipelines/common/installDistro.ts create mode 100644 build/azure-pipelines/common/installDistroDependencies.ts create mode 100755 build/azure-pipelines/linux/build-arm.sh create mode 100755 build/azure-pipelines/linux/prebuild-arm.sh create mode 100644 build/azure-pipelines/linux/product-build-linux-arm.yml create mode 100755 build/azure-pipelines/linux/publish-arm.sh create mode 100644 extensions/css-language-features/server/src/test/links.test.ts create mode 100644 extensions/css-language-features/server/test/linksTestFixtures/.gitignore create mode 100644 extensions/css-language-features/server/test/linksTestFixtures/node_modules/foo/package.json create mode 100644 extensions/git-ui/.vscodeignore create mode 100644 extensions/git-ui/README.md create mode 100644 extensions/git-ui/cgmanifest.json create mode 100644 extensions/git-ui/extension.webpack.config.js create mode 100644 extensions/git-ui/package.json create mode 100644 extensions/git-ui/package.nls.json create mode 100644 extensions/git-ui/resources/icons/git.png create mode 100644 extensions/git-ui/src/main.ts create mode 100644 extensions/git-ui/src/typings/refs.d.ts create mode 100644 extensions/git-ui/tsconfig.json create mode 100644 extensions/git-ui/yarn.lock create mode 100644 extensions/objective-c/build/update-grammars.js create mode 100644 extensions/objective-c/test/colorize-fixtures/test.mm create mode 100644 extensions/objective-c/test/colorize-results/test_mm.json delete mode 100755 remote/installDevModules.sh delete mode 100755 remote/installDevPackagesAsRoot.sh delete mode 100755 remote/launchDevMode.sh delete mode 100644 scripts/code-web.js create mode 100644 src/sql/workbench/api/browser/extensionHost.contribution.common.ts rename src/sql/workbench/api/{node => browser}/mainThreadAccountManagement.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadBackgroundTaskManagement.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadConnectionManagement.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadCredentialManagement.ts (100%) rename src/sql/workbench/api/{electron-browser => browser}/mainThreadDashboard.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadDashboardWebview.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadDataProtocol.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadExtensionManagement.ts (100%) rename src/sql/workbench/api/{electron-browser => browser}/mainThreadModalDialog.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadModelView.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadModelViewDialog.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadNotebook.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadNotebookDocumentsAndEditors.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadObjectExplorer.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadQueryEditor.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadResourceProvider.ts (100%) rename src/sql/workbench/api/{node => browser}/mainThreadSerializationProvider.ts (100%) rename src/sql/workbench/api/{electron-browser => browser}/mainThreadTasks.ts (100%) rename src/{vs/code/code.main.ts => sql/workbench/api/electron-browser/extensionHost.contribution.ts} (85%) delete mode 100644 src/sql/workbench/api/node/sqlExtHost.contribution.ts create mode 100644 src/typings/onigasm-umd.d.ts rename src/{vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css => typings/vscode-windows-ca-certs.d.ts} (88%) rename src/{sql/workbench/api/electron-browser/sqlExtensionHost.contribution.ts => typings/vsda.d.ts} (82%) create mode 100644 src/typings/xterm-addon-search.d.ts create mode 100644 src/typings/xterm-addon-web-links.d.ts rename src/typings/{vscode-xterm.d.ts => xterm.d.ts} (91%) delete mode 100644 src/vs/base/browser/hash.ts delete mode 100644 src/vs/base/test/browser/hash.test.ts delete mode 100644 src/vs/code/electron-main/keyboard.ts delete mode 100644 src/vs/code/electron-main/theme.ts delete mode 100644 src/vs/editor/contrib/gotoError/media/status-error-inverse.svg delete mode 100644 src/vs/editor/contrib/gotoError/media/status-error.svg delete mode 100644 src/vs/editor/contrib/gotoError/media/status-info-inverse.svg delete mode 100644 src/vs/editor/contrib/gotoError/media/status-info.svg delete mode 100644 src/vs/editor/contrib/gotoError/media/status-warning-inverse.svg delete mode 100644 src/vs/editor/contrib/gotoError/media/status-warning.svg delete mode 100644 src/vs/platform/configuration/node/configuration.ts create mode 100644 src/vs/platform/credentials/common/credentials.ts create mode 100644 src/vs/platform/credentials/node/credentialsService.ts create mode 100644 src/vs/platform/severityIcon/common/severityIcon.ts create mode 100644 src/vs/platform/theme/electron-main/themeMainService.ts rename src/vs/workbench/api/{electron-browser => browser}/extensionHost.contribution.ts (51%) create mode 100644 src/vs/workbench/api/browser/mainThreadCodeInsets.ts rename src/vs/workbench/api/{electron-browser => browser}/mainThreadWebview.ts (75%) create mode 100644 src/vs/workbench/api/common/extHostCodeInsets.ts create mode 100644 src/vs/workbench/browser/media/icons.css create mode 100644 src/vs/workbench/browser/media/images/activitybar/debug-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/activitybar/extensions-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/activitybar/files-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/activitybar/git-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/activitybar/more-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/activitybar/search-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/activitybar/settings-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/add-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/breakpoint-activate-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/breakpoint-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/breakpoint-conditional-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/breakpoint-function-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/breakpoint-function-disabled-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/breakpoint-function-unverified-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/breakpoint-log-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/breakpoint-log-unverified-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/breakpoint-unverified-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/close-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/continue-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/current-and-breakpoint-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/current-arrow-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/disconnect-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/drag-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/gear-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/pause-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/repl-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/restart-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/stackframe-and-breakpoint-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/stackframe-arrow-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/start-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/step-into-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/step-out-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/step-over-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/debug/stop-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/editor/open-change-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/editor/split-horizontal-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/editor/split-vertical-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/explorer/add-file-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/explorer/add-folder-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/explorer/close-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/explorer/collapse-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/explorer/layout-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/explorer/more-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/explorer/refresh-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/explorer/save-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/extensions/clear-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/extensions/download-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/extensions/gear-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/extensions/star-empty-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/extensions/star-full-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/extensions/star-half-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/find/close-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/find/collapse-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/find/expand-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/find/next-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/find/previous-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/find/replace-all-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/find/replace-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/find/selection-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/git/check-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/git/clean-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/git/close-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/git/gotofile-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/git/initialze.svg create mode 100644 src/vs/workbench/browser/media/images/git/next-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/git/previous-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/git/refresh-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/git/stage-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/git/unstage-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/git/whitespace-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/array-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/boolean-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/class-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/close-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/color-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/constant-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/enum-member-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/enumerator-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/event-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/field-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/file-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/folder-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/info-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/interface-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/key-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/keyword-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/lightbulb-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/lightbulb-autofix-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/method-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/namespace-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/numeric-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/operator-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/parameter-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/property-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/reference-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/ruler-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/snippet-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/string-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/structure-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/intellisense/variable-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/clear-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/fold-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/gotofile-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/json-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/keyboard-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/more-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/precedence-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/preview-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/preview-icon-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/split-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/misc/unfold-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/notifications/close-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/notifications/closeall-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/notifications/configure-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/notifications/down-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/notifications/error-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/notifications/info-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/notifications/up-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/notifications/warning-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/add-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/clear-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/close-all-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/close-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/collapse-all-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/down-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/gear-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/gotofile-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/kill-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/output-lock-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/output-unlock-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/split-horizontal-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/split-vertical-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/panel/up-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/case-sensitive-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/clear-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/collapse-all-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/collapse-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/exclude-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/expand-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/more-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/refresh-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/regex-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/remove-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/replace-all-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/replace-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/stop-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/search/whole-word-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/tree/close-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/tree/collapse-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/tree/dirty-alt1.svg create mode 100644 src/vs/workbench/browser/media/images/tree/expand-alt1.svg create mode 100644 src/vs/workbench/browser/web.resources.ts delete mode 100644 src/vs/workbench/contrib/codeinset/common/codeInset.ts delete mode 100644 src/vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution.ts delete mode 100644 src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts delete mode 100644 src/vs/workbench/contrib/extensions/electron-browser/media/status-info-inverse.svg delete mode 100644 src/vs/workbench/contrib/extensions/electron-browser/media/status-info.svg delete mode 100644 src/vs/workbench/contrib/extensions/electron-browser/media/status-warning-inverse.svg delete mode 100644 src/vs/workbench/contrib/extensions/electron-browser/media/status-warning.svg delete mode 100644 src/vs/workbench/contrib/markers/browser/media/status-error-inverse.svg delete mode 100644 src/vs/workbench/contrib/markers/browser/media/status-error.svg delete mode 100644 src/vs/workbench/contrib/markers/browser/media/status-info-inverse.svg delete mode 100644 src/vs/workbench/contrib/markers/browser/media/status-info.svg delete mode 100644 src/vs/workbench/contrib/markers/browser/media/status-warning-inverse.svg delete mode 100644 src/vs/workbench/contrib/markers/browser/media/status-warning.svg rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/media/check-inverse.svg (100%) rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/media/check.svg (100%) rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/media/configure-inverse.svg (100%) rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/media/configure.svg (100%) rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/media/edit-json-inverse.svg (100%) rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/media/edit-json.svg (100%) delete mode 100644 src/vs/workbench/contrib/preferences/browser/media/info.svg rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/media/preferences-editor-inverse.svg (100%) rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/media/preferences-editor.svg (100%) rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/media/settingsEditor2.css (100%) delete mode 100644 src/vs/workbench/contrib/preferences/browser/media/status-error.svg rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/preferences.contribution.ts (93%) create mode 100644 src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts rename src/vs/workbench/contrib/preferences/{electron-browser => browser}/settingsEditor2.ts (99%) delete mode 100644 src/vs/workbench/contrib/tasks/common/media/status-error.svg delete mode 100644 src/vs/workbench/contrib/tasks/common/media/status-info.svg delete mode 100644 src/vs/workbench/contrib/tasks/common/media/status-warning.svg delete mode 100644 src/vs/workbench/contrib/tasks/common/media/task.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts create mode 100644 src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts create mode 100644 src/vs/workbench/contrib/webview/browser/webviewService.ts delete mode 100644 src/vs/workbench/services/broadcast/common/broadcast.ts delete mode 100644 src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts create mode 100644 src/vs/workbench/services/configuration/browser/configurationCache.ts create mode 100644 src/vs/workbench/services/extensions/browser/extensionService.ts create mode 100644 src/vs/workbench/services/extensions/common/abstractExtensionService.ts create mode 100644 src/vs/workbench/services/extensions/common/extensionPoints.ts rename src/vs/workbench/services/extensions/{node => common}/extensionsUtil.ts (86%) rename src/vs/workbench/services/extensions/{electron-browser => common}/remoteExtensionHostClient.ts (86%) create mode 100644 src/vs/workbench/services/progress/browser/localProgressService.ts rename src/vs/workbench/services/progress/browser/media/{progressService2.css => progressService.css} (85%) delete mode 100644 src/vs/workbench/services/progress/browser/progressService2.ts rename src/vs/workbench/services/progress/test/{progressService.test.ts => localProgressService.test.ts} (99%) create mode 100644 src/vs/workbench/services/textMate/browser/abstractTextMateService.ts create mode 100644 src/vs/workbench/services/textMate/browser/textMateService.ts rename src/vs/workbench/services/textMate/{electron-browser => common}/cgmanifest.json (100%) diff --git a/.gitignore b/.gitignore index 3ffb11ed188e..f9239568455d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ out-vscode-reh/ out-vscode-reh-min/ out-vscode-reh-pkg/ **/node_modules +src/vs/server +resources/server +build/node_modules coverage/ test_data/ test-results/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 4ae937594228..bc148b2186d9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -218,4 +218,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/.yarnrc b/.yarnrc index beab9f70b955..7e63643de04a 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "3.1.8" +target "4.2.3" runtime "electron" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 65c8a42b8d25..000000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1 +0,0 @@ -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/build/.nativeignore b/build/.nativeignore index a04b4dbc23e3..17ced53438cb 100644 --- a/build/.nativeignore +++ b/build/.nativeignore @@ -1,5 +1,8 @@ # cleanup rules for native node modules, .gitignore style +nan/** +*/node_modules/nan/** + fsevents/binding.gyp fsevents/fsevents.cc fsevents/build/** @@ -83,6 +86,7 @@ node-pty/binding.gyp node-pty/build/** node-pty/src/** node-pty/tools/** +node-pty/deps/** !node-pty/build/Release/*.exe !node-pty/build/Release/*.dll !node-pty/build/Release/*.node diff --git a/build/azure-pipelines/common/installDistro.ts b/build/azure-pipelines/common/installDistro.ts deleted file mode 100644 index ca3795285b03..000000000000 --- a/build/azure-pipelines/common/installDistro.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as cp from 'child_process'; -import * as path from 'path'; - -function yarnInstall(packageName: string): void { - cp.execSync(`yarn add --no-lockfile ${packageName}`); - cp.execSync(`yarn add --no-lockfile ${packageName}`, { cwd: path.join( process.cwd(), 'remote') }); -} - -const product = require('../../../product.json'); -const dependencies = product.dependencies || {} as { [name: string]: string; }; - -Object.keys(dependencies).forEach(name => { - const url = dependencies[name]; - yarnInstall(url); -}); \ No newline at end of file diff --git a/build/azure-pipelines/common/installDistroDependencies.ts b/build/azure-pipelines/common/installDistroDependencies.ts new file mode 100644 index 000000000000..c7284708c66b --- /dev/null +++ b/build/azure-pipelines/common/installDistroDependencies.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as cp from 'child_process'; +import * as path from 'path'; +import * as fs from 'fs'; + +function yarnInstall(packageName: string, cwd: string): void { + console.log(`yarn add --no-lockfile ${packageName}`, cwd); + cp.execSync(`yarn add --no-lockfile ${packageName}`, { cwd, stdio: 'inherit' }); +} + +/** + * Install additional dependencies listed on each quality `package.json` file. + */ +function main() { + const quality = process.env['VSCODE_QUALITY']; + + if (!quality) { + throw new Error('Missing VSCODE_QUALITY, can\'t install distro'); + } + + const rootPath = path.dirname(path.dirname(path.dirname(__dirname))); + const qualityPath = path.join(rootPath, 'quality', quality); + const packagePath = path.join(qualityPath, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + const dependencies = pkg.dependencies || {} as { [name: string]: string; }; + + Object.keys(dependencies).forEach(name => { + const url = dependencies[name]; + const cwd = process.argv.length < 3 ? process.cwd() : path.join(process.cwd(), process.argv[2]); + yarnInstall(url, cwd); + }); +} + +main(); \ No newline at end of file diff --git a/build/azure-pipelines/common/symbols.ts b/build/azure-pipelines/common/symbols.ts index a334ee119580..69b8f4be39dd 100644 --- a/build/azure-pipelines/common/symbols.ts +++ b/build/azure-pipelines/common/symbols.ts @@ -36,7 +36,6 @@ export interface IVersionAccessor extends IApplicationAccessor { enum Platform { WIN_32 = 'win32-ia32', WIN_64 = 'win32-x64', - LINUX_32 = 'linux-ia32', LINUX_64 = 'linux-x64', MAC_OS = 'darwin-x64' } @@ -191,7 +190,7 @@ if (process.platform === 'darwin') { } else if (process.platform === 'win32') { platform = is64 ? Platform.WIN_64 : Platform.WIN_32; } else { - platform = is64 ? Platform.LINUX_64 : Platform.LINUX_32; + platform = Platform.LINUX_64; } // Create version and upload symbols in HockeyApp diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 5d6ec8c2cff0..f136f2b44961 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -34,7 +34,8 @@ steps: yarn gulp mixin yarn gulp hygiene yarn monaco-compile-check - node build/azure-pipelines/common/installDistro.js + node build/azure-pipelines/common/installDistroDependencies.js + node build/azure-pipelines/common/installDistroDependencies.js remote node build/lib/builtInExtensions.js displayName: Prepare build diff --git a/build/azure-pipelines/linux/build-arm.sh b/build/azure-pipelines/linux/build-arm.sh new file mode 100755 index 000000000000..4f01bc9a7e5d --- /dev/null +++ b/build/azure-pipelines/linux/build-arm.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/prebuild-arm.sh b/build/azure-pipelines/linux/prebuild-arm.sh new file mode 100755 index 000000000000..4f01bc9a7e5d --- /dev/null +++ b/build/azure-pipelines/linux/prebuild-arm.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/product-build-linux-arm.yml b/build/azure-pipelines/linux/product-build-linux-arm.yml new file mode 100644 index 000000000000..55350cce2953 --- /dev/null +++ b/build/azure-pipelines/linux/product-build-linux-arm.yml @@ -0,0 +1,65 @@ +steps: +- task: NodeTool@0 + inputs: + versionSpec: "10.15.1" + +- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.10.1" + +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + +- task: Docker@1 + displayName: 'Pull image' + inputs: + azureSubscriptionEndpoint: 'vscode-builds-subscription' + azureContainerRegistry: vscodehub.azurecr.io + command: 'Run an image' + imageName: 'vscode-linux-build-agent:armhf' + containerCommand: uname + +- script: | + set -e + + cat << EOF > ~/.netrc + machine monacotools.visualstudio.com + password $(devops-pat) + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF + + git config user.email "vscode@microsoft.com" + git config user.name "VSCode" + git remote add distro "https://github.com/$(VSCODE_MIXIN_REPO).git" + git fetch distro + git merge $(node -p "require('./package.json').distro") + + CHILD_CONCURRENCY=1 yarn + yarn gulp mixin + yarn gulp hygiene + yarn monaco-compile-check + ./build/azure-pipelines/linux/prebuild-arm.sh + displayName: Prepare build + +- script: | + set -e + ./build/azure-pipelines/linux/build-arm.sh + displayName: Build + +- script: | + set -e + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + VSCODE_HOCKEYAPP_TOKEN="$(vscode-hockeyapp-token)" \ + ./build/azure-pipelines/linux/publish-arm.sh + displayName: Publish + +- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: 'Component Detection' + continueOnError: true \ No newline at end of file diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index f727e30d252b..c1386b93f71f 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -16,9 +16,6 @@ steps: - script: | set -e export npm_config_arch="$(VSCODE_ARCH)" - if [[ "$(VSCODE_ARCH)" == "ia32" ]]; then - export PKG_CONFIG_PATH="/usr/lib/i386-linux-gnu/pkgconfig" - fi cat << EOF > ~/.netrc machine monacotools.visualstudio.com @@ -38,7 +35,8 @@ steps: yarn gulp mixin yarn gulp hygiene yarn monaco-compile-check - node build/azure-pipelines/common/installDistro.js + node build/azure-pipelines/common/installDistroDependencies.js + node build/azure-pipelines/common/installDistroDependencies.js remote node build/lib/builtInExtensions.js displayName: Prepare build diff --git a/build/azure-pipelines/linux/publish-arm.sh b/build/azure-pipelines/linux/publish-arm.sh new file mode 100755 index 000000000000..4f01bc9a7e5d --- /dev/null +++ b/build/azure-pipelines/linux/publish-arm.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -e +echo 'noop' \ No newline at end of file diff --git a/build/azure-pipelines/linux/publish.sh b/build/azure-pipelines/linux/publish.sh index 0f864600bd31..a6b48389d58a 100755 --- a/build/azure-pipelines/linux/publish.sh +++ b/build/azure-pipelines/linux/publish.sh @@ -5,8 +5,6 @@ ROOT="$REPO/.." # Publish tarball PLATFORM_LINUX="linux-$VSCODE_ARCH" -[[ "$VSCODE_ARCH" == "ia32" ]] && DEB_ARCH="i386" || DEB_ARCH="amd64" -[[ "$VSCODE_ARCH" == "ia32" ]] && RPM_ARCH="i386" || RPM_ARCH="x86_64" BUILDNAME="VSCode-$PLATFORM_LINUX" BUILD="$ROOT/$BUILDNAME" BUILD_VERSION="$(date +%s)" @@ -39,7 +37,7 @@ node build/azure-pipelines/common/symbols.js "$VSCODE_MIXIN_PASSWORD" "$VSCODE_H # Publish DEB yarn gulp "vscode-linux-$VSCODE_ARCH-build-deb" PLATFORM_DEB="linux-deb-$VSCODE_ARCH" -[[ "$VSCODE_ARCH" == "ia32" ]] && DEB_ARCH="i386" || DEB_ARCH="amd64" +DEB_ARCH="amd64" DEB_FILENAME="$(ls $REPO/.build/linux/deb/$DEB_ARCH/deb/)" DEB_PATH="$REPO/.build/linux/deb/$DEB_ARCH/deb/$DEB_FILENAME" @@ -48,7 +46,7 @@ node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "$PLATFORM_DEB" p # Publish RPM yarn gulp "vscode-linux-$VSCODE_ARCH-build-rpm" PLATFORM_RPM="linux-rpm-$VSCODE_ARCH" -[[ "$VSCODE_ARCH" == "ia32" ]] && RPM_ARCH="i386" || RPM_ARCH="x86_64" +RPM_ARCH="x86_64" RPM_FILENAME="$(ls $REPO/.build/linux/rpm/$RPM_ARCH/ | grep .rpm)" RPM_PATH="$REPO/.build/linux/rpm/$RPM_ARCH/$RPM_FILENAME" diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index c8bedfbffc09..ac9ce03b593e 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -1,11 +1,8 @@ resources: containers: - container: vscode-x64 - endpoint: VSCodeHub image: vscodehub.azurecr.io/vscode-linux-build-agent:x64 - - container: vscode-ia32 endpoint: VSCodeHub - image: vscodehub.azurecr.io/vscode-linux-build-agent:ia32 - container: snapcraft image: snapcore/snapcraft @@ -49,15 +46,14 @@ jobs: steps: - template: linux/snap-build-linux.yml -- job: Linux32 - condition: eq(variables['VSCODE_BUILD_LINUX_32BIT'], 'true') +- job: LinuxArmhf + condition: eq(variables['VSCODE_BUILD_LINUX_ARMHF'], 'true') pool: vmImage: 'Ubuntu-16.04' variables: - VSCODE_ARCH: ia32 - container: vscode-ia32 + VSCODE_ARCH: armhf steps: - - template: linux/product-build-linux.yml + - template: linux/product-build-linux-arm.yml - job: macOS condition: eq(variables['VSCODE_BUILD_MACOS'], 'true') @@ -75,7 +71,7 @@ jobs: - Windows32 - Linux - LinuxSnap - - Linux32 + - LinuxArmhf - macOS steps: - template: sync-mooncake.yml \ No newline at end of file diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 8a6a6abb6580..339bdde1b9cc 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -35,7 +35,8 @@ steps: exec { yarn gulp mixin } exec { yarn gulp hygiene } exec { yarn monaco-compile-check } - exec { node build/azure-pipelines/common/installDistro.js } + exec { node build/azure-pipelines/common/installDistroDependencies.js } + exec { node build/azure-pipelines/common/installDistroDependencies.js remote } exec { node build/lib/builtInExtensions.js } displayName: Prepare build diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index fe5cbcfc567a..bfe3f0f6fdd1 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -7,10 +7,111 @@ const gulp = require('gulp'); +const path = require('path'); +const es = require('event-stream'); +const util = require('./lib/util'); +const task = require('./lib/task'); + +const vfs = require('vinyl-fs'); +const flatmap = require('gulp-flatmap'); +const gunzip = require('gulp-gunzip'); +const untar = require('gulp-untar'); +const File = require('vinyl'); +const fs = require('fs'); + +const REPO_ROOT = path.dirname(__dirname); + const noop = () => { return Promise.resolve(); }; gulp.task('vscode-reh-win32-ia32-min', noop); gulp.task('vscode-reh-win32-x64-min', noop); gulp.task('vscode-reh-darwin-min', noop); gulp.task('vscode-reh-linux-x64-min', noop); -gulp.task('vscode-reh-linux-arm-min', noop); +gulp.task('vscode-reh-linux-armhf-min', noop); + + +function getNodeVersion() { + const yarnrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8'); + const target = /^target "(.*)"$/m.exec(yarnrc)[1]; + return target; +} + +function ensureDirs(dirPath) { + if (!fs.existsSync(dirPath)) { + ensureDirs(path.dirname(dirPath)); + fs.mkdirSync(dirPath); + } +} + +/* Downloads the node executable used for the remote server to ./build/node-remote */ +gulp.task(task.define('node-remote', () => { + const VERSION = getNodeVersion(); + const nodePath = path.join('.build', 'node-remote'); + const nodeVersionPath = path.join(nodePath, 'version'); + if (!fs.existsSync(nodeVersionPath) || fs.readFileSync(nodeVersionPath).toString() !== VERSION) { + ensureDirs(nodePath); + util.rimraf(nodePath); + fs.writeFileSync(nodeVersionPath, VERSION); + return nodejs(process.platform, process.arch).pipe(vfs.dest(nodePath)); + } + return vfs.src(nodePath); +})); + +function nodejs(platform, arch) { + const VERSION = getNodeVersion(); + + if (arch === 'ia32') { + arch = 'x86'; + } + + if (platform === 'win32') { + const downloadPath = `/dist/v${VERSION}/win-${arch}/node.exe`; + + return ( + util.download({ host: 'nodejs.org', path: downloadPath }) + .pipe(es.through(function (data) { + // base comes in looking like `https:\nodejs.org\dist\v10.2.1\win-x64\node.exe` + this.emit('data', new File({ + path: data.path, + base: data.base.replace(/\\node\.exe$/, ''), + contents: data.contents, + stat: { + isFile: true, + mode: /* 100755 */ 33261 + } + })); + })) + ); + } + + if (platform === 'darwin') { + arch = 'x64'; + } + + if (arch === 'armhf') { + arch = 'armv7l'; + } + + const downloadPath = `/dist/v${VERSION}/node-v${VERSION}-${platform}-${arch}.tar.gz`; + + return ( + util.download({ host: 'nodejs.org', path: downloadPath }) + .pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) + .pipe(es.through(function (data) { + // base comes in looking like `https:/nodejs.org/dist/v8.9.3/node-v8.9.3-darwin-x64.tar.gz` + // => we must remove the `.tar.gz` + // Also, keep only bin/node + if (/\/bin\/node$/.test(data.path)) { + this.emit('data', new File({ + path: data.path.replace(/bin\/node$/, 'node'), + base: data.base.replace(/\.tar\.gz$/, ''), + contents: data.contents, + stat: { + isFile: true, + mode: /* 100755 */ 33261 + } + })); + } + })) + ); +} diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 3c4b82555d2b..d0bebb5af2b6 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -402,6 +402,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(replace('@@VERSION@@', version)) .pipe(replace('@@COMMIT@@', commit)) .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(replace('@@DATAFOLDER@@', product.dataFolderName)) .pipe(replace('@@QUALITY@@', quality)) .pipe(rename(function (f) { f.basename = product.applicationName; f.extname = ''; }))); @@ -537,14 +538,14 @@ gulp.task('vscode-translations-import', function () { // Sourcemaps gulp.task('upload-vscode-sourcemaps', () => { - const vs = gulp.src('out-vscode-min/**/*.map', { base: 'out-vscode-min' }) + const vs = gulp.src('out-vscode-min/**/*.map', { base: 'out-vscode-min' }) // client source-maps only .pipe(es.mapSync(f => { f.path = `${f.base}/core/${f.relative}`; return f; })); - const extensionsOut = gulp.src('extensions/**/out/**/*.map', { base: '.' }); - const extensionsDist = gulp.src('extensions/**/dist/**/*.map', { base: '.' }); + const extensionsOut = gulp.src(['extensions/**/out/**/*.map', '!extensions/**/node_modules/**'], { base: '.' }); + const extensionsDist = gulp.src(['extensions/**/dist/**/*.map', '!extensions/**/node_modules/**'], { base: '.' }); return es.merge(vs, extensionsOut, extensionsDist) .pipe(es.through(function (data) { diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index a563ed929fc9..7cd31a1a1812 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -23,7 +23,7 @@ const commit = util.getVersion(root); const linuxPackageRevision = Math.floor(new Date().getTime() / 1000); function getDebPackageArch(arch) { - return { x64: 'amd64', ia32: 'i386', arm: 'armhf', arm64: "arm64" }[arch]; + return { x64: 'amd64', arm: 'armhf', arm64: "arm64" }[arch]; } function prepareDebPackage(arch) { @@ -115,7 +115,7 @@ function getRpmBuildPath(rpmArch) { } function getRpmPackageArch(arch) { - return { x64: 'x86_64', ia32: 'i386', arm: 'armhf', arm64: "arm64" }[arch]; + return { x64: 'x86_64', arm: 'armhf', arm64: "arm64" }[arch]; } function prepareRpmPackage(arch) { @@ -241,7 +241,6 @@ function buildSnapPackage(arch) { } const BUILD_TARGETS = [ - { arch: 'ia32' }, { arch: 'x64' }, { arch: 'arm' }, { arch: 'arm64' }, diff --git a/build/lib/electron.js b/build/lib/electron.js index ee737e9d6005..2c981fe95c96 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -24,7 +24,7 @@ module.exports.getElectronVersion = getElectronVersion; if (require.main === module) { const version = getElectronVersion(); const versionFile = path.join(root, '.build', 'electron', 'version'); - const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `v${version}`; + const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; process.exit(isUpToDate ? 0 : 1); } diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 22d3d165c3c3..32b0e6fb4c79 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -38,10 +38,6 @@ "name": "vs/workbench/contrib/codeEditor", "project": "vscode-workbench" }, - { - "name": "vs/workbench/contrib/codeinset", - "project": "vscode-workbench" - }, { "name": "vs/workbench/contrib/callHierarchy", "project": "vscode-workbench" diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index 7bb98cbab7ba..616a672ce456 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -25,7 +25,7 @@ function log(message: any, ...rest: any[]): void { export interface Language { id: string; // language id, e.g. zh-tw, de - translationId?: string; // language id used in translation tools, e.g zh-hant, de (optional, if not set, the id is used) + translationId?: string; // language id used in translation tools, e.g. zh-hant, de (optional, if not set, the id is used) folderName?: string; // language specific folder name, e.g. cht, deu (optional, if not set, the id is used) } diff --git a/build/lib/watch/package.json b/build/lib/watch/package.json index 0d0313401536..3afffdec7d39 100644 --- a/build/lib/watch/package.json +++ b/build/lib/watch/package.json @@ -4,6 +4,7 @@ "description": "", "author": "Microsoft ", "private": true, + "license": "MIT", "devDependencies": { "gulp-watch": "^4.3.9" } diff --git a/build/package.json b/build/package.json index 8fe67192a093..4dad3474cdc1 100644 --- a/build/package.json +++ b/build/package.json @@ -1,6 +1,7 @@ { "name": "azuredatastudio-oss-dev-build", "version": "1.0.0", + "license": "MIT", "devDependencies": { "@types/ansi-colors": "^3.2.0", "@types/azure": "0.9.19", @@ -43,7 +44,7 @@ "request": "^2.85.0", "tslint": "^5.9.1", "service-downloader": "github:anthonydresser/service-downloader#0.1.5", - "typescript": "3.4.5", + "typescript": "3.5.1", "vsce": "1.48.0", "xml2js": "^0.4.17" }, diff --git a/build/win32/i18n/messages.en.isl b/build/win32/i18n/messages.en.isl index 4bd6c75ad433..a6aab59b95a9 100644 --- a/build/win32/i18n/messages.en.isl +++ b/build/win32/i18n/messages.en.isl @@ -2,7 +2,7 @@ AddContextMenuFiles=Add "Open with %1" action to Windows Explorer file context menu AddContextMenuFolders=Add "Open with %1" action to Windows Explorer directory context menu AssociateWithFiles=Register %1 as an editor for supported file types -AddToPath=Add to PATH (available after restart) +AddToPath=Add to PATH (requires shell restart) RunAfter=Run %1 after installation Other=Other: SourceFile=%1 Source File \ No newline at end of file diff --git a/build/yarn.lock b/build/yarn.lock index 7a77cf1451eb..8551be127d1b 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3201,10 +3201,10 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@3.4.5: - version "3.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" - integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== +typescript@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" + integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" diff --git a/cgmanifest.json b/cgmanifest.json index 01cdd8df5f10..6cebbffd6b9d 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "164c37e3f235134c88e80fac2a182cfba3f07f00" + "commitHash": "c6a08e5368de4352903e702cde750b33239a50ab" } }, "licenseDetail": [ @@ -40,20 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "66.0.3359.181" - }, - { - "component": { - "type": "git", - "git": { - "name": "libchromiumcontent", - "repositoryUrl": "https://github.com/electron/libchromiumcontent", - "commitHash": "7ea271f92018b1eeb8e70ec6de8c29f9758a0c05" - } - }, - "isOnlyProductionDependency": true, - "license": "MIT", - "version": "66.0.3359.181" + "version": "69.0.3497.128" }, { "component": { @@ -61,11 +48,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "5cbb905c1af7cea2d709932d59827d7c6d03ef4a" + "commitHash": "8c70b2084ce5f76ea1e3b3c4ccdeee4483fe338b" } }, "isOnlyProductionDependency": true, - "version": "10.2.0" + "version": "10.11.0" }, { "component": { @@ -73,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "e84a6860e35e14b4031b88bb9b49841cdb89a305" + "commitHash": "c1b5a1cfc8a14a337540193daecfa5d0f50dd7bb" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "3.1.8" + "version": "4.2.3" }, { "component": { @@ -111,11 +98,11 @@ "git": { "name": "vscode-octicons-font", "repositoryUrl": "https://github.com/Microsoft/vscode-octicons-font", - "commitHash": "4f69de3a233ed501c2098e33047e116ac2fbbf42" + "commitHash": "415cd5b42ab699b6b46c0bf011ada0a2ae50bfb4" } }, "license": "MIT", - "version": "1.1.0" + "version": "1.3.1" }, { "component": { diff --git a/extensions/bat/package.json b/extensions/bat/package.json index c54d1a4ce5df..00bd84e4ae42 100644 --- a/extensions/bat/package.json +++ b/extensions/bat/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "scripts": { "update-grammar": "node ../../build/npm/update-grammar.js mmims/language-batchfile grammars/batchfile.cson ./syntaxes/batchfile.tmLanguage.json" diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index e9f9e255f33a..e555d61457e9 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "^1.0.0" }, @@ -108,6 +109,6 @@ ] }, "devDependencies": { - "@types/node": "^10.12.21" + "@types/node": "^10.14.8" } } diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index 38b3c640c50a..55950a9021eb 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -5,6 +5,7 @@ "type": "object", "definitions": { "devContainerCommon": { + "type": "object", "properties": { "name": { "type": "string", @@ -18,11 +19,14 @@ } }, "settings": { - "type": "object", + "$ref": "vscode://schemas/settings/machine", "description": "Machine specific settings that should be copied into the container." }, "postCreateCommand": { - "type": ["string", "array"], + "type": [ + "string", + "array" + ], "description": "A command to run after creating the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", "items": { "type": "string" @@ -35,12 +39,20 @@ } }, "nonComposeBase": { + "type": "object", "properties": { "appPort": { - "type": ["integer", "string", "array"], + "type": [ + "integer", + "string", + "array" + ], "description": "Application ports that are exposed by the container. This can be a single port or an array of ports. Each port can be a number or a string. A number is mapped to the same port on the host. A string is passed to Docker unchanged and can be used to map ports differently, e.g. \"8000:8010\".", "items": { - "type": ["integer", "string"] + "type": [ + "integer", + "string" + ] } }, "runArgs": { @@ -52,7 +64,10 @@ }, "shutdownAction": { "type": "string", - "enum": ["none", "stopContainer"], + "enum": [ + "none", + "stopContainer" + ], "description": "Action to take when VS Code is shutting down. The default is to stop the container." }, "overrideCommand": { @@ -70,6 +85,7 @@ } }, "dockerFileContainer": { + "type": "object", "properties": { "dockerFile": { "type": "string", @@ -80,21 +96,30 @@ "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." } }, - "required": ["dockerFile"] + "required": [ + "dockerFile" + ] }, "imageContainer": { + "type": "object", "properties": { "image": { "type": "string", "description": "The docker image that will be used to create the container." } }, - "required": ["image"] + "required": [ + "image" + ] }, "composeContainer": { + "type": "object", "properties": { "dockerComposeFile": { - "type": ["string", "array"], + "type": [ + "string", + "array" + ], "description": "The name of the docker-compose file(s) used to start the services.", "items": { "type": "string" @@ -110,11 +135,18 @@ }, "shutdownAction": { "type": "string", - "enum": ["none", "stopCompose"], + "enum": [ + "none", + "stopCompose" + ], "description": "Action to take when VS Code is shutting down. The default is to stop the containers." } }, - "required": ["dockerComposeFile", "service", "workspaceFolder"] + "required": [ + "dockerComposeFile", + "service", + "workspaceFolder" + ] } }, "allOf": [ diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index 49c471660664..ce653f097a9e 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@^10.12.21": - version "10.12.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" - integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== jsonc-parser@2.0.2: version "2.0.2" diff --git a/extensions/css-language-features/server/src/test/links.test.ts b/extensions/css-language-features/server/src/test/links.test.ts new file mode 100644 index 000000000000..ff0aae439004 --- /dev/null +++ b/extensions/css-language-features/server/src/test/links.test.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import 'mocha'; +import * as assert from 'assert'; +import Uri from 'vscode-uri'; +import { resolve } from 'path'; +import { TextDocument, DocumentLink } from 'vscode-languageserver-types'; +import { WorkspaceFolder } from 'vscode-languageserver-protocol'; +import { getCSSLanguageService } from 'vscode-css-languageservice'; +import { getDocumentContext } from '../utils/documentContext'; + +export interface ItemDescription { + offset: number; + value: string; + target: string; +} + +suite('Links', () => { + const cssLanguageService = getCSSLanguageService(); + + let assertLink = function (links: DocumentLink[], expected: ItemDescription, document: TextDocument) { + let matches = links.filter(link => { + return document.offsetAt(link.range.start) === expected.offset; + }); + + assert.equal(matches.length, 1, `${expected.offset} should only existing once: Actual: ${links.map(l => document.offsetAt(l.range.start)).join(', ')}`); + let match = matches[0]; + assert.equal(document.getText(match.range), expected.value); + assert.equal(match.target, expected.target); + }; + + function assertLinks(value: string, expected: ItemDescription[], testUri: string, workspaceFolders?: WorkspaceFolder[], lang: string = 'css'): void { + const offset = value.indexOf('|'); + value = value.substr(0, offset) + value.substr(offset + 1); + + const document = TextDocument.create(testUri, lang, 0, value); + + if (!workspaceFolders) { + workspaceFolders = [{ name: 'x', uri: testUri.substr(0, testUri.lastIndexOf('/')) }]; + } + + const context = getDocumentContext(testUri, workspaceFolders); + + const stylesheet = cssLanguageService.parseStylesheet(document); + let links = cssLanguageService.findDocumentLinks(document, stylesheet, context)!; + + assert.equal(links.length, expected.length); + + for (let item of expected) { + assertLink(links, item, document); + } + } + + function getTestResource(path: string) { + return Uri.file(resolve(__dirname, '../../test/linksTestFixtures', path)).toString(); + } + + test('url links', function () { + + let testUri = getTestResource('about.css'); + let folders = [{ name: 'x', uri: getTestResource('') }]; + + assertLinks('html { background-image: url("hello.html|")', + [{ offset: 29, value: '"hello.html"', target: getTestResource('hello.html') }], testUri, folders + ); + }); + + test('node module resolving', function () { + + let testUri = getTestResource('about.css'); + let folders = [{ name: 'x', uri: getTestResource('') }]; + + assertLinks('html { background-image: url("~foo/hello.html|")', + [{ offset: 29, value: '"~foo/hello.html"', target: getTestResource('node_modules/foo/hello.html') }], testUri, folders + ); + }); +}); \ No newline at end of file diff --git a/extensions/css-language-features/server/test/linksTestFixtures/.gitignore b/extensions/css-language-features/server/test/linksTestFixtures/.gitignore new file mode 100644 index 000000000000..d591cdfd50a1 --- /dev/null +++ b/extensions/css-language-features/server/test/linksTestFixtures/.gitignore @@ -0,0 +1 @@ +!/node_modules \ No newline at end of file diff --git a/extensions/css-language-features/server/test/linksTestFixtures/node_modules/foo/package.json b/extensions/css-language-features/server/test/linksTestFixtures/node_modules/foo/package.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/extensions/docker/package.json b/extensions/docker/package.json index 41df24723fae..f5f2f795fc46 100644 --- a/extensions/docker/package.json +++ b/extensions/docker/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "scripts": { "update-grammar": "node ../../build/npm/update-grammar.js moby/moby contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage ./syntaxes/docker.tmLanguage.json" diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index 52159b965bbe..9c07a2568aa9 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "^1.4.0" }, @@ -53,6 +54,6 @@ }, "devDependencies": { "@types/markdown-it": "0.0.2", - "@types/node": "^10.12.21" + "@types/node": "^10.14.8" } } diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index 720c84f06565..971bf580ba7e 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660" integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA= -"@types/node@^10.12.21": - version "10.12.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" - integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== "@types/node@^6.0.46": version "6.0.78" diff --git a/extensions/git-ui/.vscodeignore b/extensions/git-ui/.vscodeignore new file mode 100644 index 000000000000..7462f7448d31 --- /dev/null +++ b/extensions/git-ui/.vscodeignore @@ -0,0 +1,8 @@ +src/** +test/** +out/** +tsconfig.json +build/** +extension.webpack.config.js +cgmanifest.json +yarn.lock diff --git a/extensions/git-ui/README.md b/extensions/git-ui/README.md new file mode 100644 index 000000000000..d418425acfeb --- /dev/null +++ b/extensions/git-ui/README.md @@ -0,0 +1,7 @@ +# Git UI integration for Visual Studio Code + +**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. + +## Features + +See [Git support in VS Code](https://code.visualstudio.com/docs/editor/versioncontrol#_git-support) to learn about the features of this extension. diff --git a/extensions/git-ui/cgmanifest.json b/extensions/git-ui/cgmanifest.json new file mode 100644 index 000000000000..f3071eb691a8 --- /dev/null +++ b/extensions/git-ui/cgmanifest.json @@ -0,0 +1,4 @@ +{ + "registrations": [], + "version": 1 +} \ No newline at end of file diff --git a/extensions/git-ui/extension.webpack.config.js b/extensions/git-ui/extension.webpack.config.js new file mode 100644 index 000000000000..b63c59c65df2 --- /dev/null +++ b/extensions/git-ui/extension.webpack.config.js @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + entry: { + main: './src/main.ts' + } +}); diff --git a/extensions/git-ui/package.json b/extensions/git-ui/package.json new file mode 100644 index 000000000000..2f1ab43f8920 --- /dev/null +++ b/extensions/git-ui/package.json @@ -0,0 +1,28 @@ +{ + "name": "git-ui", + "displayName": "%displayName%", + "description": "%description%", + "publisher": "vscode", + "version": "1.0.0", + "engines": { + "vscode": "^1.5.0" + }, + "extensionKind": "ui", + "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "enableProposedApi": true, + "categories": [ + "Other" + ], + "activationEvents": [ + "onCommand:git.credential" + ], + "main": "./out/main", + "icon": "resources/icons/git.png", + "scripts": { + "compile": "gulp compile-extension:git-ui", + "watch": "gulp watch-extension:git-ui" + }, + "devDependencies": { + "@types/node": "^10.14.8" + } +} \ No newline at end of file diff --git a/extensions/git-ui/package.nls.json b/extensions/git-ui/package.nls.json new file mode 100644 index 000000000000..5303e91f4cd8 --- /dev/null +++ b/extensions/git-ui/package.nls.json @@ -0,0 +1,4 @@ +{ + "displayName": "Git UI", + "description": "Git SCM UI Integration" +} \ No newline at end of file diff --git a/extensions/git-ui/resources/icons/git.png b/extensions/git-ui/resources/icons/git.png new file mode 100644 index 0000000000000000000000000000000000000000..51f4ae5404fc79be09e69171bfc9d34d48810297 GIT binary patch literal 2383 zcmb_edr(tX8b3FeJQNAIb;Tf>)JL(q3J4vLirl*vi*Kow20-DLSS9|3^Zwk7&^dy0_BKc76X2>}!()lOlVnV}tr z%7&jSS|mYX4~n%NS=f@&X(b^Q(N+N%0zF~9g{A- zZfSW+-Hr{m9%u8|2k$3dtage|2Sl;$h{sF(-i|c>B<|Qtz%K^PbRz=513=(`{A&g* z;RKU3B@gTnOaDvupQ!DT9hv|fD?ePvQ@0F%CkIDtqH+r6-`Pof?+-KO$>7G7NXXf@Nusk$YC-Nu- z23d{+;0ha8oHXp(O9h@TmW@CbU`rV z;l_K-qf92mZ;_k&oa6Cfm$b0End8ChmVX)f^pq##^aeu^Al9jBsZfODGUaf3a1>BU z#)l8$FxyesvmRxYX%&I`xjrI)@}}{Yz?WIexeH}QYZaSD3*0t5$8`+Ut3z}uuEv_l zb~=$F*(jZi!&FC0jtaAaNV1^#;yj+uX?0ZYbnwg26}mtz%iwT$HXX)ck)u$l7-(c|flTERxKM;g%Wl*nxU4yuBeLE+%mLQk3j#Q<_)GxC!`o6owL0SxvRSsR*zM_= zM;TNgl4km?o7x7!8}u>19+UmMGd!jky3e24KfUA+5jayP8JB}vti|+79AKDU-2xDY zqn+Hu(=dH4_b`}DuK6stpXcU@G9Z(UVjJC3;j2(CH&^;lDwRFv=gGOes*l+Q;vj%R z1CjmXXlAdvD}6z?*_cB>EeMeU3vbyDzxWD8a=-Y{)zbaNRsI}cXmGi}n|<>UAbYG9 z;xUVTfTo<;jjje( z<5@yEyyg^r9lW4kDOmWi7F$h4OvgW4*hR#{_R2TkNomGiT2@BGExcp}eH{5=tvPPr znrdcAhi9x>Cm;X5CI-$%(YABf@VI-Er7q>WS-MM&t|)rN1|93ssE#gv_%ypPosiRO zHauI2pZ{7#4Cak`9Bm+Eea_1Ri=F@QCrUo01C6qy1LOxLv(y%#4Rb1Cchij&G zr#4JZU%q$h=`k1i`eKFEX0D35ZPMpXFU?6-DzEpNF}+yks$ZJ7-sJd`US6|5H&c)= z?g+u?LxEVi_P~Y{L{FuKKRsbd!pg{zU6$E&)tT=G*E)_wv{c$(%`Lu27Pz|UsdXoo zog#W>2g$8C_+Cx>O@=}tkFwdxlOcOdgU!`~l$ABWSbdW+ZsxSDuMA~-A?WTkY=Uc%5xpg0$kgN=(W^ez> zH_0Zh@CX}tY3=ThcqDg~OVlqDK(!IiGN zC}T7iq-;&)B2ASvfD#5%fx7n~mIN!?Q47j>Zp`9ZwCvXL{U~GNpN-6MZ!^s|bb5-u zl{43z!4-)KlMrXIEQx3vDV*3C+$6xH%iJ^_k9EUe041;&y!qhF5f0HAh zFXfj)ECZw`K31%^WqqQaPbqdu$R@JyV3>73;@C#tjq&C3al~h1#r9#_?ZT-$)5Nk^ zmUQOPgk&zS|FArSgk}Pw6l3UjcW!>`-=XfV0hI_S$2b4{BL82 z2k(D``e93+SzOn9`ydHEt@(u*!(K_su{EJ|-OXKk#a^R&C_m@uwUJZEka5TH48ge_ vp%EL>Wi@Nxn8V*2x$L2D+u#MyDSqcb+P#5YHS3sP9I$26&gfeklTQ2-d^Cga literal 0 HcmV?d00001 diff --git a/extensions/git-ui/src/main.ts b/extensions/git-ui/src/main.ts new file mode 100644 index 000000000000..057074fae6c5 --- /dev/null +++ b/extensions/git-ui/src/main.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ExtensionContext, commands } from 'vscode'; + +import * as cp from 'child_process'; + +export async function deactivate(): Promise { +} + +export async function activate(context: ExtensionContext): Promise { + context.subscriptions.push(commands.registerCommand('git.credential', async (data: any) => { + try { + const { stdout, stderr } = await exec(`git credential ${data.command}`, { + stdin: data.stdin, + env: Object.assign(process.env, { GIT_TERMINAL_PROMPT: '0' }) + }); + return { stdout, stderr, code: 0 }; + } catch ({ stdout, stderr, error }) { + const code = error.code || 0; + if (stderr.indexOf('terminal prompts disabled') !== -1) { + stderr = ''; + } + return { stdout, stderr, code }; + } + })); +} + +export interface ExecResult { + error: Error | null; + stdout: string; + stderr: string; +} + + +export function exec(command: string, options: cp.ExecOptions & { stdin?: string } = {}) { + return new Promise((resolve, reject) => { + const child = cp.exec(command, options, (error, stdout, stderr) => { + (error ? reject : resolve)({ error, stdout, stderr }); + }); + if (options.stdin) { + child.stdin.write(options.stdin, (err: any) => { + if (err) { + reject(err); + return; + } + child.stdin.end((err: any) => { + if (err) { + reject(err); + } + }); + }); + } + }); +} diff --git a/extensions/git-ui/src/typings/refs.d.ts b/extensions/git-ui/src/typings/refs.d.ts new file mode 100644 index 000000000000..e28bb3971604 --- /dev/null +++ b/extensions/git-ui/src/typings/refs.d.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// +/// +/// \ No newline at end of file diff --git a/extensions/git-ui/tsconfig.json b/extensions/git-ui/tsconfig.json new file mode 100644 index 000000000000..27e9268b39b2 --- /dev/null +++ b/extensions/git-ui/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../shared.tsconfig.json", + "compilerOptions": { + "outDir": "./out", + "experimentalDecorators": true, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "include": [ + "src/**/*" + ] +} \ No newline at end of file diff --git a/extensions/git-ui/yarn.lock b/extensions/git-ui/yarn.lock new file mode 100644 index 000000000000..b23b0ac0392e --- /dev/null +++ b/extensions/git-ui/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== diff --git a/extensions/git/package.json b/extensions/git/package.json index a6cf112f69a8..2debe889d389 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -3,6 +3,7 @@ "displayName": "%displayName%", "description": "%description%", "publisher": "vscode", + "license": "MIT", "version": "1.0.0", "engines": { "vscode": "^1.5.0" @@ -20,7 +21,8 @@ "scripts": { "compile": "gulp compile-extension:git", "watch": "gulp watch-extension:git", - "update-grammar": "node ./build/update-grammars.js" + "update-grammar": "node ./build/update-grammars.js", + "test": "mocha" }, "contributes": { "commands": [ @@ -1458,13 +1460,14 @@ "jschardet": "^1.6.0", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0", + "vscode-uri": "^2.0.0", "which": "^1.3.0" }, "devDependencies": { "@types/byline": "4.2.31", "@types/file-type": "^5.2.1", "@types/mocha": "2.2.43", - "@types/node": "^10.12.21", + "@types/node": "^10.14.8", "@types/which": "^1.0.28", "mocha": "^3.2.0" } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 767aacd3c9f3..9e82d769f1a7 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -235,4 +235,4 @@ export const enum GitErrorCodes { CantRebaseMultipleBranches = 'CantRebaseMultipleBranches', PatchDoesNotApply = 'PatchDoesNotApply', NoPathFound = 'NoPathFound' -} \ No newline at end of file +} diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index e034c98593d8..ed912b8c441a 100755 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -56,7 +56,13 @@ class CheckoutRemoteHeadItem extends CheckoutItem { return; } - await repository.checkoutTracking(this.ref.name); + const branches = await repository.findTrackingBranches(this.ref.name); + + if (branches.length > 0) { + await repository.checkout(branches[0].name!); + } else { + await repository.checkoutTracking(this.ref.name); + } } } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 3684b30c5a0a..d9d475bbf87d 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -12,7 +12,8 @@ import { EventEmitter } from 'events'; import iconv = require('iconv-lite'); import * as filetype from 'file-type'; import { assign, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent } from './util'; -import { CancellationToken, Uri, workspace } from 'vscode'; +import { CancellationToken } from 'vscode'; +import { URI } from 'vscode-uri'; import { detectEncoding } from './encoding'; import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git'; @@ -557,7 +558,7 @@ export function parseGitmodules(raw: string): Submodule[] { return; } - const propertyMatch = /^\s*(\w+) = (.*)$/.exec(line); + const propertyMatch = /^\s*(\w+)\s+=\s+(.*)$/.exec(line); if (!propertyMatch) { return; @@ -636,6 +637,7 @@ export interface CommitOptions { export interface PullOptions { unshallow?: boolean; + tags?: boolean; } export enum ForcePushMode { @@ -995,7 +997,7 @@ export class Repository { break; } - const originalUri = Uri.file(path.isAbsolute(resourcePath) ? resourcePath : path.join(this.repositoryRoot, resourcePath)); + const originalUri = URI.file(path.isAbsolute(resourcePath) ? resourcePath : path.join(this.repositoryRoot, resourcePath)); let status: Status = Status.UNTRACKED; // Copy or Rename status comes with a number, e.g. 'R100'. We don't need the number, so we use only first character of the status. @@ -1023,7 +1025,7 @@ export class Repository { break; } - const uri = Uri.file(path.isAbsolute(newPath) ? newPath : path.join(this.repositoryRoot, newPath)); + const uri = URI.file(path.isAbsolute(newPath) ? newPath : path.join(this.repositoryRoot, newPath)); result.push({ uri, renameUri: uri, @@ -1363,9 +1365,8 @@ export class Repository { async pull(rebase?: boolean, remote?: string, branch?: string, options: PullOptions = {}): Promise { const args = ['pull']; - const config = workspace.getConfiguration('git', Uri.file(this.root)); - if (config.get('pullTags')) { + if (options.tags) { args.push('--tags'); } @@ -1578,6 +1579,14 @@ export class Repository { } } + async findTrackingBranches(upstreamBranch: string): Promise { + const result = await this.run(['for-each-ref', '--format', '%(refname:short)%00%(upstream:short)', 'refs/heads']); + return result.stdout.trim().split('\n') + .map(line => line.trim().split('\0')) + .filter(([_, upstream]) => upstream === upstreamBranch) + .map(([ref]) => ({ name: ref, type: RefType.Head } as Branch)); + } + async getRefs(): Promise { const result = await this.run(['for-each-ref', '--format', '%(refname) %(objectname)', '--sort', '-committerdate']); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 26888a11a2d8..125326535c12 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -299,6 +299,7 @@ export const enum Operation { GetObjectDetails = 'GetObjectDetails', SubmoduleUpdate = 'SubmoduleUpdate', RebaseContinue = 'RebaseContinue', + FindTrackingBranches = 'GetTracking', Apply = 'Apply', Blame = 'Blame', Log = 'Log', @@ -909,6 +910,10 @@ export class Repository implements Disposable { await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { track: true })); } + async findTrackingBranches(upstreamRef: string): Promise { + return await this.run(Operation.FindTrackingBranches, () => this.repository.findTrackingBranches(upstreamRef)); + } + async getCommit(ref: string): Promise { return await this.repository.getCommit(ref); } @@ -979,11 +984,12 @@ export class Repository implements Disposable { await this.maybeAutoStash(async () => { const config = workspace.getConfiguration('git', Uri.file(this.root)); const fetchOnPull = config.get('fetchOnPull'); + const tags = config.get('pullTags'); if (fetchOnPull) { - await this.repository.pull(rebase, undefined, undefined, { unshallow }); + await this.repository.pull(rebase, undefined, undefined, { unshallow, tags }); } else { - await this.repository.pull(rebase, remote, branch, { unshallow }); + await this.repository.pull(rebase, remote, branch, { unshallow, tags }); } }); }); @@ -1039,11 +1045,12 @@ export class Repository implements Disposable { await this.maybeAutoStash(async () => { const config = workspace.getConfiguration('git', Uri.file(this.root)); const fetchOnPull = config.get('fetchOnPull'); + const tags = config.get('pullTags'); if (fetchOnPull) { - await this.repository.pull(rebase); + await this.repository.pull(rebase, undefined, undefined, { tags }); } else { - await this.repository.pull(rebase, remoteName, pullBranch); + await this.repository.pull(rebase, remoteName, pullBranch, { tags }); } const remote = this.remotes.find(r => r.name === remoteName); @@ -1156,7 +1163,7 @@ export class Repository implements Disposable { } // https://git-scm.com/docs/git-check-ignore#git-check-ignore--z - const child = this.repository.stream(['check-ignore', '-z', '--stdin'], { stdio: [null, null, null] }); + const child = this.repository.stream(['check-ignore', '-v', '-z', '--stdin'], { stdio: [null, null, null] }); child.stdin.end(filePaths.join('\0'), 'utf8'); const onExit = (exitCode: number) => { @@ -1164,8 +1171,7 @@ export class Repository implements Disposable { // nothing ignored resolve(new Set()); } else if (exitCode === 0) { - // paths are separated by the null-character - resolve(new Set(data.split('\0'))); + resolve(new Set(this.parseIgnoreCheck(data))); } else { if (/ is in submodule /.test(stderr)) { reject(new GitError({ stdout: data, stderr, exitCode, gitErrorCode: GitErrorCodes.IsInSubmodule })); @@ -1193,6 +1199,23 @@ export class Repository implements Disposable { }); } + // Parses output of `git check-ignore -v -z` and returns only those paths + // that are actually ignored by git. + // Matches to a negative pattern (starting with '!') are filtered out. + // See also https://git-scm.com/docs/git-check-ignore#_output. + private parseIgnoreCheck(raw: string): string[] { + const ignored = []; + const elements = raw.split('\0'); + for (let i = 0; i < elements.length; i += 4) { + const pattern = elements[i + 2]; + const path = elements[i + 3]; + if (pattern && !pattern.startsWith('!')) { + ignored.push(path); + } + } + return ignored; + } + private async run(operation: Operation, runOperation: () => Promise = () => Promise.resolve(null)): Promise { if (this.state !== RepositoryState.Idle) { throw new Error('Repository not initialized'); diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index 8e27a374fa19..facabe355001 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -172,6 +172,17 @@ suite('git', () => { { name: 'deps/spdlog4', path: 'deps/spdlog4', url: 'https://github.com/gabime/spdlog4.git' } ]); }); + + test('whitespace #74844', () => { + const sample = `[submodule "deps/spdlog"] + path = deps/spdlog + url = https://github.com/gabime/spdlog.git +`; + + assert.deepEqual(parseGitmodules(sample), [ + { name: 'deps/spdlog', path: 'deps/spdlog', url: 'https://github.com/gabime/spdlog.git' } + ]); + }); }); suite('parseGitCommit', () => { diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index f13ef93c34f7..1f2ea8aed42b 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -26,10 +26,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== -"@types/node@^10.12.21": - version "10.12.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" - integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== "@types/which@^1.0.28": version "1.0.28" @@ -325,6 +325,11 @@ vscode-nls@^4.0.0: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-uri@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.0.tgz#2df704222f72b8a71ff266ba0830ed6c51ac1542" + integrity sha512-lWXWofDSYD8r/TIyu64MdwB4FaSirQ608PP/TzUyslyOeHGwQ0eTHUZeJrK1ILOmwUHaJtV693m2JoUYroUDpw== + which@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 9dc4f87d9f08..0847ae5e083b 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "engines": { "vscode": "0.10.x" @@ -107,6 +108,6 @@ "request-light": "^0.2.4" }, "devDependencies": { - "@types/node": "^10.12.21" + "@types/node": "^10.14.8" } } diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index 27be5ab1bb15..9b02dc24ef69 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -154,7 +154,8 @@ To connect to the server from NodeJS, see Remy Suen's great write-up on [how to ## Participate -The source code of the JSON language server can be found [VSCode repository](https://github.com/Microsoft/vscode) at [extensions/json-language-features/server](https://github.com/Microsoft/vscode/tree/master/extensions/json-language-features/server). +The source code of the JSON language server can be found in the [VSCode repository](https://github.com/Microsoft/vscode) at [extensions/json-language-features/server](https://github.com/Microsoft/vscode/tree/master/extensions/json-language-features/server). + File issues and pull requests in the [VSCode GitHub Issues](https://github.com/Microsoft/vscode/issues). See the document [How to Contribute](https://github.com/Microsoft/vscode/wiki/How-to-Contribute) on how to build and run from source. Most of the functionality of the server is located in libraries: @@ -164,10 +165,12 @@ Most of the functionality of the server is located in libraries: Help on any of these projects is very welcome. -Please see also our [Code of Conduct](CODE_OF_CONDUCT.md). +## Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## License Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the [MIT](LICENSE.txt) License. +Licensed under the [MIT](https://github.com/microsoft/vscode/blob/master/LICENSE.txt) License. diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 1f73cea2295d..054c0fe0178c 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@types/mocha": "2.2.33", - "@types/node": "^10.12.21" + "@types/node": "^10.14.8" }, "scripts": { "prepublishOnly": "npm run clean && npm run test", diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 3f0383a8c4f1..c7e54ce8845a 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.33.tgz#d79a0061ec270379f4d9e225f4096fb436669def" integrity sha1-15oAYewnA3n02eIl9AlvtDZmne8= -"@types/node@^10.12.21": - version "10.12.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" - integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== agent-base@4, agent-base@^4.1.0: version "4.1.2" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 758f96fd5115..0f11fa9d58d1 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@^10.12.21": - version "10.12.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" - integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== agent-base@4, agent-base@^4.1.0: version "4.2.1" diff --git a/extensions/json/package.json b/extensions/json/package.json index fd0dd54ff262..9c8b0f84a2a2 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "0.10.x" }, diff --git a/extensions/markdown-basics/package.json b/extensions/markdown-basics/package.json index 8d1ef2828c74..26a3599cf5ba 100644 --- a/extensions/markdown-basics/package.json +++ b/extensions/markdown-basics/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "^1.20.0" }, diff --git a/extensions/markdown-language-features/media/markdown.css b/extensions/markdown-language-features/media/markdown.css index 1be3a3fa03eb..f1da84d9dcd7 100644 --- a/extensions/markdown-language-features/media/markdown.css +++ b/extensions/markdown-language-features/media/markdown.css @@ -135,7 +135,6 @@ h3 code, h4 code, h5 code, h6 code { - font-size: inherit; line-height: auto; } @@ -168,8 +167,8 @@ blockquote { code { font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; - font-size: 1rem; - line-height: 1.357rem; + font-size: 1em; + line-height: 1.357em; } body.wordWrap pre { diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index e18099fe673e..dc82280c4ebd 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -5,6 +5,7 @@ "version": "1.0.0", "icon": "icon.png", "publisher": "vscode", + "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "engines": { "vscode": "^1.20.0" @@ -313,7 +314,7 @@ "build-preview": "webpack --mode development" }, "dependencies": { - "highlight.js": "9.13.1", + "highlight.js": "9.15.8", "markdown-it": "^8.4.2", "markdown-it-front-matter": "^0.1.2", "vscode-extension-telemetry": "0.1.1", @@ -323,7 +324,7 @@ "@types/highlight.js": "9.12.3", "@types/lodash.throttle": "^4.1.3", "@types/markdown-it": "0.0.2", - "@types/node": "^10.12.21", + "@types/node": "^10.14.8", "lodash.throttle": "^4.1.1", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", diff --git a/extensions/markdown-language-features/src/features/documentLinkProvider.ts b/extensions/markdown-language-features/src/features/documentLinkProvider.ts index a41e4adc7ff3..970cea3d8918 100644 --- a/extensions/markdown-language-features/src/features/documentLinkProvider.ts +++ b/extensions/markdown-language-features/src/features/documentLinkProvider.ts @@ -5,17 +5,20 @@ import * as path from 'path'; import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; import { OpenDocumentLinkCommand } from '../commands/openDocumentLink'; import { getUriForLinkWithKnownExternalScheme } from '../util/links'; -function normalizeLink( +const localize = nls.loadMessageBundle(); + +function parseLink( document: vscode.TextDocument, link: string, base: string -): vscode.Uri { +): { uri: vscode.Uri, tooltip?: string } { const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link); if (externalSchemeUri) { - return externalSchemeUri; + return { uri: externalSchemeUri }; } // Assume it must be an relative or absolute file path @@ -34,7 +37,10 @@ function normalizeLink( resourcePath = base ? path.join(base, tempUri.path) : tempUri.path; } - return OpenDocumentLinkCommand.createCommandUri(resourcePath, tempUri.fragment); + return { + uri: OpenDocumentLinkCommand.createCommandUri(resourcePath, tempUri.fragment), + tooltip: localize('documentLink.tooltip', 'follow link') + }; } function matchAll( @@ -61,9 +67,12 @@ function extractDocumentLink( const linkStart = document.positionAt(offset); const linkEnd = document.positionAt(offset + link.length); try { - return new vscode.DocumentLink( + const { uri, tooltip } = parseLink(document, link, base); + const documentLink = new vscode.DocumentLink( new vscode.Range(linkStart, linkEnd), - normalizeLink(document, link, base)); + uri); + documentLink.tooltip = tooltip; + return documentLink; } catch (e) { return undefined; } @@ -144,11 +153,10 @@ export default class LinkProvider implements vscode.DocumentLinkProvider { } } - for (const definition of Array.from(definitions.values())) { + for (const definition of definitions.values()) { try { - results.push(new vscode.DocumentLink( - definition.linkRange, - normalizeLink(document, definition.link, base))); + const { uri } = parseLink(document, definition.link, base); + results.push(new vscode.DocumentLink(definition.linkRange, uri)); } catch (e) { // noop } diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index e85ae950b3a8..a1b876e7dc8a 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -29,10 +29,10 @@ resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660" integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA= -"@types/node@^10.12.21": - version "10.12.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" - integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== abbrev@1: version "1.1.1" @@ -2933,10 +2933,10 @@ he@1.1.1: resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= -highlight.js@9.13.1: - version "9.13.1" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e" - integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A== +highlight.js@9.15.8: + version "9.15.8" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.8.tgz#f344fda123f36f1a65490e932cf90569e4999971" + integrity sha512-RrapkKQWwE+wKdF73VsOa2RQdIoO3mxwJ4P8mhbI6KYJUraUHRKM5w5zQQKXNk0xNL4UVRdulV9SBJcmzJNzVA== hmac-drbg@^1.0.0: version "1.0.1" diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json index efc0ff0cf7a4..2828669b04d3 100644 --- a/extensions/merge-conflict/package.json +++ b/extensions/merge-conflict/package.json @@ -5,6 +5,7 @@ "description": "%description%", "icon": "resources/icons/merge-conflict.png", "version": "1.0.0", + "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", "engines": { "vscode": "^1.5.0" @@ -114,6 +115,21 @@ "type": "boolean", "description": "%config.autoNavigateNextConflictEnabled%", "default": false + }, + "merge-conflict.diffViewPosition": { + "type": "string", + "enum": [ + "Current", + "Beside", + "Below" + ], + "description": "%config.diffViewPosition%", + "enumDescriptions": [ + "%config.diffViewPosition.current%", + "%config.diffViewPosition.beside%", + "%config.diffViewPosition.below%" + ], + "default": "Current" } } } @@ -122,6 +138,6 @@ "vscode-nls": "^4.0.0" }, "devDependencies": { - "@types/node": "8.0.33" + "@types/node": "^10.14.8" } } diff --git a/extensions/merge-conflict/package.nls.json b/extensions/merge-conflict/package.nls.json index 94599bf281e3..3310dac7e21c 100644 --- a/extensions/merge-conflict/package.nls.json +++ b/extensions/merge-conflict/package.nls.json @@ -15,5 +15,9 @@ "config.title": "Merge Conflict", "config.autoNavigateNextConflictEnabled": "Whether to automatically navigate to the next merge conflict after resolving a merge conflict.", "config.codeLensEnabled": "Create a Code Lens for merge conflict blocks within editor.", - "config.decoratorsEnabled": "Create decorators for merge conflict blocks within editor." + "config.decoratorsEnabled": "Create decorators for merge conflict blocks within editor.", + "config.diffViewPosition": "Controls where the diff view should be opened when comparing changes in merge conflicts.", + "config.diffViewPosition.current": "Open the diff view in the current editor group.", + "config.diffViewPosition.beside": "Open the diff view next to the current editor group.", + "config.diffViewPosition.below": "Open the diff view below the current editor group." } \ No newline at end of file diff --git a/extensions/merge-conflict/src/commandHandler.ts b/extensions/merge-conflict/src/commandHandler.ts index 5807223e4e11..7f6630c09e07 100644 --- a/extensions/merge-conflict/src/commandHandler.ts +++ b/extensions/merge-conflict/src/commandHandler.ts @@ -88,18 +88,54 @@ export default class CommandHandler implements vscode.Disposable { } } + const conflicts = await this.tracker.getConflicts(editor.document); + + // Still failed to find conflict, warn the user and exit + if (!conflicts) { + vscode.window.showWarningMessage(localize('cursorNotInConflict', 'Editor cursor is not within a merge conflict')); + return; + } + const scheme = editor.document.uri.scheme; let range = conflict.current.content; + let leftRanges = conflicts.map(conflict => [conflict.current.content, conflict.range]); + let rightRanges = conflicts.map(conflict => [conflict.incoming.content, conflict.range]); + const leftUri = editor.document.uri.with({ scheme: ContentProvider.scheme, - query: JSON.stringify({ scheme, range }) + query: JSON.stringify({ scheme, range: range, ranges: leftRanges }) }); + range = conflict.incoming.content; - const rightUri = leftUri.with({ query: JSON.stringify({ scheme, range }) }); + const rightUri = leftUri.with({ query: JSON.stringify({ scheme, ranges: rightRanges }) }); + + let mergeConflictLineOffsets = 0; + for (let nextconflict of conflicts) { + if (nextconflict.range.isEqual(conflict.range)) { + break; + } else { + mergeConflictLineOffsets += (nextconflict.range.end.line - nextconflict.range.start.line) - (nextconflict.incoming.content.end.line - nextconflict.incoming.content.start.line); + } + } + const selection = new vscode.Range( + conflict.range.start.line - mergeConflictLineOffsets, conflict.range.start.character, + conflict.range.start.line - mergeConflictLineOffsets, conflict.range.start.character + ); const title = localize('compareChangesTitle', '{0}: Current Changes ⟷ Incoming Changes', fileName); - vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, title); + const mergeConflictConfig = vscode.workspace.getConfiguration('merge-conflict'); + const openToTheSide = mergeConflictConfig.get('diffViewPosition'); + const opts: vscode.TextDocumentShowOptions = { + viewColumn: openToTheSide === 'Beside' ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active, + selection + }; + + if (openToTheSide === 'Below') { + await vscode.commands.executeCommand('workbench.action.newGroupBelow'); + } + + await vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, title, opts); } navigateNext(editor: vscode.TextEditor): Promise { diff --git a/extensions/merge-conflict/src/contentProvider.ts b/extensions/merge-conflict/src/contentProvider.ts index 542a9ccc02eb..49bbf0aa948e 100644 --- a/extensions/merge-conflict/src/contentProvider.ts +++ b/extensions/merge-conflict/src/contentProvider.ts @@ -23,11 +23,27 @@ export default class MergeConflictContentProvider implements vscode.TextDocument async provideTextDocumentContent(uri: vscode.Uri): Promise { try { - const { scheme, range } = JSON.parse(uri.query) as { scheme: string; range: { line: number, character: number }[] }; - const [start, end] = range; + const { scheme, ranges } = JSON.parse(uri.query) as { scheme: string, ranges: [{ line: number, character: number }[], { line: number, character: number }[]][] }; + // complete diff const document = await vscode.workspace.openTextDocument(uri.with({ scheme, query: '' })); - const text = document.getText(new vscode.Range(start.line, start.character, end.line, end.character)); + + let text = ''; + let lastPosition = new vscode.Position(0, 0); + + ranges.forEach(rangeObj => { + let [conflictRange, fullRange] = rangeObj; + const [start, end] = conflictRange; + const [fullStart, fullEnd] = fullRange; + + text += document.getText(new vscode.Range(lastPosition.line, lastPosition.character, fullStart.line, fullStart.character)); + text += document.getText(new vscode.Range(start.line, start.character, end.line, end.character)); + lastPosition = new vscode.Position(fullEnd.line, fullEnd.character); + }); + + let documentEnd = document.lineAt(document.lineCount - 1).range.end; + text += document.getText(new vscode.Range(lastPosition.line, lastPosition.character, documentEnd.line, documentEnd.character)); + return text; } catch (ex) { diff --git a/extensions/merge-conflict/yarn.lock b/extensions/merge-conflict/yarn.lock index 6767cb8d8c22..e6247e292557 100644 --- a/extensions/merge-conflict/yarn.lock +++ b/extensions/merge-conflict/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@8.0.33": - version "8.0.33" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.33.tgz#1126e94374014e54478092830704f6ea89df04cd" - integrity sha512-vmCdO8Bm1ExT+FWfC9sd9r4jwqM7o97gGy2WBshkkXbf/2nLAJQUrZfIhw27yVOtLUev6kSZc4cav/46KbDd8A== +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== vscode-nls@^4.0.0: version "4.0.0" diff --git a/extensions/objective-c/build/update-grammars.js b/extensions/objective-c/build/update-grammars.js new file mode 100644 index 000000000000..2c9b27a4e943 --- /dev/null +++ b/extensions/objective-c/build/update-grammars.js @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +var updateGrammar = require('../../../build/npm/update-grammar'); + +updateGrammar.update('jeff-hykin/cpp-textmate-grammar', '/syntaxes/objc.tmLanguage.json', './syntaxes/objective-c.tmLanguage.json'); +updateGrammar.update('jeff-hykin/cpp-textmate-grammar', '/syntaxes/objcpp.tmLanguage.json', './syntaxes/objective-c++.tmLanguage.json'); + diff --git a/extensions/objective-c/test/colorize-fixtures/test.mm b/extensions/objective-c/test/colorize-fixtures/test.mm new file mode 100644 index 000000000000..d5d31433ae31 --- /dev/null +++ b/extensions/objective-c/test/colorize-fixtures/test.mm @@ -0,0 +1,52 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +#import "UseQuotes.h" +#import + +/* + Multi + Line + Comments +*/ +@implementation Test + +- (void) applicationWillFinishLaunching:(NSNotification *)notification +{ +} + +- (IBAction)onSelectInput:(id)sender +{ + NSString* defaultDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true)[0]; + + NSOpenPanel* panel = [NSOpenPanel openPanel]; + [panel setAllowedFileTypes:[[NSArray alloc] initWithObjects:@"ipa", @"xcarchive", @"app", nil]]; + + [panel beginWithCompletionHandler:^(NSInteger result) + { + if (result == NSFileHandlingPanelOKButton) + [self.inputTextField setStringValue:[panel.URL path]]; + }]; + return YES; + + int hex = 0xFEF1F0F; + float ing = 3.14; + ing = 3.14e0; + ing = 31.4e-2; +} + +-(id) initWithParams:(id) aHandler withDeviceStateManager:(id) deviceStateManager +{ + // add a tap gesture recognizer + UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; + NSMutableArray *gestureRecognizers = [NSMutableArray array]; + [gestureRecognizers addObject:tapGesture]; + [gestureRecognizers addObjectsFromArray:scnView.gestureRecognizers]; + scnView.gestureRecognizers = gestureRecognizers; + + return tapGesture; + return nil; +} + +@end diff --git a/extensions/objective-c/test/colorize-results/test_mm.json b/extensions/objective-c/test/colorize-results/test_mm.json new file mode 100644 index 000000000000..2af2751bcec3 --- /dev/null +++ b/extensions/objective-c/test/colorize-results/test_mm.json @@ -0,0 +1,3093 @@ +[ + { + "c": "//", + "t": "source.objcpp comment.line.double-slash.objcpp punctuation.definition.comment.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "//", + "t": "source.objcpp comment.line.double-slash.objcpp punctuation.definition.comment.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " Copyright (c) Microsoft Corporation. All rights reserved.", + "t": "source.objcpp comment.line.double-slash.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "//", + "t": "source.objcpp comment.line.double-slash.objcpp punctuation.definition.comment.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "#", + "t": "source.objcpp meta.preprocessor.include.objcpp keyword.control.directive.import.objcpp punctuation.definition.directive.objcpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "import", + "t": "source.objcpp meta.preprocessor.include.objcpp keyword.control.directive.import.objcpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.objcpp meta.preprocessor.include.objcpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "\"", + "t": "source.objcpp meta.preprocessor.include.objcpp string.quoted.double.include.objcpp punctuation.definition.string.begin.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "UseQuotes.h", + "t": "source.objcpp meta.preprocessor.include.objcpp string.quoted.double.include.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\"", + "t": "source.objcpp meta.preprocessor.include.objcpp string.quoted.double.include.objcpp punctuation.definition.string.end.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "#", + "t": "source.objcpp meta.preprocessor.include.objcpp keyword.control.directive.import.objcpp punctuation.definition.directive.objcpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "import", + "t": "source.objcpp meta.preprocessor.include.objcpp keyword.control.directive.import.objcpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.objcpp meta.preprocessor.include.objcpp", + "r": { + "dark_plus": "meta.preprocessor: #569CD6", + "light_plus": "meta.preprocessor: #0000FF", + "dark_vs": "meta.preprocessor: #569CD6", + "light_vs": "meta.preprocessor: #0000FF", + "hc_black": "meta.preprocessor: #569CD6" + } + }, + { + "c": "<", + "t": "source.objcpp meta.preprocessor.include.objcpp string.quoted.other.lt-gt.include.objcpp punctuation.definition.string.begin.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "Use/GTLT.h", + "t": "source.objcpp meta.preprocessor.include.objcpp string.quoted.other.lt-gt.include.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ">", + "t": "source.objcpp meta.preprocessor.include.objcpp string.quoted.other.lt-gt.include.objcpp punctuation.definition.string.end.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "/*", + "t": "source.objcpp comment.block.objcpp punctuation.definition.comment.begin.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "\tMulti", + "t": "source.objcpp comment.block.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "\tLine", + "t": "source.objcpp comment.block.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "\tComments", + "t": "source.objcpp comment.block.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "*/", + "t": "source.objcpp comment.block.objcpp punctuation.definition.comment.end.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "@", + "t": "source.objcpp meta.implementation.objcpp storage.type.objcpp punctuation.definition.storage.type.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": "implementation", + "t": "source.objcpp meta.implementation.objcpp storage.type.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "Test", + "t": "source.objcpp meta.implementation.objcpp entity.name.type.objcpp", + "r": { + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.type: #4EC9B0" + } + }, + { + "c": "- ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp punctuation.definition.type.begin.objcpp", + "r": { + "dark_plus": "meta.return-type: #4EC9B0", + "light_plus": "meta.return-type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "meta.return-type: #4EC9B0" + } + }, + { + "c": "void", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp storage.type.built-in.primitive.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp punctuation.definition.type.end.objcpp", + "r": { + "dark_plus": "meta.return-type: #4EC9B0", + "light_plus": "meta.return-type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "meta.return-type: #4EC9B0" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp", + "r": { + "dark_plus": "meta.return-type: #4EC9B0", + "light_plus": "meta.return-type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "meta.return-type: #4EC9B0" + } + }, + { + "c": "applicationWillFinishLaunching", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp entity.name.function.objcpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": ":", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp entity.name.function.name-of-parameter.objcpp punctuation.separator.arguments.objcpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp punctuation.definition.type.begin.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSNotification", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp support.class.cocoa.objcpp", + "r": { + "dark_plus": "support.class: #4EC9B0", + "light_plus": "support.class: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.class: #4EC9B0" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp keyword.operator.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp punctuation.definition.type.end.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "notification", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp variable.parameter.function.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "{", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.section.block.begin.bracket.curly.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.section.block.end.bracket.curly.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "- ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp punctuation.definition.type.begin.objcpp", + "r": { + "dark_plus": "meta.return-type: #4EC9B0", + "light_plus": "meta.return-type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "meta.return-type: #4EC9B0" + } + }, + { + "c": "IBAction", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp storage.type.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp punctuation.definition.type.end.objcpp", + "r": { + "dark_plus": "meta.return-type: #4EC9B0", + "light_plus": "meta.return-type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "meta.return-type: #4EC9B0" + } + }, + { + "c": "onSelectInput", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp entity.name.function.objcpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": ":", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp entity.name.function.name-of-parameter.objcpp punctuation.separator.arguments.objcpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp punctuation.definition.type.begin.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "id", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp storage.type.id.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp punctuation.definition.type.end.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "sender", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp variable.parameter.function.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "{", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.section.block.begin.bracket.curly.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSString", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp support.class.cocoa.objcpp", + "r": { + "dark_plus": "support.class: #4EC9B0", + "light_plus": "support.class: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.class: #4EC9B0" + } + }, + { + "c": "*", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " defaultDir ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.assignment.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.whitespace.support.function.leading.cocoa.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSSearchPathForDirectoriesInDomains", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp support.function.cocoa.objcpp", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.parens.block.objcpp punctuation.section.parens.begin.bracket.round.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSDocumentDirectory", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.parens.block.objcpp support.constant.cocoa.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.parens.block.objcpp punctuation.separator.delimiter.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.parens.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSUserDomainMask", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.parens.block.objcpp support.constant.cocoa.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ",", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.parens.block.objcpp punctuation.separator.delimiter.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.parens.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "true", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.parens.block.objcpp constant.language.objcpp", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.parens.block.objcpp punctuation.section.parens.end.bracket.round.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp constant.numeric.decimal.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSOpenPanel", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp support.class.cocoa.objcpp", + "r": { + "dark_plus": "support.class: #4EC9B0", + "light_plus": "support.class: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.class: #4EC9B0" + } + }, + { + "c": "*", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " panel ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.assignment.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSOpenPanel", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp support.class.cocoa.objcpp", + "r": { + "dark_plus": "support.class: #4EC9B0", + "light_plus": "support.class: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.class: #4EC9B0" + } + }, + { + "c": " openPanel", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "panel setAllowedFileTypes:", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSArray", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp support.class.cocoa.objcpp", + "r": { + "dark_plus": "support.class: #4EC9B0", + "light_plus": "support.class: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.class: #4EC9B0" + } + }, + { + "c": " alloc", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " initWithObjects:", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "@\"", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp string.quoted.double.objcpp punctuation.definition.string.begin.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "ipa", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp string.quoted.double.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\"", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp string.quoted.double.objcpp punctuation.definition.string.end.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ",", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.separator.delimiter.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "@\"", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp string.quoted.double.objcpp punctuation.definition.string.begin.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "xcarchive", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp string.quoted.double.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\"", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp string.quoted.double.objcpp punctuation.definition.string.end.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ",", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.separator.delimiter.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "@\"", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp string.quoted.double.objcpp punctuation.definition.string.begin.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "app", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp string.quoted.double.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\"", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp string.quoted.double.objcpp punctuation.definition.string.end.objcpp", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ",", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.separator.delimiter.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "nil", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp constant.language.objcpp", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "panel beginWithCompletionHandler:", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "^", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp keyword.operator.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.section.parens.begin.bracket.round.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSInteger", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp support.type.cocoa.leopard.objcpp", + "r": { + "dark_plus": "support.type: #4EC9B0", + "light_plus": "support.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.type: #4EC9B0" + } + }, + { + "c": " result", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.section.parens.end.bracket.round.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "{", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.section.block.begin.bracket.curly.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "if", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp keyword.control.objcpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.parens.block.objcpp punctuation.section.parens.begin.bracket.round.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "result ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.parens.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "==", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.parens.block.objcpp keyword.operator.comparison.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.parens.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSFileHandlingPanelOKButton", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.parens.block.objcpp support.constant.cocoa.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.parens.block.objcpp punctuation.section.parens.end.bracket.round.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "self", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp variable.other.object.access.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.separator.dot-access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "inputTextField", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp variable.other.member.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " setStringValue:", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "panel", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp variable.other.object.access.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.separator.dot-access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "URL", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp variable.other.member.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " path", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.section.block.end.bracket.curly.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "return", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.control.objcpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "YES", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.language.objcpp", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "int", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp storage.type.built-in.primitive.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " hex ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.assignment.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "0x", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.other.unit.hexadecimal.objcpp", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #09885A", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #09885A", + "hc_black": "keyword.other.unit: #B5CEA8" + } + }, + { + "c": "FEF1F0F", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.hexadecimal.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\t ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "float", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp storage.type.built-in.primitive.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": " ing ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.assignment.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "3", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.decimal.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ".", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.decimal.point.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "14", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.decimal.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\t ing ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.assignment.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "3", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.decimal.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ".", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.decimal.point.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "14", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.decimal.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "e", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.other.unit.exponent.decimal.objcpp", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #09885A", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #09885A", + "hc_black": "keyword.other.unit: #B5CEA8" + } + }, + { + "c": "0", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.exponent.decimal.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\t ing ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.assignment.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "31", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.decimal.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ".", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.decimal.point.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "4", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.decimal.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": "e", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.other.unit.exponent.decimal.objcpp", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #09885A", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #09885A", + "hc_black": "keyword.other.unit: #B5CEA8" + } + }, + { + "c": "-", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.minus.exponent.decimal.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "2", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.numeric.exponent.decimal.objcpp", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #09885A", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #09885A", + "hc_black": "constant.numeric: #B5CEA8" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.section.block.end.bracket.curly.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp punctuation.definition.type.begin.objcpp", + "r": { + "dark_plus": "meta.return-type: #4EC9B0", + "light_plus": "meta.return-type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "meta.return-type: #4EC9B0" + } + }, + { + "c": "id", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp storage.type.id.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp punctuation.definition.type.end.objcpp", + "r": { + "dark_plus": "meta.return-type: #4EC9B0", + "light_plus": "meta.return-type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "meta.return-type: #4EC9B0" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp", + "r": { + "dark_plus": "meta.return-type: #4EC9B0", + "light_plus": "meta.return-type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "meta.return-type: #4EC9B0" + } + }, + { + "c": "initWithParams", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.return-type.objcpp entity.name.function.objcpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": ":", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp entity.name.function.name-of-parameter.objcpp punctuation.separator.arguments.objcpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp punctuation.definition.type.begin.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "id", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp meta.id-with-protocol.objcpp storage.type.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": "<", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp meta.id-with-protocol.objcpp meta.protocol-list.objcpp punctuation.section.scope.begin.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "anObject", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp meta.id-with-protocol.objcpp meta.protocol-list.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp meta.id-with-protocol.objcpp meta.protocol-list.objcpp punctuation.section.scope.end.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp punctuation.definition.type.end.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "aHandler", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp variable.parameter.function.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "withDeviceStateManager", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp entity.name.function.name-of-parameter.objcpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": ":", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp entity.name.function.name-of-parameter.objcpp punctuation.separator.arguments.objcpp", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp punctuation.definition.type.begin.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "id", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp meta.id-with-protocol.objcpp storage.type.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": "<", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp meta.id-with-protocol.objcpp meta.protocol-list.objcpp punctuation.section.scope.begin.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "anotherObject", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp meta.id-with-protocol.objcpp meta.protocol-list.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ">", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp meta.id-with-protocol.objcpp meta.protocol-list.objcpp punctuation.section.scope.end.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp punctuation.definition.type.end.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "deviceStateManager", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.function.objcpp meta.argument-type.objcpp variable.parameter.function.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "{", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.section.block.begin.bracket.curly.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.whitespace.comment.leading.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "//", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp comment.line.double-slash.objcpp punctuation.definition.comment.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " add a tap gesture recognizer", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp comment.line.double-slash.objcpp", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " UITapGestureRecognizer ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "tapGesture ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.assignment.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "UITapGestureRecognizer alloc", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " initWithTarget:self action:", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "@", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.selector.objcpp storage.type.objcpp punctuation.definition.storage.type.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": "selector", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.selector.objcpp storage.type.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": "(", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.selector.objcpp punctuation.definition.storage.type.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "handleTap:", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.selector.objcpp meta.selector.method-name.objcpp support.function.any-method.name-of-parameter.objcpp", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": ")", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp meta.selector.objcpp punctuation.definition.storage.type.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSMutableArray", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp support.class.cocoa.objcpp", + "r": { + "dark_plus": "support.class: #4EC9B0", + "light_plus": "support.class: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.class: #4EC9B0" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "*", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": "gestureRecognizers ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.assignment.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "NSMutableArray", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp support.class.cocoa.objcpp", + "r": { + "dark_plus": "support.class: #4EC9B0", + "light_plus": "support.class: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.class: #4EC9B0" + } + }, + { + "c": " array", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "gestureRecognizers addObject:tapGesture", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.begin.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "gestureRecognizers addObjectsFromArray:", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "scnView", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp variable.other.object.access.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.separator.dot-access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "gestureRecognizers", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp variable.other.member.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "]", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp meta.bracket.square.access.objcpp punctuation.definition.end.bracket.square.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "scnView", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp variable.other.object.access.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ".", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.separator.dot-access.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "gestureRecognizers", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp variable.other.member.objcpp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "=", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.operator.assignment.objcpp", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4" + } + }, + { + "c": " gestureRecognizers", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\t", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "return", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.control.objcpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " tapGesture", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\t", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "return", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp keyword.control.objcpp", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " ", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "nil", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp constant.language.objcpp", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": ";", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.terminator.statement.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "}", + "t": "source.objcpp meta.implementation.objcpp meta.scope.implementation.objcpp meta.function-with-body.objcpp meta.block.objcpp punctuation.section.block.end.bracket.curly.objcpp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "@", + "t": "source.objcpp meta.implementation.objcpp storage.type.objcpp punctuation.definition.storage.type.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": "end", + "t": "source.objcpp meta.implementation.objcpp storage.type.objcpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + } +] \ No newline at end of file diff --git a/extensions/package.json b/extensions/package.json index 2615d1efa9bb..b397a2d852eb 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.5.0-dev.20190522" + "typescript": "3.5.1" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/powershell/package.json b/extensions/powershell/package.json index 399f6fc5601c..232acb1a7d28 100644 --- a/extensions/powershell/package.json +++ b/extensions/powershell/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "contributes": { "languages": [{ diff --git a/extensions/python/package.json b/extensions/python/package.json index 53ca25bd88fe..1553bad31919 100644 --- a/extensions/python/package.json +++ b/extensions/python/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "activationEvents": ["onLanguage:python"], "main": "./out/pythonMain", @@ -12,6 +13,7 @@ "id": "python", "extensions": [ ".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi", ".snakefile", ".smk", ".pyi", ".ipy"], "aliases": [ "Python", "py" ], + "filenames": [ "Snakefile" ], "firstLine": "^#!\\s*/.*\\bpython[0-9.-]*\\b", "configuration": "./language-configuration.json" }], diff --git a/extensions/r/package.json b/extensions/r/package.json index c9b682918b4a..63ff6a6dcbc1 100644 --- a/extensions/r/package.json +++ b/extensions/r/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "scripts": { "update-grammar": "node ../../build/npm/update-grammar.js Ikuyadeu/vscode-R syntax/r.json ./syntaxes/r.tmLanguage.json" diff --git a/extensions/sql/package.json b/extensions/sql/package.json index f0256026d4c9..8c283e972467 100644 --- a/extensions/sql/package.json +++ b/extensions/sql/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "scripts": { "update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json" diff --git a/extensions/theme-abyss/package.json b/extensions/theme-abyss/package.json index 2d6e6962c25e..bdeb6a2a6437 100644 --- a/extensions/theme-abyss/package.json +++ b/extensions/theme-abyss/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "contributes": { "themes": [ diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index 1f7d48782d21..43a3ae648dca 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -86,7 +86,9 @@ "name": "Class name", "scope": [ "entity.name.class", - "entity.name.type" + "entity.name.type", + "entity.name.namespace", + "entity.name.scope-resolution" ], "settings": { "fontStyle": "underline", diff --git a/extensions/theme-defaults/package.json b/extensions/theme-defaults/package.json index fbfc50afa303..39c00d37b727 100644 --- a/extensions/theme-defaults/package.json +++ b/extensions/theme-defaults/package.json @@ -5,6 +5,7 @@ "categories": [ "Themes" ], "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "contributes": { "themes": [ diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index e73cc3a09af2..1898153ba896 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -21,6 +21,8 @@ "support.class", "support.type", "entity.name.type", + "entity.name.namespace", + "entity.name.scope-resolution", "entity.name.class", "storage.type.numeric.go", "storage.type.byte.go", @@ -67,10 +69,10 @@ } }, { - "name": "Control flow keywords", + "name": "Control flow / Special keywords", "scope": [ "keyword.control", - "keyword.operator.new.cpp", + "source.cpp keyword.operator.new", "keyword.operator.delete", "keyword.other.using", "keyword.other.operator" diff --git a/extensions/theme-defaults/themes/hc_black.json b/extensions/theme-defaults/themes/hc_black.json index 870681495dc5..8119256d5f24 100644 --- a/extensions/theme-defaults/themes/hc_black.json +++ b/extensions/theme-defaults/themes/hc_black.json @@ -26,6 +26,8 @@ "support.class", "support.type", "entity.name.type", + "entity.name.namespace", + "entity.name.scope-resolution", "entity.name.class", "storage.type.cs", "storage.type.generic.cs", @@ -65,11 +67,11 @@ } }, { - "name": "Control flow keywords", + "name": "Control flow / Special keywords", "scope": [ "keyword.control", - "keyword.operator.new.cpp", - "keyword.operator.delete.cpp", + "source.cpp keyword.operator.new", + "source.cpp keyword.operator.delete", "keyword.other.using", "keyword.other.operator" ], diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index ce23ed901d68..7138f045d589 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -21,6 +21,8 @@ "support.class", "support.type", "entity.name.type", + "entity.name.namespace", + "entity.name.scope-resolution", "entity.name.class", "storage.type.numeric.go", "storage.type.byte.go", @@ -67,11 +69,11 @@ } }, { - "name": "Control flow keywords", + "name": "Control flow / Special keywords", "scope": [ "keyword.control", - "keyword.operator.new.cpp", - "keyword.operator.delete.cpp", + "source.cpp keyword.operator.new", + "source.cpp keyword.operator.delete", "keyword.other.using", "keyword.other.operator" ], diff --git a/extensions/theme-kimbie-dark/package.json b/extensions/theme-kimbie-dark/package.json index 06682901e027..1031c34d2e7f 100644 --- a/extensions/theme-kimbie-dark/package.json +++ b/extensions/theme-kimbie-dark/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "contributes": { "themes": [ diff --git a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json index fac06fd00d4d..4b0eb5bdfcf8 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -147,7 +147,9 @@ "scope": [ "support.class", "entity.name.class", - "entity.name.type" + "entity.name.type", + "entity.name.namespace", + "entity.name.scope-resolution" ], "settings": { "foreground": "#f06431" diff --git a/extensions/theme-monokai-dimmed/package.json b/extensions/theme-monokai-dimmed/package.json index f64721a898f2..66c4711d9abe 100644 --- a/extensions/theme-monokai-dimmed/package.json +++ b/extensions/theme-monokai-dimmed/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index ed325071b64e..5f5e5cff9165 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -143,7 +143,7 @@ }, { "name": "Class name", - "scope": "entity.name.class, entity.name.type", + "scope": "entity.name.class, entity.name.type, entity.name.namespace, entity.name.scope-resolution", "settings": { "fontStyle": "", "foreground": "#9B0000", @@ -255,7 +255,7 @@ } }, { - "name": "Keyword Control", + "name": "Keyword Control / Special", "scope": [ "keyword.control", "keyword.operator.new.cpp", diff --git a/extensions/theme-monokai/package.json b/extensions/theme-monokai/package.json index 550f22933d33..13b2db10d005 100644 --- a/extensions/theme-monokai/package.json +++ b/extensions/theme-monokai/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index 14d616f61532..c16fa3c55755 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -205,7 +205,7 @@ }, { "name": "Class name", - "scope": "entity.name.type, entity.name.class", + "scope": "entity.name.type, entity.name.class, entity.name.namespace, entity.name.scope-resolution", "settings": { "fontStyle": "underline", "foreground": "#A6E22E" diff --git a/extensions/theme-quietlight/package.json b/extensions/theme-quietlight/package.json index 0620f730886b..0263925eee81 100644 --- a/extensions/theme-quietlight/package.json +++ b/extensions/theme-quietlight/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 67f7caa4da5d..38681612b420 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -125,6 +125,8 @@ "name": "Classes", "scope": [ "entity.name.type", + "entity.name.namespace", + "entity.name.scope-resolution", "entity.other.inherited-class", "support.class" ], diff --git a/extensions/theme-red/package.json b/extensions/theme-red/package.json index 4b4f294fc5a3..ba751a33e42e 100644 --- a/extensions/theme-red/package.json +++ b/extensions/theme-red/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "contributes": { "themes": [ diff --git a/extensions/theme-seti/package.json b/extensions/theme-seti/package.json index 554119b52b85..a9721611a6f3 100644 --- a/extensions/theme-seti/package.json +++ b/extensions/theme-seti/package.json @@ -5,6 +5,7 @@ "displayName": "%displayName%", "description": "%description%", "publisher": "vscode", + "license": "MIT", "icon": "icons/seti-circular-128x128.png", "scripts": { "update": "node ./build/update-icon-theme.js" diff --git a/extensions/theme-solarized-dark/package.json b/extensions/theme-solarized-dark/package.json index bb43708ca1a2..427b50fe4828 100644 --- a/extensions/theme-solarized-dark/package.json +++ b/extensions/theme-solarized-dark/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "contributes": { "themes": [ diff --git a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json index 5605aba721dd..f2ee483bea46 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -72,7 +72,9 @@ "name": "Class name", "scope": [ "entity.name.class", - "entity.name.type" + "entity.name.type", + "entity.name.namespace", + "entity.name.scope-resolution" ], "settings": { "fontStyle": "", diff --git a/extensions/theme-solarized-light/package.json b/extensions/theme-solarized-light/package.json index 1925afaa3944..3afb2b7ed9d6 100644 --- a/extensions/theme-solarized-light/package.json +++ b/extensions/theme-solarized-light/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "contributes": { "themes": [ 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 b94aeb71de83..8cc7e74a7792 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -72,7 +72,9 @@ "name": "Class name", "scope": [ "entity.name.class", - "entity.name.type" + "entity.name.type", + "entity.name.namespace", + "entity.name.scope-resolution" ], "settings": { "foreground": "#268BD2" @@ -219,16 +221,14 @@ "meta.diff.header" ], "settings": { - "background": "#b58900", "fontStyle": "italic", - "foreground": "#E0EDDD" + "foreground": "#268bd2" } }, { "name": "diff: deleted", "scope": "markup.deleted", "settings": { - "background": "#eee8d5", "fontStyle": "", "foreground": "#dc322f" } @@ -237,7 +237,6 @@ "name": "diff: changed", "scope": "markup.changed", "settings": { - "background": "#eee8d5", "fontStyle": "", "foreground": "#cb4b16" } @@ -246,7 +245,6 @@ "name": "diff: inserted", "scope": "markup.inserted", "settings": { - "background": "#eee8d5", "foreground": "#219186" } }, @@ -417,7 +415,7 @@ "tab.activeBackground": "#FDF6E3", "tab.inactiveForeground": "#586E75", "tab.inactiveBackground": "#D3CBB7", - "tab.modifiedBorder": "#cb4b16", + "tab.activeModifiedBorder": "#cb4b16", // "tab.activeBackground": "", // "tab.activeForeground": "", // "tab.inactiveForeground": "", diff --git a/extensions/theme-tomorrow-night-blue/package.json b/extensions/theme-tomorrow-night-blue/package.json index bed66ce548a7..1266ac58ca48 100644 --- a/extensions/theme-tomorrow-night-blue/package.json +++ b/extensions/theme-tomorrow-night-blue/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "contributes": { "themes": [ diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json index 2ca39bf565d1..6c985d1ea5eb 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json @@ -101,7 +101,7 @@ }, { "name": "Class, Support", - "scope": "entity.name.class, entity.name.type, support.type, support.class", + "scope": "entity.name.class, entity.name.type, entity.name.namespace, entity.name.scope-resolution, support.type, support.class", "settings": { "fontStyle": "", "foreground": "#FFEEAD" diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index 517429bb6d76..702e36e66cd2 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -3,6 +3,7 @@ "description": "Colorize tests for VS Code", "version": "0.0.1", "publisher": "vscode", + "license": "MIT", "private": true, "engines": { "vscode": "*" @@ -11,7 +12,7 @@ "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json" }, "devDependencies": { - "@types/node": "^10.12.21", + "@types/node": "^10.14.8", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "vscode": "1.1.5" diff --git a/extensions/vscode-colorize-tests/yarn.lock b/extensions/vscode-colorize-tests/yarn.lock index 46684d06b5ef..368b81f2f961 100644 --- a/extensions/vscode-colorize-tests/yarn.lock +++ b/extensions/vscode-colorize-tests/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@^10.12.21": - version "10.12.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" - integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== ajv@^5.1.0: version "5.3.0" diff --git a/extensions/vscode-test-resolver/package.json b/extensions/vscode-test-resolver/package.json index 6377065d8746..cf87ab6c73b1 100644 --- a/extensions/vscode-test-resolver/package.json +++ b/extensions/vscode-test-resolver/package.json @@ -21,7 +21,7 @@ ], "main": "./out/extension", "devDependencies": { - "@types/node": "^10.12.21", + "@types/node": "^10.14.8", "vscode": "1.1.5" }, "contributes": { diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts index 6d2d78682340..3823f6e33dae 100644 --- a/extensions/vscode-test-resolver/src/extension.ts +++ b/extensions/vscode-test-resolver/src/extension.ts @@ -26,10 +26,11 @@ export function activate(context: vscode.ExtensionContext) { progress.report({ message: 'Starting Test Resolver' }); outputChannel = vscode.window.createOutputChannel('TestResolver'); - let isStarted = false; + let isResolved = false; async function processError(message: string) { outputChannel.appendLine(message); - if (!isStarted) { + if (!isResolved) { + isResolved = true; outputChannel.show(); const result = await vscode.window.showErrorMessage(message, { modal: true }, ...getActions()); @@ -48,7 +49,7 @@ export function activate(context: vscode.ExtensionContext) { if (chr === CharCode.LineFeed) { const match = lastProgressLine.match(/Extension host agent listening on (\d+)/); if (match) { - isStarted = true; + isResolved = true; res(new vscode.ResolvedAuthority('localhost', parseInt(match[1], 10))); // success! } lastProgressLine = ''; @@ -65,47 +66,30 @@ export function activate(context: vscode.ExtensionContext) { return; } - const { updateUrl, commit, quality } = getProductConfiguration(); + const { updateUrl, commit, quality, serverDataFolderName, dataFolderName } = getProductConfiguration(); + const serverCommand = process.platform === 'win32' ? 'server.bat' : 'server.sh'; + const commandArgs = ['--port=0', '--disable-telemetry']; + const env = getNewEnv(); + const remoteDataDir = process.env['TESTRESOLVER_DATA_FOLDER'] || path.join(os.homedir(), serverDataFolderName || `${dataFolderName}-testresolver`); + env['VSCODE_AGENT_FOLDER'] = remoteDataDir; + outputChannel.appendLine(`Using data folder at ${remoteDataDir}`); + if (!commit) { // dev mode const vscodePath = path.resolve(path.join(context.extensionPath, '..', '..')); - const nodeExec = process.platform === 'win32' ? 'node.exe' : 'node'; - const nodePath = path.join(vscodePath, '.build', 'node-remote', nodeExec); - - if (!fs.existsSync(nodePath)) { - try { - progress.report({ message: 'Installing node' }); - outputChannel.appendLine(`Installing node at ${nodePath}`); - cp.execSync(`node ${path.join(vscodePath, 'node_modules/gulp/bin/gulp.js')} node-remote`); - } catch (e) { - processError(`Problem downloading node: ${e.message}`); - - } - } - outputChannel.appendLine(`Using node at ${nodePath}`); - - const env = getNewEnv(); - env['PATH'] = path.join(vscodePath, 'resources', 'server', 'bin') + path.delimiter + env['PATH']; // allow calling code-dev.sh - - outputChannel.appendLine(env['PATH'] || ''); - - extHostProcess = cp.spawn(nodePath, [path.join('out', 'remoteExtensionHostAgent'), '--port=0'], { cwd: vscodePath, env }); + const serverCommandPath = path.join(vscodePath, 'resources', 'server', 'bin-dev', serverCommand); + extHostProcess = cp.spawn(serverCommandPath, commandArgs, { env, cwd: vscodePath }); } else { - const serverBin = path.resolve(os.homedir(), '.vscode-remote', 'bin'); + const serverBin = path.join(remoteDataDir, 'bin'); progress.report({ message: 'Installing VSCode Server' }); const serverLocation = await downloadAndUnzipVSCodeServer(updateUrl, commit, quality, serverBin); outputChannel.appendLine(`Using server build at ${serverLocation}`); - const commandArgs = ['--port=0', '--disable-telemetry']; - - const env = getNewEnv(); - env['PATH'] = path.join(serverLocation, 'bin') + path.delimiter + env['PATH']; // code command for the terminal - - extHostProcess = cp.spawn(path.join(serverLocation, 'server.sh'), commandArgs, { env, cwd: serverLocation }); + extHostProcess = cp.spawn(path.join(serverLocation, serverCommand), commandArgs, { env, cwd: serverLocation }); } extHostProcess.stdout.on('data', (data: Buffer) => processOutput(data.toString())); extHostProcess.stderr.on('data', (data: Buffer) => processOutput(data.toString())); - extHostProcess.on('error', (error: Error) => processError(`remoteExtensionHostAgent failed with error:\n${error.message}`)); - extHostProcess.on('close', (code: number) => processError(`remoteExtensionHostAgent closed unexpectedly.\nError code: ${code}`)); + extHostProcess.on('error', (error: Error) => processError(`server failed with error:\n${error.message}`)); + extHostProcess.on('close', (code: number) => processError(`server closed unexpectedly.\nError code: ${code}`)); }); } @@ -133,7 +117,6 @@ export function activate(context: vscode.ExtensionContext) { outputChannel.show(); } }); - } type ActionItem = (vscode.MessageItem & { execute: () => void; }); @@ -170,6 +153,8 @@ export interface IProductConfiguration { updateUrl: string; commit: string; quality: string; + dataFolderName: string; + serverDataFolderName?: string; } function getProductConfiguration(): IProductConfiguration { diff --git a/extensions/vscode-test-resolver/yarn.lock b/extensions/vscode-test-resolver/yarn.lock index 4cb378177382..925cef2c1428 100644 --- a/extensions/vscode-test-resolver/yarn.lock +++ b/extensions/vscode-test-resolver/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@^10.12.21": - version "10.12.30" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.30.tgz#4c2b4f0015f214f8158a347350481322b3b29b2f" - integrity sha512-nsqTN6zUcm9xtdJiM9OvOJ5EF0kOI8f1Zuug27O/rgtxCRJHGqncSWfCMZUP852dCKPsDsYXGvBhxfRjDBkF5Q== +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== ajv@^6.5.5: version "6.10.0" diff --git a/extensions/xml/package.json b/extensions/xml/package.json index 985266edf908..2e6cc49fd65f 100644 --- a/extensions/xml/package.json +++ b/extensions/xml/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, "contributes": { "languages": [{ diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json index 79e2eecc3164..5b12e0fbc5bf 100644 --- a/extensions/yaml/package.json +++ b/extensions/yaml/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "license": "MIT", "engines": { "vscode": "*" }, diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 659f98aa615d..fd211376d487 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.5.0-dev.20190522: - version "3.5.0-dev.20190522" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.0-dev.20190522.tgz#ade4702c6e599a7e8905d7acaaf416f7e32ba0fc" - integrity sha512-V+QsNMtXl8lGwov4O04D+E6vtiL0TWEpz6iDxI2tAZizEn7y8Hh9SXzz3JRWepr7dfdqqxEv9CT3J18zpaZKJQ== +typescript@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" + integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== diff --git a/package.json b/package.json index 0e50fff810a0..8062ace56346 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "author": { "name": "Microsoft Corporation" }, + "license": "MIT", "main": "./out/main", "private": true, "scripts": { @@ -26,7 +27,8 @@ "strict-null-check": "tsc -p src/tsconfig.strictNullChecks.json", "strict-null-check-watch": "tsc -p src/tsconfig.strictNullChecks.json --watch", "strict-initialization-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictPropertyInitialization", - "web": "node scripts/code-web.js" + "web": "node resources/server/bin-dev/code-web.js", + "install-server": "git fetch distro && git checkout distro/distro -- src/vs/server resources/server && git reset HEAD src/vs/server resources/server" }, "dependencies": { "@angular/animations": "~4.1.3", @@ -59,25 +61,27 @@ "native-keymap": "1.2.5", "native-watchdog": "1.0.0", "ng2-charts": "^1.6.0", - "node-pty": "0.9.0-beta9", + "node-pty": "0.9.0-beta17", + "onigasm-umd": "^2.2.2", "pretty-data": "^0.40.0", "reflect-metadata": "^0.1.8", "rxjs": "5.4.0", "sanitize-html": "^1.19.1", "semver": "^5.5.0", "slickgrid": "github:anthonydresser/SlickGrid#2.3.29", - "spdlog": "0.8.1", + "spdlog": "^0.9.0", "sudo-prompt": "8.2.0", "v8-inspect-profiler": "^0.0.20", "vscode-chokidar": "1.6.5", - "vscode-debugprotocol": "1.34.0", - "vscode-nsfw": "1.1.1", + "vscode-nsfw": "1.1.2", "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.2.5", "vscode-sqlite3": "4.0.7", - "vscode-textmate": "^4.0.1", - "vscode-xterm": "3.14.0-beta3", - "yauzl": "^2.9.1", + "vscode-textmate": "^4.1.1", + "xterm": "3.15.0-beta23", + "xterm-addon-search": "0.1.0-beta5", + "xterm-addon-web-links": "0.1.0-beta9", + "yauzl": "^2.9.2", "yazl": "^2.4.3", "zone.js": "^0.8.4" }, @@ -98,7 +102,6 @@ "ansi-colors": "^3.2.3", "asar": "^0.14.0", "chromium-pickle-js": "^0.2.0", - "clean-css": "3.4.6", "copy-webpack-plugin": "^4.5.2", "coveralls": "^3.0.3", "cson-parser": "^1.3.3", @@ -161,13 +164,14 @@ "tslint": "^5.16.0", "tslint-microsoft-contrib": "^6.0.0", "typemoq": "^0.3.2", - "typescript": "3.4.5", + "typescript": "3.5.1", "typescript-formatter": "7.1.0", "uglify-es": "^3.0.18", "underscore": "^1.8.2", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", "vsce": "1.48.0", + "vscode-debugprotocol": "1.35.0", "vscode-nls-dev": "3.2.5", "webpack": "^4.16.5", "webpack-cli": "^3.1.0", diff --git a/remote/installDevModules.sh b/remote/installDevModules.sh deleted file mode 100755 index 3b8941d0024c..000000000000 --- a/remote/installDevModules.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -set -ex - -# Install Node and Yarn -export NODE_VERSION=10.2.1 -export YARN_VERSION=1.10.1 -curl -fsSLO --compressed "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" -curl -fsSLO --compressed "https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" -tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C "$HOME" --no-same-owner -tar -xzf "yarn-v$YARN_VERSION.tar.gz" -C "$HOME" -mkdir -p "$HOME/bin" -ln -s "$HOME/node-v$NODE_VERSION-linux-x64/bin/node" "$HOME/bin/node" -ln -s "$HOME/yarn-v$YARN_VERSION/bin/yarn" "$HOME/bin/yarn" -ln -s "$HOME/yarn-v$YARN_VERSION/bin/yarnpkg" "$HOME/bin/yarnpkg" -rm "node-v$NODE_VERSION-linux-x64.tar.xz" "yarn-v$YARN_VERSION.tar.gz" - -# Compile native /remote node_modules -PATH="$HOME/bin:$PATH" \ -PYTHON=/usr/bin/python2.7 \ -yarn --ignore-optional diff --git a/remote/installDevPackagesAsRoot.sh b/remote/installDevPackagesAsRoot.sh deleted file mode 100755 index 1df37767c965..000000000000 --- a/remote/installDevPackagesAsRoot.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -set -ex - -# Install libraries and tools -apt-get update -apt-get install -y \ - curl \ - make \ - gcc \ - g++ \ - python2.7 \ - libx11-dev \ - libxkbfile-dev \ - libsecret-1-dev \ - xz-utils -rm -rf /var/lib/apt/lists/* diff --git a/remote/launchDevMode.sh b/remote/launchDevMode.sh deleted file mode 100755 index cd7ab12976a3..000000000000 --- a/remote/launchDevMode.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -set -e - -export NODE_ENV=development -export VSCODE_DEV=1 -export VSCODE_INJECT_NODE_MODULE_LOOKUP_PATH="$HOME/.vscode-remote/bin/dev-remote/node_modules" - -cd $VSCODE_REPO - -if [ -z "$extensions" ] ; then - echo No extensions to install. - mkdir -p /root/.vscode-remote -else - (PATH="$HOME/bin:$PATH" node out/remoteExtensionHostAgent.js ${VSCODE_TELEMETRY_ARG} ${extensions} || true) -fi - -PATH="$HOME/bin:$PATH" node out/remoteExtensionHostAgent.js ${VSCODE_TELEMETRY_ARG} --port $PORT diff --git a/remote/package.json b/remote/package.json index cd8b8c6673e9..34e2855e3caa 100644 --- a/remote/package.json +++ b/remote/package.json @@ -13,13 +13,15 @@ "minimist": "1.2.0", "native-watchdog": "1.0.0", "node-pty": "0.8.1", + "onigasm-umd": "^2.2.2", "semver": "^5.5.0", - "spdlog": "0.8.1", + "spdlog": "^0.9.0", "vscode-chokidar": "1.6.5", "vscode-nsfw": "1.1.1", "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.2.5", - "yauzl": "^2.9.1", + "vscode-textmate": "^4.1.1", + "yauzl": "^2.9.2", "yazl": "^2.4.3" }, "optionalDependencies": { diff --git a/remote/yarn.lock b/remote/yarn.lock index 2df672960ff2..0b32cb56dbe7 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -80,10 +80,12 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" integrity sha1-muuabF6IY4qtFx4Wf1kAq+JINdA= -bindings@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" - integrity sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw== +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" bl@^1.0.0: version "1.2.2" @@ -281,13 +283,18 @@ extract-opts@^3.2.0: editions "^1.1.1" typechecker "^4.3.0" -fd-slicer@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" - integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= dependencies: pend "~1.2.0" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + filename-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775" @@ -597,7 +604,7 @@ nan@2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== -nan@2.8.0, nan@^2.8.0: +nan@2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" integrity sha1-7XFfP+neArV6XmJS2QqWZ14fCFo= @@ -607,6 +614,11 @@ nan@^2.10.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099" integrity sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw== +nan@^2.12.1, nan@^2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" + integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + native-watchdog@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.0.0.tgz#97344e83cd6815a8c8e6c44a52e7be05832e65ca" @@ -683,6 +695,18 @@ once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +onigasm-umd@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257" + integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw== + +oniguruma@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/oniguruma/-/oniguruma-7.1.0.tgz#106ddf7eb42507d0442ac68b187c4f7fdf052c83" + integrity sha512-mV+6HcDNQ38vM8HVKM+MJyXO4EtSigwIZhq023A4rA8Am4dMlGhUkPwudDykExYR45oLrssR/Ep7PZCQ1OM3pA== + dependencies: + nan "^2.12.1" + os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -912,14 +936,14 @@ socks@~2.2.0: ip "^1.1.5" smart-buffer "^4.0.1" -spdlog@0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.8.1.tgz#dfb3f3422ab3efe32be79e4769b95440ed72699f" - integrity sha512-W0s8IOXpn86md+8PJ4mJeB/22thykzH5YaNc3Rgnql4x4/zFIhvNiEx6/a1arnqvmJF0HtRO0Ehlswg0WcwTLQ== +spdlog@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.9.0.tgz#c85dd9d0b9cd385f6f3f5b92dc9d2e1691092b5c" + integrity sha512-AeLWPCYjGi4w5DfpXFKb9pCdgMe4gFBMroGfgwXiNfzwmcNYGoFQkIuD1MChZBR1Iwrx0nGhsTSHFslt/qfTAQ== dependencies: - bindings "^1.3.0" + bindings "^1.5.0" mkdirp "^0.5.1" - nan "^2.8.0" + nan "^2.14.0" string-width@^1.0.1: version "1.0.2" @@ -1079,6 +1103,13 @@ vscode-ripgrep@^1.2.5: resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.2.5.tgz#2093c8f36d52bd2dab9eb45b003dd02533c5499c" integrity sha512-n5XBm9od5hahpljw9T8wbkuMnAY7LlAG1OyEEtcCZEX9aCHFuBKSP0IcvciGRTbtWRovNuT83A2iRjt6PL3bLg== +vscode-textmate@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.1.1.tgz#857e836fbc13a376ec624242437e1747d79610a9" + integrity sha512-xBjq9LH6fMhWDhIVkbKlB1JeCu6lT3FI/QKN24Xi4RKPBUm16IhHTqs6Q6SUGewkNsFZGkb1tJdZsuMnlmVpgw== + dependencies: + oniguruma "^7.0.0" + vscode-windows-ca-certs@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.1.0.tgz#d58eeb40b536130918cfde2b01e6dc7e5c1bd757" @@ -1108,13 +1139,13 @@ xtend@^4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= -yauzl@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f" - integrity sha1-qBmB6nCleUYTOIPwKcWCGok1mn8= +yauzl@^2.9.2: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= dependencies: buffer-crc32 "~0.2.3" - fd-slicer "~1.0.1" + fd-slicer "~1.1.0" yazl@^2.4.3: version "2.4.3" diff --git a/resources/linux/rpm/dependencies.json b/resources/linux/rpm/dependencies.json index d0b64c4fa684..34f127e1ae59 100644 --- a/resources/linux/rpm/dependencies.json +++ b/resources/linux/rpm/dependencies.json @@ -63,83 +63,5 @@ "libxcb.so.1()(64bit)", "libxkbfile.so.1()(64bit)", "libsecret-1.so.0()(64bit)" - ], - "i386": [ - "ld-linux.so.2", - "ld-linux.so.2(GLIBC_2.1)", - "libX11-xcb.so.1", - "libX11.so.6", - "libXcomposite.so.1", - "libXcursor.so.1", - "libXdamage.so.1", - "libXext.so.6", - "libXfixes.so.3", - "libXi.so.6", - "libXrandr.so.2", - "libXrender.so.1", - "libXss.so.1", - "libXtst.so.6", - "libasound.so.2", - "libatk-1.0.so.0", - "libc.so.6", - "libc.so.6(GLIBC_2.0)", - "libc.so.6(GLIBC_2.1)", - "libc.so.6(GLIBC_2.1.3)", - "libc.so.6(GLIBC_2.11)", - "libc.so.6(GLIBC_2.2)", - "libc.so.6(GLIBC_2.2.3)", - "libc.so.6(GLIBC_2.3)", - "libc.so.6(GLIBC_2.3.2)", - "libc.so.6(GLIBC_2.3.4)", - "libc.so.6(GLIBC_2.4)", - "libc.so.6(GLIBC_2.6)", - "libc.so.6(GLIBC_2.7)", - "libcairo.so.2", - "libcups.so.2", - "libdbus-1.so.3", - "libdl.so.2", - "libdl.so.2(GLIBC_2.0)", - "libdl.so.2(GLIBC_2.1)", - "libexpat.so.1", - "libfontconfig.so.1", - "libfreetype.so.6", - "libgcc_s.so.1", - "libgcc_s.so.1(GCC_4.0.0)", - "libgcc_s.so.1(GLIBC_2.0)", - "libgdk-x11-2.0.so.0", - "libgdk_pixbuf-2.0.so.0", - "libgio-2.0.so.0", - "libglib-2.0.so.0", - "libgmodule-2.0.so.0", - "libgobject-2.0.so.0", - "libgtk-3.so.0", - "libm.so.6", - "libm.so.6(GLIBC_2.0)", - "libm.so.6(GLIBC_2.1)", - "libnspr4.so", - "libnss3.so", - "libnssutil3.so", - "libpango-1.0.so.0", - "libpangocairo-1.0.so.0", - "libpthread.so.0", - "libpthread.so.0(GLIBC_2.0)", - "libpthread.so.0(GLIBC_2.1)", - "libpthread.so.0(GLIBC_2.2)", - "libpthread.so.0(GLIBC_2.2.3)", - "libpthread.so.0(GLIBC_2.3.2)", - "libpthread.so.0(GLIBC_2.3.3)", - "librt.so.1", - "librt.so.1(GLIBC_2.2)", - "libsmime3.so", - "libstdc++.so.6", - "libstdc++.so.6(GLIBCXX_3.4)", - "libstdc++.so.6(GLIBCXX_3.4.10)", - "libstdc++.so.6(GLIBCXX_3.4.11)", - "libstdc++.so.6(GLIBCXX_3.4.14)", - "libstdc++.so.6(GLIBCXX_3.4.15)", - "libstdc++.so.6(GLIBCXX_3.4.9)", - "libxcb.so.1", - "libxkbfile.so.1", - "libsecret-1.so.0" ] } \ No newline at end of file diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index 0ff31d69dd28..da01223b00dd 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -6,6 +6,7 @@ COMMIT="@@COMMIT@@" APP_NAME="@@APPNAME@@" QUALITY="@@QUALITY@@" NAME="@@NAME@@" +DATAFOLDER="@@DATAFOLDER@@" VSCODE_PATH="$(dirname "$(dirname "$(realpath "$0")")")" ELECTRON="$VSCODE_PATH/$NAME.exe" if grep -qi Microsoft /proc/version; then @@ -26,7 +27,7 @@ if grep -qi Microsoft /proc/version; then # replace \r\n with \n in WSL_EXT_WLOC WSL_CODE=$(wslpath -u "${WSL_EXT_WLOC%%[[:cntrl:]]}")/scripts/wslCode.sh WIN_CODE_CMD=$(wslpath -w "$VSCODE_PATH/bin/$APP_NAME.cmd") - "$WSL_CODE" $COMMIT $QUALITY "$WIN_CODE_CMD" "$APP_NAME" "$@" + "$WSL_CODE" "$COMMIT" "$QUALITY" "$WIN_CODE_CMD" "$APP_NAME" "$DATAFOLDER" "$@" exit $? else CLI=$(wslpath -m "$VSCODE_PATH/resources/app/out/cli.js") diff --git a/scripts/code-web.js b/scripts/code-web.js deleted file mode 100644 index 772168174504..000000000000 --- a/scripts/code-web.js +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const opn = require('opn'); -const cp = require('child_process'); -const path = require('path'); - -const proc = cp.execFile(path.join(__dirname, process.platform === 'win32' ? 'remoteExtensionAgent.bat' : 'remoteExtensionAgent.sh')); - -let launched = false; -proc.stdout.on("data", data => { - if (!launched && data.toString().indexOf('Extension host agent listening on 8000')) { - launched = true; - - setTimeout(() => { - const url = 'http://127.0.0.1:8000'; - console.log(`Open ${url} in your browser`); - - opn(url); - }, 100); - } -}); \ No newline at end of file diff --git a/scripts/code.sh b/scripts/code.sh index 1f7cda590a1b..b0b112bc237e 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -58,7 +58,7 @@ function code() { function code-wsl() { # in a wsl shell - local WIN_CODE_CLI_CMD=$(wslpath -w "$ROOT/scripts/code-cli.bat") + local WIN_CODE_CLI_CMD=$(wslpath -w "$ROOT/scripts/code-cli.bat" 2>/dev/null) if ! [ -z "$WIN_CODE_CLI_CMD" ]; then local WSL_EXT_ID="ms-vscode-remote.remote-wsl" local WSL_EXT_WLOC=$(cmd.exe /c "$WIN_CODE_CLI_CMD" --locate-extension $WSL_EXT_ID) diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index ff579e873dd8..1e25bf29df71 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -32,6 +32,10 @@ call .\scripts\test.bat --runGlob **\*.integrationTest.js %* call .\scripts\node-electron.bat .\node_modules\mocha\bin\_mocha .\extensions\*\server\out\test\**\*.test.js if %errorlevel% neq 0 exit /b %errorlevel% +if exist ".\resources\server\test\test-remote-integration.bat" ( + call .\resources\server\test\test-remote-integration.bat +) + rmdir /s /q %VSCODEUSERDATADIR% popd diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index cab90665c459..3eb21c4bf97b 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -27,6 +27,10 @@ mkdir -p $ROOT/extensions/emmet/test-fixtures ./scripts/code.sh $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-extensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started rm -rf $ROOT/extensions/emmet/test-fixtures +if [ -f ./resources/server/test/test-remote-integration.sh ]; then + ./resources/server/test/test-remote-integration.sh +fi + # Tests in commonJS cd $ROOT/extensions/css-language-features/server && $ROOT/scripts/node-electron.sh test/index.js cd $ROOT/extensions/html-language-features/server && $ROOT/scripts/node-electron.sh test/index.js diff --git a/src/main.js b/src/main.js index 52ece7e3ff75..62fec18aa446 100644 --- a/src/main.js +++ b/src/main.js @@ -205,7 +205,7 @@ function parseCLIArgs() { function setCurrentWorkingDirectory() { try { if (process.platform === 'win32') { - process.env['VSCODE_CWD'] = process.cwd(); // remember as environment letiable + process.env['VSCODE_CWD'] = process.cwd(); // remember as environment variable process.chdir(path.dirname(app.getPath('exe'))); // always set application folder as cwd } else if (process.env['VSCODE_CWD']) { process.chdir(process.env['VSCODE_CWD']); @@ -347,4 +347,4 @@ function getUserDefinedLocale() { return undefined; }); } -//#endregion \ No newline at end of file +//#endregion diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index 7f77cfbf10dd..264c41ed490a 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -4489,8 +4489,8 @@ declare module 'azdata' { * } * ``` * @export - * @param {NotebookProvider} provider - * @returns {vscode.Disposable} + * @param notebook provider + * @returns disposable */ export function registerNotebookProvider(provider: NotebookProvider): vscode.Disposable; diff --git a/src/sql/base/browser/ui/button/button.ts b/src/sql/base/browser/ui/button/button.ts index 16d89c6569d0..a2e32c0bfafb 100644 --- a/src/sql/base/browser/ui/button/button.ts +++ b/src/sql/base/browser/ui/button/button.ts @@ -18,7 +18,7 @@ export class Button extends vsButton { super(container, options); this._register(DOM.addDisposableListener(this.element, DOM.EventType.FOCUS, () => { - this.element.style.outlineColor = this.buttonFocusOutline ? this.buttonFocusOutline.toString() : null; + this.element.style.outlineColor = this.buttonFocusOutline ? this.buttonFocusOutline.toString() : ''; this.element.style.outlineWidth = '1px'; })); diff --git a/src/sql/base/browser/ui/dropdownList/dropdownList.ts b/src/sql/base/browser/ui/dropdownList/dropdownList.ts index 520053afa1b9..534e3b584232 100644 --- a/src/sql/base/browser/ui/dropdownList/dropdownList.ts +++ b/src/sql/base/browser/ui/dropdownList/dropdownList.ts @@ -41,11 +41,11 @@ export class DropdownList extends Dropdown { if (action) { this.button = new Button(_contentContainer); this.button.label = action.label; - this.toDispose.push(DOM.addDisposableListener(this.button.element, DOM.EventType.CLICK, () => { + this._register(DOM.addDisposableListener(this.button.element, DOM.EventType.CLICK, () => { action.run(); this.hide(); })); - this.toDispose.push(DOM.addDisposableListener(this.button.element, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { + this._register(DOM.addDisposableListener(this.button.element, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { let event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter)) { e.stopPropagation(); @@ -75,7 +75,7 @@ export class DropdownList extends Dropdown { } })); - this.toDispose.push(this._list.onSelectionChange(() => { + this._register(this._list.onSelectionChange(() => { // focus on the dropdown label then hide the dropdown list this.element.focus(); this.hide(); diff --git a/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts b/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts index 66b506609ea0..3c4d1af1bfe7 100644 --- a/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts +++ b/src/sql/base/browser/ui/scrollableSplitview/scrollableSplitview.ts @@ -241,7 +241,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { } this.onRemoveItems(new ArrayIterator([item.view.id!])); }); - const disposable = combinedDisposable([onChangeDisposable, containerDisposable]); + const disposable = combinedDisposable(onChangeDisposable, containerDisposable); const onAdd = view.onAdd ? () => view.onAdd!() : () => { }; const onRemove = view.onRemove ? () => view.onRemove!() : () => { }; @@ -292,7 +292,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { const onEndDisposable = onEnd(this.onSashEnd, this); const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash))); - const disposable = combinedDisposable([onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash]); + const disposable = combinedDisposable(onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash); const sashItem: ISashItem = { sash, disposable }; this.sashItems.splice(currentIndex - 1, 0, sashItem); @@ -344,7 +344,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { } this.onRemoveItems(new ArrayIterator([item.view.id!])); }); - const disposable = combinedDisposable([onChangeDisposable, containerDisposable]); + const disposable = combinedDisposable(onChangeDisposable, containerDisposable); const onAdd = view.onAdd ? () => view.onAdd!() : () => { }; const onRemove = view.onRemove ? () => view.onRemove!() : () => { }; @@ -395,7 +395,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { const onEndDisposable = onEnd(this.onSashEnd, this); const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash))); - const disposable = combinedDisposable([onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash]); + const disposable = combinedDisposable(onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash); const sashItem: ISashItem = { sash, disposable }; this.sashItems.splice(index - 1, 0, sashItem); @@ -527,10 +527,10 @@ export class ScrollableSplitView extends HeightMap implements IDisposable { const index = firstIndex(this.sashItems, item => item.sash === sash); // This way, we can press Alt while we resize a sash, macOS style! - const disposable = combinedDisposable([ + const disposable = combinedDisposable( domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState.current, e.altKey)), domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState.current, false)) - ]); + ); const resetSashDragState = (start: number, alt: boolean) => { const sizes = this.viewItems.map(i => i.size); diff --git a/src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts b/src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts index ae6035f83d77..36af00cdc0bd 100644 --- a/src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts +++ b/src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts @@ -14,7 +14,7 @@ const defaultOptions: IAutoColumnSizeOptions = { autoSizeOnRender: false }; -export class AutoColumnSize implements Slick.Plugin { +export class AutoColumnSize implements Slick.Plugin { private _grid: Slick.Grid; private _$container: JQuery; private _context: CanvasRenderingContext2D; diff --git a/src/sql/base/browser/ui/taskbar/actionbar.ts b/src/sql/base/browser/ui/taskbar/actionbar.ts index 0b48545e7387..86ae2de8349e 100644 --- a/src/sql/base/browser/ui/taskbar/actionbar.ts +++ b/src/sql/base/browser/ui/taskbar/actionbar.ts @@ -43,13 +43,12 @@ export class ActionBar extends ActionRunner implements IActionRunner { super(); this._options = options; this._context = options.context; - this._toDispose = []; if (this._options.actionRunner) { this._actionRunner = this._options.actionRunner; } else { this._actionRunner = new ActionRunner(); - this._toDispose.push(this._actionRunner); + this._register(this._actionRunner); } //this._toDispose.push(this.addEmitter(this._actionRunner)); @@ -365,8 +364,6 @@ export class ActionBar extends ActionRunner implements IActionRunner { lifecycle.dispose(this._items); this._items = []; - this._toDispose = lifecycle.dispose(this._toDispose); - this._domNode.remove(); super.dispose(); diff --git a/src/sql/platform/accounts/browser/accountDialog.ts b/src/sql/platform/accounts/browser/accountDialog.ts index 251738a0a7eb..6a41d7f37e70 100644 --- a/src/sql/platform/accounts/browser/accountDialog.ts +++ b/src/sql/platform/accounts/browser/accountDialog.ts @@ -53,7 +53,7 @@ class AccountPanel extends ViewletPanel { protected renderBody(container: HTMLElement): void { this.accountList = new List(container, new AccountListDelegate(AccountDialog.ACCOUNTLIST_HEIGHT), [this.instantiationService.createInstance(AccountListRenderer)]); - this.disposables.push(attachListStyler(this.accountList, this.themeService)); + this._register(attachListStyler(this.accountList, this.themeService)); } protected layoutBody(size: number): void { diff --git a/src/sql/platform/connection/common/connectionManagementService.ts b/src/sql/platform/connection/common/connectionManagementService.ts index bcd7a50f482c..861a8e8d5a7c 100644 --- a/src/sql/platform/connection/common/connectionManagementService.ts +++ b/src/sql/platform/connection/common/connectionManagementService.ts @@ -43,7 +43,6 @@ import * as platform from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { Event, Emitter } from 'vs/base/common/event'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -51,6 +50,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import * as interfaces from './interfaces'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Memento } from 'vs/workbench/common/memento'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class ConnectionManagementService extends Disposable implements IConnectionManagementService { @@ -70,7 +70,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti private _onConnectRequestSent = new Emitter(); private _onConnectionChanged = new Emitter(); private _onLanguageFlavorChanged = new Emitter(); - private _connectionGlobalStatus = new ConnectionGlobalStatus(this._statusBarService); + private _connectionGlobalStatus = new ConnectionGlobalStatus(this._notificationService); private _mementoContext: Memento; private _mementoObj: any; @@ -86,7 +86,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti @IConfigurationService private _configurationService: IConfigurationService, @ICapabilitiesService private _capabilitiesService: ICapabilitiesService, @IQuickInputService private _quickInputService: IQuickInputService, - @IStatusbarService private _statusBarService: IStatusbarService, + @INotificationService private _notificationService: INotificationService, @IResourceProviderService private _resourceProviderService: IResourceProviderService, @IAngularEventingService private _angularEventing: IAngularEventingService, @IAccountManagementService private _accountManagementService: IAccountManagementService, diff --git a/src/sql/platform/query/common/queryModelService.ts b/src/sql/platform/query/common/queryModelService.ts index c6d81a506dd2..605024c4bbab 100644 --- a/src/sql/platform/query/common/queryModelService.ts +++ b/src/sql/platform/query/common/queryModelService.ts @@ -90,23 +90,31 @@ export class QueryModelService implements IQueryModelService { (platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor( TimeElapsedStatusBarItem, + 'status.timeElapsed', + nls.localize('status.timeElapsed', "Time Elapsed"), StatusbarAlignment.RIGHT, 100 /* Should appear to the right of the SQL editor status */ )); (platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor( RowCountStatusBarItem, + 'status.rowCount', + nls.localize('status.rowCount', "Row Count"), StatusbarAlignment.RIGHT, 100 /* Should appear to the right of the SQL editor status */ )); (platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor( QueryStatusbarItem, + 'status.query', + nls.localize('status.query', "Query"), StatusbarAlignment.RIGHT, 100 /* High Priority */ )); (platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor( SqlFlavorStatusbarItem, + 'status.sqlFlavor', + nls.localize('status.sqlFlavor', "SQL Flavor"), StatusbarAlignment.RIGHT, 90 /* Should appear to the right of the SQL editor status */ )); diff --git a/src/sql/workbench/api/browser/extensionHost.contribution.common.ts b/src/sql/workbench/api/browser/extensionHost.contribution.common.ts new file mode 100644 index 000000000000..c9b3d20ef24a --- /dev/null +++ b/src/sql/workbench/api/browser/extensionHost.contribution.common.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './mainThreadAccountManagement'; +import './mainThreadBackgroundTaskManagement'; +import './mainThreadConnectionManagement'; +import './mainThreadCredentialManagement'; +import './mainThreadDashboard'; +import './mainThreadDashboardWebview'; +import './mainThreadDataProtocol'; +import './mainThreadExtensionManagement'; +import './mainThreadModalDialog'; +import './mainThreadModelView'; +import './mainThreadModelViewDialog'; +import './mainThreadNotebook'; +import './mainThreadNotebookDocumentsAndEditors'; +import './mainThreadObjectExplorer'; +import './mainThreadQueryEditor'; +import './mainThreadResourceProvider'; +import './mainThreadSerializationProvider'; +import './mainThreadTasks'; diff --git a/src/sql/workbench/api/node/mainThreadAccountManagement.ts b/src/sql/workbench/api/browser/mainThreadAccountManagement.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadAccountManagement.ts rename to src/sql/workbench/api/browser/mainThreadAccountManagement.ts diff --git a/src/sql/workbench/api/node/mainThreadBackgroundTaskManagement.ts b/src/sql/workbench/api/browser/mainThreadBackgroundTaskManagement.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadBackgroundTaskManagement.ts rename to src/sql/workbench/api/browser/mainThreadBackgroundTaskManagement.ts diff --git a/src/sql/workbench/api/node/mainThreadConnectionManagement.ts b/src/sql/workbench/api/browser/mainThreadConnectionManagement.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadConnectionManagement.ts rename to src/sql/workbench/api/browser/mainThreadConnectionManagement.ts diff --git a/src/sql/workbench/api/node/mainThreadCredentialManagement.ts b/src/sql/workbench/api/browser/mainThreadCredentialManagement.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadCredentialManagement.ts rename to src/sql/workbench/api/browser/mainThreadCredentialManagement.ts diff --git a/src/sql/workbench/api/electron-browser/mainThreadDashboard.ts b/src/sql/workbench/api/browser/mainThreadDashboard.ts similarity index 100% rename from src/sql/workbench/api/electron-browser/mainThreadDashboard.ts rename to src/sql/workbench/api/browser/mainThreadDashboard.ts diff --git a/src/sql/workbench/api/node/mainThreadDashboardWebview.ts b/src/sql/workbench/api/browser/mainThreadDashboardWebview.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadDashboardWebview.ts rename to src/sql/workbench/api/browser/mainThreadDashboardWebview.ts diff --git a/src/sql/workbench/api/node/mainThreadDataProtocol.ts b/src/sql/workbench/api/browser/mainThreadDataProtocol.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadDataProtocol.ts rename to src/sql/workbench/api/browser/mainThreadDataProtocol.ts diff --git a/src/sql/workbench/api/node/mainThreadExtensionManagement.ts b/src/sql/workbench/api/browser/mainThreadExtensionManagement.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadExtensionManagement.ts rename to src/sql/workbench/api/browser/mainThreadExtensionManagement.ts diff --git a/src/sql/workbench/api/electron-browser/mainThreadModalDialog.ts b/src/sql/workbench/api/browser/mainThreadModalDialog.ts similarity index 100% rename from src/sql/workbench/api/electron-browser/mainThreadModalDialog.ts rename to src/sql/workbench/api/browser/mainThreadModalDialog.ts diff --git a/src/sql/workbench/api/node/mainThreadModelView.ts b/src/sql/workbench/api/browser/mainThreadModelView.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadModelView.ts rename to src/sql/workbench/api/browser/mainThreadModelView.ts diff --git a/src/sql/workbench/api/node/mainThreadModelViewDialog.ts b/src/sql/workbench/api/browser/mainThreadModelViewDialog.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadModelViewDialog.ts rename to src/sql/workbench/api/browser/mainThreadModelViewDialog.ts diff --git a/src/sql/workbench/api/node/mainThreadNotebook.ts b/src/sql/workbench/api/browser/mainThreadNotebook.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadNotebook.ts rename to src/sql/workbench/api/browser/mainThreadNotebook.ts diff --git a/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts rename to src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts diff --git a/src/sql/workbench/api/node/mainThreadObjectExplorer.ts b/src/sql/workbench/api/browser/mainThreadObjectExplorer.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadObjectExplorer.ts rename to src/sql/workbench/api/browser/mainThreadObjectExplorer.ts diff --git a/src/sql/workbench/api/node/mainThreadQueryEditor.ts b/src/sql/workbench/api/browser/mainThreadQueryEditor.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadQueryEditor.ts rename to src/sql/workbench/api/browser/mainThreadQueryEditor.ts diff --git a/src/sql/workbench/api/node/mainThreadResourceProvider.ts b/src/sql/workbench/api/browser/mainThreadResourceProvider.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadResourceProvider.ts rename to src/sql/workbench/api/browser/mainThreadResourceProvider.ts diff --git a/src/sql/workbench/api/node/mainThreadSerializationProvider.ts b/src/sql/workbench/api/browser/mainThreadSerializationProvider.ts similarity index 100% rename from src/sql/workbench/api/node/mainThreadSerializationProvider.ts rename to src/sql/workbench/api/browser/mainThreadSerializationProvider.ts diff --git a/src/sql/workbench/api/electron-browser/mainThreadTasks.ts b/src/sql/workbench/api/browser/mainThreadTasks.ts similarity index 100% rename from src/sql/workbench/api/electron-browser/mainThreadTasks.ts rename to src/sql/workbench/api/browser/mainThreadTasks.ts diff --git a/src/vs/code/code.main.ts b/src/sql/workbench/api/electron-browser/extensionHost.contribution.ts similarity index 85% rename from src/vs/code/code.main.ts rename to src/sql/workbench/api/electron-browser/extensionHost.contribution.ts index 7cbce7528ed8..4c24e9f73406 100644 --- a/src/vs/code/code.main.ts +++ b/src/sql/workbench/api/electron-browser/extensionHost.contribution.ts @@ -3,4 +3,4 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/platform/update/node/update.config.contribution'; \ No newline at end of file +import '../browser/extensionHost.contribution.common'; diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts index bb50c5d952c2..58bf6d43b0c8 100644 --- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts @@ -40,8 +40,7 @@ import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { ExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures'; -import { AzureResource } from 'sql/platform/accounts/common/interfaces'; +import { IURITransformer } from 'vs/base/common/uriIpc'; import { mssqlProviderName } from 'sql/platform/connection/common/constants'; export interface ISqlExtensionApiFactory { @@ -61,10 +60,9 @@ export function createApiFactory( extensionService: ExtHostExtensionService, logService: ExtHostLogService, extHostStorage: ExtHostStorage, - schemeTransformer: ISchemeTransformer | null, - outputChannelName: string + uriTransformer: IURITransformer | null ): ISqlExtensionApiFactory { - let vsCodeFactory = extHostApi.createApiFactory(initData, rpcProtocol, extHostWorkspace, extHostConfiguration, extensionService, logService, extHostStorage, schemeTransformer, outputChannelName); + let vsCodeFactory = extHostApi.createApiFactory(initData, rpcProtocol, extHostWorkspace, extHostConfiguration, extensionService, logService, extHostStorage, uriTransformer); // Addressable instances const extHostAccountManagement = rpcProtocol.set(SqlExtHostContext.ExtHostAccountManagement, new ExtHostAccountManagement(rpcProtocol)); diff --git a/src/sql/workbench/api/node/sqlExtHost.contribution.ts b/src/sql/workbench/api/node/sqlExtHost.contribution.ts deleted file mode 100644 index c3795ef7ba5a..000000000000 --- a/src/sql/workbench/api/node/sqlExtHost.contribution.ts +++ /dev/null @@ -1,46 +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 { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; - -// --- SQL contributions -import 'sql/workbench/api/node/mainThreadConnectionManagement'; -import 'sql/workbench/api/node/mainThreadCredentialManagement'; -import 'sql/workbench/api/node/mainThreadDataProtocol'; -import 'sql/workbench/api/node/mainThreadObjectExplorer'; -import 'sql/workbench/api/node/mainThreadBackgroundTaskManagement'; -import 'sql/workbench/api/node/mainThreadSerializationProvider'; -import 'sql/workbench/api/node/mainThreadResourceProvider'; -import 'sql/workbench/api/electron-browser/mainThreadTasks'; -import 'sql/workbench/api/electron-browser/mainThreadDashboard'; -import 'sql/workbench/api/node/mainThreadDashboardWebview'; -import 'sql/workbench/api/node/mainThreadQueryEditor'; -import 'sql/workbench/api/node/mainThreadModelView'; -import 'sql/workbench/api/node/mainThreadModelViewDialog'; -import 'sql/workbench/api/node/mainThreadNotebook'; -import 'sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors'; -import 'sql/workbench/api/node/mainThreadAccountManagement'; -import 'sql/workbench/api/node/mainThreadExtensionManagement'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; - -export class SqlExtHostContribution implements IWorkbenchContribution { - - constructor( - @IInstantiationService private instantiationService: IInstantiationService - ) { - } - - public getId(): string { - return 'sql.api.sqlExtHost'; - } -} - -// Register File Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( - SqlExtHostContribution, - LifecyclePhase.Restored -); diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/customView.ts index 3fc97e8fe6bb..5bcd1c40539e 100644 --- a/src/sql/workbench/browser/parts/views/customView.ts +++ b/src/sql/workbench/browser/parts/views/customView.ts @@ -14,7 +14,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ViewContainer, ITreeItemLabel } from 'vs/workbench/common/views'; import { FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IProgressService2 } from 'vs/platform/progress/common/progress'; +import { IProgressService } from 'vs/platform/progress/common/progress'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -160,7 +160,7 @@ export class CustomTreeView extends Disposable implements ITreeView { @IInstantiationService private instantiationService: IInstantiationService, @ICommandService private commandService: ICommandService, @IConfigurationService private configurationService: IConfigurationService, - @IProgressService2 private progressService: IProgressService2 + @IProgressService private progressService: IProgressService ) { super(); this.root = new Root(); @@ -516,7 +516,7 @@ class TreeDataSource implements IDataSource { private treeView: ITreeView, private container: ViewContainer, private id: string, - @IProgressService2 private progressService: IProgressService2, + @IProgressService private progressService: IProgressService, @IOEShimService private objectExplorerService: IOEShimService ) { } diff --git a/src/sql/workbench/electron-browser/modelComponents/diffeditor.component.ts b/src/sql/workbench/electron-browser/modelComponents/diffeditor.component.ts index c5b6338d262f..0545623ee295 100644 --- a/src/sql/workbench/electron-browser/modelComponents/diffeditor.component.ts +++ b/src/sql/workbench/electron-browser/modelComponents/diffeditor.component.ts @@ -18,7 +18,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/componentBase'; import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/electron-browser/modelComponents/interfaces'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { SimpleProgressService } from 'vs/editor/standalone/browser/simpleServices'; +import { SimpleLocalProgressService } from 'vs/editor/standalone/browser/simpleServices'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -70,7 +70,7 @@ export default class DiffEditorComponent extends ComponentBase implements ICompo } private _createEditor(): void { - this._instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleProgressService()])); + this._instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleLocalProgressService()])); this._editor = this._instantiationService.createInstance(TextDiffEditor); this._editor.reverseColoring(); this._editor.create(this._el.nativeElement); diff --git a/src/sql/workbench/electron-browser/modelComponents/editor.component.ts b/src/sql/workbench/electron-browser/modelComponents/editor.component.ts index a901eb9ae8f5..bace82f53b0c 100644 --- a/src/sql/workbench/electron-browser/modelComponents/editor.component.ts +++ b/src/sql/workbench/electron-browser/modelComponents/editor.component.ts @@ -21,7 +21,7 @@ import { ComponentBase } from 'sql/workbench/electron-browser/modelComponents/co import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces'; import { QueryTextEditor } from 'sql/workbench/electron-browser/modelComponents/queryTextEditor'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { SimpleProgressService } from 'vs/editor/standalone/browser/simpleServices'; +import { SimpleLocalProgressService } from 'vs/editor/standalone/browser/simpleServices'; import { IProgressService } from 'vs/platform/progress/common/progress'; @Component({ @@ -59,7 +59,7 @@ export default class EditorComponent extends ComponentBase implements IComponent } private _createEditor(): void { - let instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleProgressService()])); + let instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleLocalProgressService()])); this._editor = instantiationService.createInstance(QueryTextEditor); this._editor.create(this._el.nativeElement); this._editor.setVisible(true); diff --git a/src/sql/workbench/parts/connection/browser/connection.contribution.ts b/src/sql/workbench/parts/connection/browser/connection.contribution.ts index 227bdd3d1651..556b5c078c2c 100644 --- a/src/sql/workbench/parts/connection/browser/connection.contribution.ts +++ b/src/sql/workbench/parts/connection/browser/connection.contribution.ts @@ -19,6 +19,8 @@ import { ConnectionStatusbarItem } from 'sql/workbench/parts/connection/browser/ // Register Statusbar item (Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor( ConnectionStatusbarItem, + 'status.connection', + localize('status.connection', "Connection"), StatusbarAlignment.RIGHT, 100 /* High Priority */ )); diff --git a/src/sql/workbench/parts/connection/browser/connectionStatus.ts b/src/sql/workbench/parts/connection/browser/connectionStatus.ts index 8355534d138a..a8ec25ce1f49 100644 --- a/src/sql/workbench/parts/connection/browser/connectionStatus.ts +++ b/src/sql/workbench/parts/connection/browser/connectionStatus.ts @@ -40,7 +40,7 @@ export class ConnectionStatusbarItem implements IStatusbarItem { this._objectExplorerService.onSelectionOrFocusChange(() => this._updateStatus()) ); - return combinedDisposable(this._toDispose); + return combinedDisposable(...this._toDispose); } // Update the connection status shown in the bar diff --git a/src/sql/workbench/parts/connection/common/connectionGlobalStatus.ts b/src/sql/workbench/parts/connection/common/connectionGlobalStatus.ts index 856008d7d106..3f846fd3b68b 100644 --- a/src/sql/workbench/parts/connection/common/connectionGlobalStatus.ts +++ b/src/sql/workbench/parts/connection/common/connectionGlobalStatus.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { ConnectionSummary } from 'azdata'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import * as LocalizedConstants from 'sql/workbench/parts/connection/common/localizedConstants'; +import { INotificationService } from 'vs/platform/notification/common/notification'; // Status when making connections from the viewlet export class ConnectionGlobalStatus { @@ -12,12 +12,12 @@ export class ConnectionGlobalStatus { private _displayTime: number = 5000; // (in ms) constructor( - @IStatusbarService private _statusBarService: IStatusbarService + @INotificationService private _notificationService: INotificationService ) { } public setStatusToConnected(connectionSummary: ConnectionSummary): void { - if (this._statusBarService) { + if (this._notificationService) { let text: string; let connInfo: string = connectionSummary.serverName; if (connInfo) { @@ -28,13 +28,13 @@ export class ConnectionGlobalStatus { } text = LocalizedConstants.onDidConnectMessage + ' ' + connInfo; } - this._statusBarService.setStatusMessage(text, this._displayTime); + this._notificationService.status(text, { hideAfter: this._displayTime }); } } public setStatusToDisconnected(fileUri: string): void { - if (this._statusBarService) { - this._statusBarService.setStatusMessage(LocalizedConstants.onDidDisconnectMessage, this._displayTime); + if (this._notificationService) { + this._notificationService.status(LocalizedConstants.onDidDisconnectMessage, { hideAfter: this._displayTime }); } } } diff --git a/src/sql/workbench/parts/dashboard/widgets/explorer/explorerTree.ts b/src/sql/workbench/parts/dashboard/widgets/explorer/explorerTree.ts index f7bb8fcb5b20..f0ce1a64542d 100644 --- a/src/sql/workbench/parts/dashboard/widgets/explorer/explorerTree.ts +++ b/src/sql/workbench/parts/dashboard/widgets/explorer/explorerTree.ts @@ -32,7 +32,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { $ } from 'vs/base/browser/dom'; import { ExecuteCommandAction } from 'vs/platform/actions/common/actions'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; export class ObjectMetadataWrapper implements ObjectMetadata { public metadataType: MetadataType; @@ -111,7 +111,7 @@ export class ExplorerController extends TreeDefaults.DefaultController { private _contextMenuService: IContextMenuService, private _capabilitiesService: ICapabilitiesService, private _instantiationService: IInstantiationService, - private _progressService: IProgressService + private _progressService: ILocalProgressService ) { super(); } @@ -424,7 +424,7 @@ class ExplorerScriptSelectAction extends ScriptSelectAction { @IQueryEditorService queryEditorService: IQueryEditorService, @IConnectionManagementService connectionManagementService: IConnectionManagementService, @IScriptingService scriptingService: IScriptingService, - @IProgressService private progressService: IProgressService + @ILocalProgressService private progressService: ILocalProgressService ) { super(id, label, queryEditorService, connectionManagementService, scriptingService); } @@ -443,7 +443,7 @@ class ExplorerScriptCreateAction extends ScriptCreateAction { @IConnectionManagementService connectionManagementService: IConnectionManagementService, @IScriptingService scriptingService: IScriptingService, @IErrorMessageService errorMessageService: IErrorMessageService, - @IProgressService private progressService: IProgressService + @ILocalProgressService private progressService: ILocalProgressService ) { super(id, label, queryEditorService, connectionManagementService, scriptingService, errorMessageService); } @@ -462,7 +462,7 @@ class ExplorerScriptAlterAction extends ScriptAlterAction { @IConnectionManagementService connectionManagementService: IConnectionManagementService, @IScriptingService scriptingService: IScriptingService, @IErrorMessageService errorMessageService: IErrorMessageService, - @IProgressService private progressService: IProgressService + @ILocalProgressService private progressService: ILocalProgressService ) { super(id, label, queryEditorService, connectionManagementService, scriptingService, errorMessageService); } @@ -481,7 +481,7 @@ class ExplorerScriptExecuteAction extends ScriptExecuteAction { @IConnectionManagementService connectionManagementService: IConnectionManagementService, @IScriptingService scriptingService: IScriptingService, @IErrorMessageService errorMessageService: IErrorMessageService, - @IProgressService private progressService: IProgressService + @ILocalProgressService private progressService: ILocalProgressService ) { super(id, label, queryEditorService, connectionManagementService, scriptingService, errorMessageService); } @@ -498,7 +498,7 @@ class ExplorerManageAction extends ManageAction { id: string, label: string, @IConnectionManagementService connectionManagementService: IConnectionManagementService, @IAngularEventingService angularEventingService: IAngularEventingService, - @IProgressService private _progressService: IProgressService + @ILocalProgressService private _progressService: ILocalProgressService ) { super(id, label, connectionManagementService, angularEventingService); } diff --git a/src/sql/workbench/parts/dashboard/widgets/explorer/explorerWidget.component.ts b/src/sql/workbench/parts/dashboard/widgets/explorer/explorerWidget.component.ts index ff2c11dccafb..ea05edf86e60 100644 --- a/src/sql/workbench/parts/dashboard/widgets/explorer/explorerWidget.component.ts +++ b/src/sql/workbench/parts/dashboard/widgets/explorer/explorerWidget.component.ts @@ -7,7 +7,7 @@ import 'vs/css!sql/media/objectTypes/objecttypes'; import 'vs/css!sql/media/icons/common-icons'; import 'vs/css!./media/explorerWidget'; -import { Component, Inject, forwardRef, ChangeDetectorRef, OnInit, ViewChild, ElementRef } from '@angular/core'; +import { Component, Inject, forwardRef, OnInit, ViewChild, ElementRef } from '@angular/core'; import { Router } from '@angular/router'; import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/workbench/parts/dashboard/common/dashboardWidget'; @@ -26,7 +26,7 @@ import { Delayer } from 'vs/base/common/async'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; @Component({ @@ -58,7 +58,6 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget, constructor( @Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface, @Inject(forwardRef(() => Router)) private _router: Router, - @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, @Inject(WIDGET_CONFIG) protected _config: WidgetConfig, @Inject(forwardRef(() => ElementRef)) private _el: ElementRef, @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, @@ -66,7 +65,7 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget, @Inject(IInstantiationService) private instantiationService: IInstantiationService, @Inject(IContextMenuService) private contextMenuService: IContextMenuService, @Inject(ICapabilitiesService) private capabilitiesService: ICapabilitiesService, - @Inject(IProgressService) private progressService: IProgressService + @Inject(ILocalProgressService) private progressService: ILocalProgressService ) { super(); this.init(); diff --git a/src/sql/workbench/parts/dataExplorer/browser/connectionViewletPanel.ts b/src/sql/workbench/parts/dataExplorer/browser/connectionViewletPanel.ts index 1053120c1dcf..371558c659cc 100644 --- a/src/sql/workbench/parts/dataExplorer/browser/connectionViewletPanel.ts +++ b/src/sql/workbench/parts/dataExplorer/browser/connectionViewletPanel.ts @@ -128,7 +128,6 @@ export class ConnectionViewletPanel extends ViewletPanel { } dispose(): void { - this.disposables = dispose(this.disposables); super.dispose(); } diff --git a/src/sql/workbench/parts/dataExplorer/electron-browser/nodeCommands.ts b/src/sql/workbench/parts/dataExplorer/electron-browser/nodeCommands.ts index 7e70a2ff5c65..6db9cc1ee77e 100644 --- a/src/sql/workbench/parts/dataExplorer/electron-browser/nodeCommands.ts +++ b/src/sql/workbench/parts/dataExplorer/electron-browser/nodeCommands.ts @@ -12,7 +12,7 @@ import { ICustomViewDescriptor, TreeViewItemHandleArg } from 'sql/workbench/comm import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IViewsRegistry, Extensions } from 'vs/workbench/common/views'; -import { IProgressService2 } from 'vs/platform/progress/common/progress'; +import { IProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; export const DISCONNECT_COMMAND_ID = 'dataExplorer.disconnect'; @@ -82,7 +82,7 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: REFRESH_COMMAND_ID, handler: (accessor, args: TreeViewItemHandleArg) => { - const progressSerivce = accessor.get(IProgressService2); + const progressSerivce = accessor.get(IProgressService); if (args.$treeItem) { const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(args.$treeViewId)); if (args.$treeContainerId) { diff --git a/src/sql/workbench/parts/editData/common/editDataInput.ts b/src/sql/workbench/parts/editData/common/editDataInput.ts index f19cc624a8d7..aacd37284d3c 100644 --- a/src/sql/workbench/parts/editData/common/editDataInput.ts +++ b/src/sql/workbench/parts/editData/common/editDataInput.ts @@ -56,12 +56,11 @@ export class EditDataInput extends EditorInput implements IConnectableInput { this._setup = false; this._stopButtonEnabled = false; this._refreshButtonEnabled = false; - this._toDispose = []; this._useQueryFilter = false; // re-emit sql editor events through this editor if it exists if (this._sql) { - this._toDispose.push(this._sql.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + this._register(this._sql.onDidChangeDirty(() => this._onDidChangeDirty.fire())); this._sql.disableSaving(); } this.disableSaving(); @@ -74,7 +73,7 @@ export class EditDataInput extends EditorInput implements IConnectableInput { let self = this; // Register callbacks for the Actions - this._toDispose.push( + this._register( this._queryModelService.onRunQueryStart(uri => { if (self.uri === uri) { self.initEditStart(); @@ -82,7 +81,7 @@ export class EditDataInput extends EditorInput implements IConnectableInput { }) ); - this._toDispose.push( + this._register( this._queryModelService.onEditSessionReady((result) => { if (self.uri === result.ownerUri) { self.initEditEnd(result); @@ -210,7 +209,6 @@ export class EditDataInput extends EditorInput implements IConnectableInput { this._queryModelService.disposeQuery(this.uri); this._sql.dispose(); this._results.dispose(); - this._toDispose = dispose(this._toDispose); super.dispose(); } diff --git a/src/sql/workbench/parts/notebook/cellViews/code.component.ts b/src/sql/workbench/parts/notebook/cellViews/code.component.ts index f7743f29c7ed..c16ae0d136e2 100644 --- a/src/sql/workbench/parts/notebook/cellViews/code.component.ts +++ b/src/sql/workbench/parts/notebook/cellViews/code.component.ts @@ -17,7 +17,7 @@ import { NotebookModel } from 'sql/workbench/parts/notebook/models/notebookModel import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as themeColors from 'vs/workbench/common/theme'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { SimpleProgressService } from 'vs/editor/standalone/browser/simpleServices'; +import { SimpleLocalProgressService } from 'vs/editor/standalone/browser/simpleServices'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; @@ -189,7 +189,7 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange } private async createEditor(): Promise { - let instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleProgressService()])); + let instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleLocalProgressService()])); this._editor = instantiationService.createInstance(QueryTextEditor); this._editor.create(this.codeElement.nativeElement); this._editor.setVisible(true); diff --git a/src/sql/workbench/parts/notebook/notebookStyles.ts b/src/sql/workbench/parts/notebook/notebookStyles.ts index 2ca4003a3791..599faa6ca231 100644 --- a/src/sql/workbench/parts/notebook/notebookStyles.ts +++ b/src/sql/workbench/parts/notebook/notebookStyles.ts @@ -7,8 +7,8 @@ import 'vs/css!./notebook'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, buttonBackground, textLinkForeground, textLinkActiveForeground, textPreformatForeground, textBlockQuoteBackground, textBlockQuoteBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IDisposable } from 'vscode-xterm'; import { editorLineHighlight, editorLineHighlightBorder } from 'vs/editor/common/view/editorColorRegistry'; +import { IDisposable } from 'vs/base/common/lifecycle'; export function registerNotebookThemes(overrideEditorThemeSetting: boolean): IDisposable { return registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { diff --git a/src/sql/workbench/parts/objectExplorer/browser/treeSelectionHandler.ts b/src/sql/workbench/parts/objectExplorer/browser/treeSelectionHandler.ts index 46f51eec5037..7e19f64d6b5a 100644 --- a/src/sql/workbench/parts/objectExplorer/browser/treeSelectionHandler.ts +++ b/src/sql/workbench/parts/objectExplorer/browser/treeSelectionHandler.ts @@ -8,7 +8,7 @@ import { ITree } from 'vs/base/parts/tree/browser/tree'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; -import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; +import { IProgressService, IProgressRunner, ILocalProgressService } from 'vs/platform/progress/common/progress'; import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode'; import { TreeUpdateUtils } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils'; @@ -18,7 +18,7 @@ export class TreeSelectionHandler { private _clicks: number = 0; private _doubleClickTimeoutTimer: NodeJS.Timer = undefined; - constructor(@IProgressService private _progressService: IProgressService) { + constructor(@ILocalProgressService private _progressService: ILocalProgressService) { } diff --git a/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts b/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts index 2146077d7fec..e05e45e64848 100644 --- a/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts +++ b/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts @@ -271,7 +271,7 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll : localize('ProfilerTableEditor.eventCount', 'Events: {0}', this._input.data.getLength()); this._disposeStatusbarItem(); - this._statusbarItem = this._statusbarService.addEntry({ text: message }, StatusbarAlignment.RIGHT); + this._statusbarItem = this._statusbarService.addEntry({ text: message }, 'status.eventCount', localize('status.eventCount', "Event Count"), StatusbarAlignment.RIGHT); } } diff --git a/src/sql/workbench/parts/query/browser/flavorStatus.ts b/src/sql/workbench/parts/query/browser/flavorStatus.ts index 624bf8689324..5eb5e19fd6c3 100644 --- a/src/sql/workbench/parts/query/browser/flavorStatus.ts +++ b/src/sql/workbench/parts/query/browser/flavorStatus.ts @@ -87,7 +87,7 @@ export class SqlFlavorStatusbarItem implements IStatusbarItem { this._editorService.onDidVisibleEditorsChange(() => this._onEditorsChanged()), this._editorService.onDidCloseEditor(event => this._onEditorClosed(event)) ); - return combinedDisposable(this._toDispose); + return combinedDisposable(...this._toDispose); } private _onSelectionClick() { diff --git a/src/sql/workbench/parts/query/browser/queryEditor.ts b/src/sql/workbench/parts/query/browser/queryEditor.ts index e377c37bb258..c9c7782940dc 100644 --- a/src/sql/workbench/parts/query/browser/queryEditor.ts +++ b/src/sql/workbench/parts/query/browser/queryEditor.ts @@ -156,7 +156,7 @@ export class QueryEditor extends BaseEditor { this.setTaskbarContent(); - this._toDispose.push(this.configurationService.onDidChangeConfiguration(e => { + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectedKeys.includes('workbench.enablePreviewFeatures')) { this.setTaskbarContent(); } diff --git a/src/sql/workbench/parts/query/browser/queryStatus.ts b/src/sql/workbench/parts/query/browser/queryStatus.ts index a4785963e5e7..fcb5311d8d1d 100644 --- a/src/sql/workbench/parts/query/browser/queryStatus.ts +++ b/src/sql/workbench/parts/query/browser/queryStatus.ts @@ -47,7 +47,7 @@ export class QueryStatusbarItem implements IStatusbarItem { this._editorService.onDidCloseEditor(event => this._onEditorClosed(event)) ); - return combinedDisposable(this._toDispose); + return combinedDisposable(...this._toDispose); } private _onEditorClosed(event: IEditorCloseEvent): void { diff --git a/src/sql/workbench/parts/query/browser/rowCountStatus.ts b/src/sql/workbench/parts/query/browser/rowCountStatus.ts index f781963087a2..38a9b72187b4 100644 --- a/src/sql/workbench/parts/query/browser/rowCountStatus.ts +++ b/src/sql/workbench/parts/query/browser/rowCountStatus.ts @@ -40,7 +40,7 @@ export class RowCountStatusBarItem implements IStatusbarItem { this._showStatus(); - return combinedDisposable(disposables); + return combinedDisposable(...disposables); } private _onEditorsChanged() { diff --git a/src/sql/workbench/parts/query/browser/timeElapsedStatus.ts b/src/sql/workbench/parts/query/browser/timeElapsedStatus.ts index 43a123879640..cd03216e35ad 100644 --- a/src/sql/workbench/parts/query/browser/timeElapsedStatus.ts +++ b/src/sql/workbench/parts/query/browser/timeElapsedStatus.ts @@ -43,7 +43,7 @@ export class TimeElapsedStatusBarItem implements IStatusbarItem { this._showStatus(); - return combinedDisposable(disposables); + return combinedDisposable(...disposables); } private _onEditorsChanged() { diff --git a/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts b/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts index 81b7bf2a0a4b..ec9226402dfa 100644 --- a/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts +++ b/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts @@ -69,6 +69,8 @@ export class AccountManagementService implements IAccountManagementService { // Register status bar item let statusbarDescriptor = new statusbar.StatusbarItemDescriptor( AccountListStatusbarItem, + 'status.accountList', + localize('status.accountList', "Account List"), StatusbarAlignment.LEFT, 15000 /* Highest Priority */ ); diff --git a/src/sql/workbench/services/commandLine/common/commandLineService.ts b/src/sql/workbench/services/commandLine/common/commandLineService.ts index 2558f0fcdd37..d65717cc5a36 100644 --- a/src/sql/workbench/services/commandLine/common/commandLineService.ts +++ b/src/sql/workbench/services/commandLine/common/commandLineService.ts @@ -22,11 +22,11 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { ipcRenderer as ipc } from 'electron'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { localize } from 'vs/nls'; import { QueryInput } from 'sql/workbench/parts/query/common/queryInput'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class CommandLineService implements ICommandLineProcessing { public _serviceBrand: any; @@ -40,7 +40,7 @@ export class CommandLineService implements ICommandLineProcessing { @IEditorService private _editorService: IEditorService, @ICommandService private _commandService: ICommandService, @IConfigurationService private _configurationService: IConfigurationService, - @IStatusbarService private _statusBarService: IStatusbarService, + @INotificationService private _notificationService: INotificationService, @ILogService private logService: ILogService ) { if (ipc) { @@ -92,8 +92,8 @@ export class CommandLineService implements ICommandLineProcessing { } let connectedContext: azdata.ConnectedContext = undefined; if (profile) { - if (this._statusBarService) { - this._statusBarService.setStatusMessage(localize('connectingLabel', 'Connecting:') + profile.serverName, 2500); + if (this._notificationService) { + this._notificationService.status(localize('connectingLabel', 'Connecting: {0}', profile.serverName), { hideAfter: 2500 }); } try { await this._connectionManagementService.connectIfNotConnected(profile, 'connection', true); @@ -106,8 +106,8 @@ export class CommandLineService implements ICommandLineProcessing { } } if (commandName) { - if (this._statusBarService) { - this._statusBarService.setStatusMessage(localize('runningCommandLabel', 'Running command:') + commandName, 2500); + if (this._notificationService) { + this._notificationService.status(localize('runningCommandLabel', 'Running command: {0}', commandName), { hideAfter: 2500 }); } await this._commandService.executeCommand(commandName, connectedContext); } else if (profile) { @@ -119,8 +119,8 @@ export class CommandLineService implements ICommandLineProcessing { } else { // Default to showing new query - if (this._statusBarService) { - this._statusBarService.setStatusMessage(localize('openingNewQueryLabel', 'Opening new query:') + profile.serverName, 2500); + if (this._notificationService) { + this._notificationService.status(localize('openingNewQueryLabel', 'Opening new query: {0}', profile.serverName), { hideAfter: 2500 }); } try { await TaskUtilities.newQuery(profile, @@ -150,8 +150,8 @@ export class CommandLineService implements ICommandLineProcessing { showConnectionDialogOnError: warnOnConnectFailure, showFirewallRuleOnError: warnOnConnectFailure }; - if (this._statusBarService) { - this._statusBarService.setStatusMessage(localize('connectingQueryLabel', 'Connecting query file'), 2500); + if (this._notificationService) { + this._notificationService.status(localize('connectingQueryLabel', 'Connecting query file'), { hideAfter: 2500 }); } await this._connectionManagementService.connect(profile, uriString, options); } diff --git a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts index 5e103556da71..0d5a0d939749 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts @@ -451,10 +451,6 @@ export class ConnectionDialogWidget extends Modal { this.onProviderTypeSelected(displayName); } - public dispose(): void { - this._toDispose.forEach(obj => obj.dispose()); - } - public set databaseDropdownExpanded(val: boolean) { this._databaseDropdownExpanded = val; } diff --git a/src/sql/workbench/services/insights/test/common/insightsUtils.test.ts b/src/sql/workbench/services/insights/test/common/insightsUtils.test.ts index 6fd373643e13..3fc9de5f46ba 100644 --- a/src/sql/workbench/services/insights/test/common/insightsUtils.test.ts +++ b/src/sql/workbench/services/insights/test/common/insightsUtils.test.ts @@ -45,7 +45,8 @@ class TestEnvironmentService implements IWorkbenchEnvironmentService { appNameLong: string; appQuality?: string; appSettingsHome: string; - appSettingsPath: string; + + settingsResource: URI; appKeybindingsPath: string; settingsSearchBuildId?: number; settingsSearchUrl?: string; diff --git a/src/sqltest/workbench/api/extHostAccountManagement.test.ts b/src/sqltest/workbench/api/extHostAccountManagement.test.ts index d09743d5237a..7d1b613e0e32 100644 --- a/src/sqltest/workbench/api/extHostAccountManagement.test.ts +++ b/src/sqltest/workbench/api/extHostAccountManagement.test.ts @@ -12,7 +12,7 @@ import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCP import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { SqlMainContext } from 'sql/workbench/api/node/sqlExtHost.protocol'; -import { MainThreadAccountManagement } from 'sql/workbench/api/node/mainThreadAccountManagement'; +import { MainThreadAccountManagement } from 'sql/workbench/api/browser/mainThreadAccountManagement'; import { IAccountManagementService, AzureResource } from 'sql/platform/accounts/common/interfaces'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/sqltest/workbench/api/extHostCredentialManagement.test.ts b/src/sqltest/workbench/api/extHostCredentialManagement.test.ts index 9668980dabbb..a6a7d3c5fad2 100644 --- a/src/sqltest/workbench/api/extHostCredentialManagement.test.ts +++ b/src/sqltest/workbench/api/extHostCredentialManagement.test.ts @@ -9,7 +9,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { ExtHostCredentialManagement } from 'sql/workbench/api/node/extHostCredentialManagement'; import { SqlMainContext } from 'sql/workbench/api/node/sqlExtHost.protocol'; import { IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import { MainThreadCredentialManagement } from 'sql/workbench/api/node/mainThreadCredentialManagement'; +import { MainThreadCredentialManagement } from 'sql/workbench/api/browser/mainThreadCredentialManagement'; import { CredentialsTestProvider, CredentialsTestService } from 'sqltest/stubs/credentialsTestStubs'; import { ICredentialsService } from 'sql/platform/credentials/common/credentialsService'; import { Credential, CredentialProvider } from 'azdata'; diff --git a/src/sqltest/workbench/api/mainThreadBackgroundTaskManagement.test.ts b/src/sqltest/workbench/api/mainThreadBackgroundTaskManagement.test.ts index 078d66771a73..656039a6a689 100644 --- a/src/sqltest/workbench/api/mainThreadBackgroundTaskManagement.test.ts +++ b/src/sqltest/workbench/api/mainThreadBackgroundTaskManagement.test.ts @@ -5,7 +5,7 @@ import * as azdata from 'azdata'; import { Mock, It, Times } from 'typemoq'; -import { MainThreadBackgroundTaskManagement, TaskStatus } from 'sql/workbench/api/node/mainThreadBackgroundTaskManagement'; +import { MainThreadBackgroundTaskManagement, TaskStatus } from 'sql/workbench/api/browser/mainThreadBackgroundTaskManagement'; import { ExtHostBackgroundTaskManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol'; import { ITaskService } from 'sql/platform/tasks/common/tasksService'; import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; diff --git a/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts b/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts index 5d3676eef929..694461c91b7a 100644 --- a/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts +++ b/src/sqltest/workbench/api/mainThreadModelViewDialog.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Mock, It, Times } from 'typemoq'; -import { MainThreadModelViewDialog } from 'sql/workbench/api/node/mainThreadModelViewDialog'; +import { MainThreadModelViewDialog } from 'sql/workbench/api/browser/mainThreadModelViewDialog'; import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { IModelViewButtonDetails, IModelViewTabDetails, IModelViewDialogDetails, IModelViewWizardPageDetails, IModelViewWizardDetails, DialogMessage, MessageLevel } from 'sql/workbench/api/common/sqlExtHostTypes'; import { CustomDialogService } from 'sql/platform/dialog/customDialogService'; diff --git a/src/sqltest/workbench/api/mainThreadNotebook.test.ts b/src/sqltest/workbench/api/mainThreadNotebook.test.ts index 45f039684f10..94878a045141 100644 --- a/src/sqltest/workbench/api/mainThreadNotebook.test.ts +++ b/src/sqltest/workbench/api/mainThreadNotebook.test.ts @@ -12,7 +12,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostNotebookShape } from 'sql/workbench/api/node/sqlExtHost.protocol'; -import { MainThreadNotebook } from 'sql/workbench/api/node/mainThreadNotebook'; +import { MainThreadNotebook } from 'sql/workbench/api/browser/mainThreadNotebook'; import { NotebookService } from 'sql/workbench/services/notebook/common/notebookServiceImpl'; import { INotebookProvider } from 'sql/workbench/services/notebook/common/notebookService'; import { INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails } from 'sql/workbench/api/common/sqlExtHostTypes'; diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts index 20e58bcd7304..59163a017cc3 100644 --- a/src/typings/electron.d.ts +++ b/src/typings/electron.d.ts @@ -1,4 +1,4 @@ -// Type definitions for Electron 3.1.8 +// Type definitions for Electron 4.2.3 // Project: http://electronjs.org/ // Definitions by: The Electron Team // Definitions: https://github.com/electron/electron-typescript-definitions @@ -86,7 +86,7 @@ declare namespace Electron { webviewTag: WebviewTag; } - interface AllElectron extends MainInterface, RendererInterface {} + interface AllElectron extends MainInterface, RendererInterface { } const app: App; const autoUpdater: AutoUpdater; @@ -119,7 +119,7 @@ declare namespace Electron { interface App extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/app + // Docs: http://electronjs.org/docs/api/app /** * Emitted when Chrome's accessibility support changes. This event fires when @@ -472,6 +472,100 @@ declare namespace Electron { once(event: 'ready', listener: (launchInfo: any) => void): this; addListener(event: 'ready', listener: (launchInfo: any) => void): this; removeListener(event: 'ready', listener: (launchInfo: any) => void): this; + /** + * Emitted when remote.getBuiltin() is called in the renderer process of + * webContents. Calling event.preventDefault() will prevent the module from being + * returned. Custom value can be returned by setting event.returnValue. + */ + on(event: 'remote-get-builtin', listener: (event: Event, + webContents: WebContents, + moduleName: string) => void): this; + once(event: 'remote-get-builtin', listener: (event: Event, + webContents: WebContents, + moduleName: string) => void): this; + addListener(event: 'remote-get-builtin', listener: (event: Event, + webContents: WebContents, + moduleName: string) => void): this; + removeListener(event: 'remote-get-builtin', listener: (event: Event, + webContents: WebContents, + moduleName: string) => void): this; + /** + * Emitted when remote.getCurrentWebContents() is called in the renderer process of + * webContents. Calling event.preventDefault() will prevent the object from being + * returned. Custom value can be returned by setting event.returnValue. + */ + on(event: 'remote-get-current-web-contents', listener: (event: Event, + webContents: WebContents) => void): this; + once(event: 'remote-get-current-web-contents', listener: (event: Event, + webContents: WebContents) => void): this; + addListener(event: 'remote-get-current-web-contents', listener: (event: Event, + webContents: WebContents) => void): this; + removeListener(event: 'remote-get-current-web-contents', listener: (event: Event, + webContents: WebContents) => void): this; + /** + * Emitted when remote.getCurrentWindow() is called in the renderer process of + * webContents. Calling event.preventDefault() will prevent the object from being + * returned. Custom value can be returned by setting event.returnValue. + */ + on(event: 'remote-get-current-window', listener: (event: Event, + webContents: WebContents) => void): this; + once(event: 'remote-get-current-window', listener: (event: Event, + webContents: WebContents) => void): this; + addListener(event: 'remote-get-current-window', listener: (event: Event, + webContents: WebContents) => void): this; + removeListener(event: 'remote-get-current-window', listener: (event: Event, + webContents: WebContents) => void): this; + /** + * Emitted when remote.getGlobal() is called in the renderer process of + * webContents. Calling event.preventDefault() will prevent the global from being + * returned. Custom value can be returned by setting event.returnValue. + */ + on(event: 'remote-get-global', listener: (event: Event, + webContents: WebContents, + globalName: string) => void): this; + once(event: 'remote-get-global', listener: (event: Event, + webContents: WebContents, + globalName: string) => void): this; + addListener(event: 'remote-get-global', listener: (event: Event, + webContents: WebContents, + globalName: string) => void): this; + removeListener(event: 'remote-get-global', listener: (event: Event, + webContents: WebContents, + globalName: string) => void): this; + /** + * Emitted when .getWebContents() is called in the renderer process of + * webContents. Calling event.preventDefault() will prevent the object from being + * returned. Custom value can be returned by setting event.returnValue. + */ + on(event: 'remote-get-guest-web-contents', listener: (event: Event, + webContents: WebContents, + guestWebContents: WebContents) => void): this; + once(event: 'remote-get-guest-web-contents', listener: (event: Event, + webContents: WebContents, + guestWebContents: WebContents) => void): this; + addListener(event: 'remote-get-guest-web-contents', listener: (event: Event, + webContents: WebContents, + guestWebContents: WebContents) => void): this; + removeListener(event: 'remote-get-guest-web-contents', listener: (event: Event, + webContents: WebContents, + guestWebContents: WebContents) => void): this; + /** + * Emitted when remote.require() is called in the renderer process of webContents. + * Calling event.preventDefault() will prevent the module from being returned. + * Custom value can be returned by setting event.returnValue. + */ + on(event: 'remote-require', listener: (event: Event, + webContents: WebContents, + moduleName: string) => void): this; + once(event: 'remote-require', listener: (event: Event, + webContents: WebContents, + moduleName: string) => void): this; + addListener(event: 'remote-require', listener: (event: Event, + webContents: WebContents, + moduleName: string) => void): this; + removeListener(event: 'remote-require', listener: (event: Event, + webContents: WebContents, + moduleName: string) => void): this; /** * This event will be emitted inside the primary instance of your application when * a second instance has been executed. argv is an Array of the second instance's @@ -690,6 +784,11 @@ declare namespace Electron { * is ready. */ enableMixedSandbox(): void; + /** + * Enables full sandbox mode on the app. This method can only be called before app + * is ready. + */ + enableSandbox(): void; /** * Exits immediately with exitCode. exitCode defaults to 0. All windows will be * closed immediately without asking user and the before-quit and will-quit events @@ -709,13 +808,22 @@ declare namespace Electron { * Fetches a path's associated icon. On Windows, there a 2 kinds of icons: On Linux * and macOS, icons depend on the application associated with file mime type. */ - getFileIcon(path: string, options: FileIconOptions, callback: (error: Error, icon: NativeImage) => void): void; + getFileIcon(path: string, callback: (error: Error, icon: NativeImage) => void): void; /** * Fetches a path's associated icon. On Windows, there a 2 kinds of icons: On Linux * and macOS, icons depend on the application associated with file mime type. */ - getFileIcon(path: string, callback: (error: Error, icon: NativeImage) => void): void; + getFileIcon(path: string, options: FileIconOptions, callback: (error: Error, icon: NativeImage) => void): void; getGPUFeatureStatus(): GPUFeatureStatus; + /** + * For infoType equal to complete: Promise is fulfilled with Object containing all + * the GPU Information as in chromium's GPUInfo object. This includes the version + * and driver information that's shown on chrome://gpu page. For infoType equal to + * basic: Promise is fulfilled with Object containing fewer attributes than when + * requested with complete. Here's an example of basic response: Using basic should + * be preferred if only basic information like vendorId or driverId is needed. + */ + getGPUInfo(infoType: string): Promise; getJumpListSettings(): JumpListSettings; /** * To set the locale, you'll want to use a command line switch at app startup, @@ -754,7 +862,7 @@ declare namespace Electron { /** * Imports the certificate in pkcs12 format into the platform certificate store. * callback is called with the result of import operation, a value of 0 indicates - * success while any other value indicates failure according to chromium + * success while any other value indicates failure according to Chromium * net_error_list. */ importCertificate(options: ImportCertificateOptions, callback: (result: number) => void): void; @@ -843,9 +951,9 @@ declare namespace Electron { setAboutPanelOptions(options: AboutPanelOptionsOptions): void; /** * Manually enables Chrome's accessibility support, allowing to expose - * accessibility switch to users in application settings. - * https://www.chromium.org/developers/design-documents/accessibility for more - * details. Disabled by default. Note: Rendering accessibility tree can + * accessibility switch to users in application settings. See Chromium's + * accessibility docs for more details. Disabled by default. This API must be + * called after the ready event is emitted. Note: Rendering accessibility tree can * significantly affect the performance of your app. It should not be enabled by * default. */ @@ -929,7 +1037,12 @@ declare namespace Electron { */ show(): void; /** - * Start accessing a security scoped resource. With this method electron + * Show the about panel with the values defined in the app's .plist file or with + * the options set via app.setAboutPanelOptions(options). + */ + showAboutPanel(): void; + /** + * Start accessing a security scoped resource. With this method Electron * applications that are packaged for the Mac App Store may reach outside their * sandbox to access files chosen by the user. See Apple's documentation for a * description of how this system works. @@ -940,7 +1053,7 @@ declare namespace Electron { * userInfo into its current userInfo dictionary. */ updateCurrentActivity(type: string, userInfo: any): void; - whenReady(): Promise; + whenReady(): Promise; commandLine: CommandLine; dock: Dock; /** @@ -949,11 +1062,19 @@ declare namespace Electron { * production environments. */ isPackaged?: boolean; + /** + * A String which is the user agent string Electron will use as a global fallback. + * This is the user agent that will be used when no user agent is set at the + * webContents or session level. Useful for ensuring your entire app has the same + * user agent. Set to a custom value as early as possible in your apps + * initialization to ensure that your overridden value is used. + */ + userAgentFallback?: string; } interface AutoUpdater extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/auto-updater + // Docs: http://electronjs.org/docs/api/auto-updater /** * This event is emitted after a user calls quitAndInstall(). When this API is @@ -990,7 +1111,9 @@ declare namespace Electron { removeListener(event: 'update-available', listener: Function): this; /** * Emitted when an update has been downloaded. On Windows only releaseName is - * available. + * available. Note: It is not strictly necessary to handle this event. A + * successfully downloaded update will still be applied the next time the + * application starts. */ on(event: 'update-downloaded', listener: (event: Event, releaseNotes: string, @@ -1029,10 +1152,10 @@ declare namespace Electron { * Restarts the app and installs the update after it has been downloaded. It should * only be called after update-downloaded has been emitted. Under the hood calling * autoUpdater.quitAndInstall() will close all application windows first, and - * automatically call app.quit() after all windows have been closed. Note: If the - * application is quit without calling this API after the update-downloaded event - * has been emitted, the application will still be replaced by the updated one on - * the next run. + * automatically call app.quit() after all windows have been closed. Note: It is + * not strictly necessary to call this function to apply an update, as a + * successfully downloaded update will always be applied the next time the + * application starts. */ quitAndInstall(): void; /** @@ -1043,7 +1166,7 @@ declare namespace Electron { interface BluetoothDevice { - // Docs: http://electron.atom.io/docs/api/structures/bluetooth-device + // Docs: http://electronjs.org/docs/api/structures/bluetooth-device deviceId: string; deviceName: string; @@ -1051,11 +1174,11 @@ declare namespace Electron { class BrowserView extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/browser-view + // Docs: http://electronjs.org/docs/api/browser-view constructor(options?: BrowserViewConstructorOptions); static fromId(id: number): BrowserView; - static fromWebContents(webContents: WebContents): BrowserView | null; + static fromWebContents(webContents: WebContents): (BrowserView) | (null); static getAllViews(): BrowserView[]; /** * Force closing the view, the unload and beforeunload events won't be emitted for @@ -1076,8 +1199,19 @@ declare namespace Electron { class BrowserWindow extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/browser-window + // Docs: http://electronjs.org/docs/api/browser-window + /** + * Emitted when the window is set or unset to show always on top of other windows. + */ + on(event: 'always-on-top-changed', listener: (event: Event, + isAlwaysOnTop: boolean) => void): this; + once(event: 'always-on-top-changed', listener: (event: Event, + isAlwaysOnTop: boolean) => void): this; + addListener(event: 'always-on-top-changed', listener: (event: Event, + isAlwaysOnTop: boolean) => void): this; + removeListener(event: 'always-on-top-changed', listener: (event: Event, + isAlwaysOnTop: boolean) => void): this; /** * Emitted when an App Command is invoked. These are typically related to keyboard * media keys or browser commands, as well as the "Back" button built into some @@ -1207,13 +1341,13 @@ declare namespace Electron { * prevent the native window's title from changing. */ on(event: 'page-title-updated', listener: (event: Event, - title: string) => void): this; + title: string, explicitSet: boolean) => void): this; once(event: 'page-title-updated', listener: (event: Event, - title: string) => void): this; + title: string, explicitSet: boolean) => void): this; addListener(event: 'page-title-updated', listener: (event: Event, - title: string) => void): this; + title: string, explicitSet: boolean) => void): this; removeListener(event: 'page-title-updated', listener: (event: Event, - title: string) => void): this; + title: string, explicitSet: boolean) => void): this; /** * Emitted when the web page has been rendered (while not being shown) and window * can be displayed without a visual flash. @@ -1223,7 +1357,7 @@ declare namespace Electron { addListener(event: 'ready-to-show', listener: Function): this; removeListener(event: 'ready-to-show', listener: Function): this; /** - * Emitted when the window is being resized. + * Emitted after the window has been resized. */ on(event: 'resize', listener: Function): this; once(event: 'resize', listener: Function): this; @@ -1318,6 +1452,58 @@ declare namespace Electron { once(event: 'unresponsive', listener: Function): this; addListener(event: 'unresponsive', listener: Function): this; removeListener(event: 'unresponsive', listener: Function): this; + /** + * Emitted before the window is moved. Calling event.preventDefault() will prevent + * the window from being moved. Note that this is only emitted when the window is + * being resized manually. Resizing the window with setBounds/setSize will not emit + * this event. + */ + on(event: 'will-move', listener: (event: Event, + /** + * ` Location the window is being moved to. + */ + newBounds: Rectangle) => void): this; + once(event: 'will-move', listener: (event: Event, + /** + * ` Location the window is being moved to. + */ + newBounds: Rectangle) => void): this; + addListener(event: 'will-move', listener: (event: Event, + /** + * ` Location the window is being moved to. + */ + newBounds: Rectangle) => void): this; + removeListener(event: 'will-move', listener: (event: Event, + /** + * ` Location the window is being moved to. + */ + newBounds: Rectangle) => void): this; + /** + * Emitted before the window is resized. Calling event.preventDefault() will + * prevent the window from being resized. Note that this is only emitted when the + * window is being resized manually. Resizing the window with setBounds/setSize + * will not emit this event. + */ + on(event: 'will-resize', listener: (event: Event, + /** + * ` Size the window is being resized to. + */ + newBounds: Rectangle) => void): this; + once(event: 'will-resize', listener: (event: Event, + /** + * ` Size the window is being resized to. + */ + newBounds: Rectangle) => void): this; + addListener(event: 'will-resize', listener: (event: Event, + /** + * ` Size the window is being resized to. + */ + newBounds: Rectangle) => void): this; + removeListener(event: 'will-resize', listener: (event: Event, + /** + * ` Size the window is being resized to. + */ + newBounds: Rectangle) => void): this; constructor(options?: BrowserWindowConstructorOptions); /** * Adds DevTools extension located at path, and returns extension's name. The @@ -1335,7 +1521,7 @@ declare namespace Electron { * This API cannot be called before the ready event of the app module is emitted. */ static addExtension(path: string): void; - static fromBrowserView(browserView: BrowserView): BrowserWindow | null; + static fromBrowserView(browserView: BrowserView): (BrowserWindow) | (null); static fromId(id: number): BrowserWindow; static fromWebContents(webContents: WebContents): BrowserWindow; static getAllWindows(): BrowserWindow[]; @@ -1349,7 +1535,7 @@ declare namespace Electron { * emitted. */ static getExtensions(): Extensions; - static getFocusedWindow(): BrowserWindow | null; + static getFocusedWindow(): (BrowserWindow) | (null); /** * Remove a DevTools extension by name. Note: This API cannot be called before the * ready event of the app module is emitted. @@ -1411,7 +1597,7 @@ declare namespace Electron { * Note: The BrowserView API is currently experimental and may change or be removed * in future Electron releases. */ - getBrowserView(): BrowserView | null; + getBrowserView(): (BrowserView) | (null); getChildWindows(): BrowserWindow[]; getContentBounds(): Rectangle; getContentSize(): number[]; @@ -1422,6 +1608,13 @@ declare namespace Electron { * (unsigned long) on Linux. */ getNativeWindowHandle(): Buffer; + /** + * Note: whatever the current state of the window : maximized, minimized or in + * fullscreen, this function always returns the position and size of the window in + * normal state. In normal state, getBounds and getNormalBounds returns the same + * Rectangle. + */ + getNormalBounds(): Rectangle; getOpacity(): number; getParentWindow(): BrowserWindow; getPosition(): number[]; @@ -1473,6 +1666,7 @@ declare namespace Electron { * On Linux always returns true. */ isMovable(): boolean; + isNormal(): boolean; isResizable(): boolean; isSimpleFullScreen(): boolean; isVisible(): boolean; @@ -1485,7 +1679,7 @@ declare namespace Electron { * Same as webContents.loadFile, filePath should be a path to an HTML file relative * to the root of your application. See the webContents docs for more information. */ - loadFile(filePath: string): void; + loadFile(filePath: string, options?: LoadFileOptions): void; /** * Same as webContents.loadURL(url[, options]). The url can be a remote address * (e.g. http://) or a path to a local HTML file using the file:// protocol. To @@ -1579,7 +1773,12 @@ declare namespace Electron { */ setAutoHideMenuBar(hide: boolean): void; /** - * Resizes and moves the window to the supplied bounds + * Sets the background color of the window. See Setting backgroundColor. + */ + setBackgroundColor(backgroundColor: string): void; + /** + * Resizes and moves the window to the supplied bounds. Any properties that are not + * supplied will default to their current values. */ setBounds(bounds: Rectangle, animate?: boolean): void; setBrowserView(browserView: BrowserView): void; @@ -1655,7 +1854,7 @@ declare namespace Electron { * Sets the menu as the window's menu bar, setting it to null will remove the menu * bar. */ - setMenu(menu: Menu | null): void; + setMenu(menu: (Menu) | (null)): void; /** * Sets whether the menu bar should be visible. If the menu bar is auto-hide, users * can still bring up the menu bar by pressing the single Alt key. @@ -1682,7 +1881,7 @@ declare namespace Electron { * Sets a 16 x 16 pixel overlay onto the current taskbar icon, usually used to * convey some sort of application status or to passively notify the user. */ - setOverlayIcon(overlay: NativeImage | null, description: string): void; + setOverlayIcon(overlay: (NativeImage) | (null), description: string): void; /** * Sets parent as current window's parent window, passing null will turn current * window into a top-level window. @@ -1754,7 +1953,8 @@ declare namespace Electron { /** * Sets the region of the window to show as the thumbnail image displayed when * hovering over the window in the taskbar. You can reset the thumbnail to be the - * entire window by specifying an empty region: {x: 0, y: 0, width: 0, height: 0}. + * entire window by specifying an empty region: { x: 0, y: 0, width: 0, height: 0 + * }. */ setThumbnailClip(region: Rectangle): void; /** @@ -1782,7 +1982,12 @@ declare namespace Electron { * Sets whether the window should be visible on all workspaces. Note: This API does * nothing on Windows. */ - setVisibleOnAllWorkspaces(visible: boolean): void; + setVisibleOnAllWorkspaces(visible: boolean, options?: VisibleOnAllWorkspacesOptions): void; + /** + * Sets whether the window traffic light buttons should be visible. This cannot be + * called when titleBarStyle is set to customButtonsOnHover. + */ + setWindowButtonVisibility(visible: boolean): void; /** * Shows and gives focus to the window. */ @@ -1818,7 +2023,7 @@ declare namespace Electron { class BrowserWindowProxy extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/browser-window-proxy + // Docs: http://electronjs.org/docs/api/browser-window-proxy /** * Removes focus from the child window. @@ -1851,7 +2056,7 @@ declare namespace Electron { interface Certificate { - // Docs: http://electron.atom.io/docs/api/structures/certificate + // Docs: http://electronjs.org/docs/api/structures/certificate /** * PEM encoded data @@ -1897,37 +2102,37 @@ declare namespace Electron { interface CertificatePrincipal { - // Docs: http://electron.atom.io/docs/api/structures/certificate-principal + // Docs: http://electronjs.org/docs/api/structures/certificate-principal /** - * Common Name + * Common Name. */ commonName: string; /** - * Country or region + * Country or region. */ country: string; /** - * Locality + * Locality. */ locality: string; /** - * Organization names + * Organization names. */ organizations: string[]; /** - * Organization Unit names + * Organization Unit names. */ organizationUnits: string[]; /** - * State or province + * State or province. */ state: string; } class ClientRequest extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/client-request + // Docs: http://electronjs.org/docs/api/client-request /** * Emitted when the request is aborted. The abort event will not be fired if the @@ -2045,7 +2250,7 @@ declare namespace Electron { * Sends the last chunk of the request data. Subsequent write or end operations * will not be allowed. The finish event is emitted just after the end operation. */ - end(chunk?: string | Buffer, encoding?: string, callback?: Function): void; + end(chunk?: (string) | (Buffer), encoding?: string, callback?: Function): void; /** * Continues any deferred redirection request when the redirection mode is manual. */ @@ -2078,13 +2283,13 @@ declare namespace Electron { * issued on the wire. After the first write operation, it is not allowed to add or * remove a custom header. */ - write(chunk: string | Buffer, encoding?: string, callback?: Function): void; + write(chunk: (string) | (Buffer), encoding?: string, callback?: Function): void; chunkedEncoding: boolean; } interface Clipboard extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/clipboard + // Docs: http://electronjs.org/docs/api/clipboard availableFormats(type?: string): string[]; /** @@ -2144,7 +2349,7 @@ declare namespace Electron { interface ContentTracing extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/content-tracing + // Docs: http://electronjs.org/docs/api/content-tracing /** * Get the current monitoring traced data. Child processes typically cache trace @@ -2181,7 +2386,7 @@ declare namespace Electron { * request. The callback will be called once all child processes have acknowledged * the startRecording request. */ - startRecording(options: TraceCategoriesAndOptions | TraceConfig, callback: Function): void; + startRecording(options: (TraceCategoriesAndOptions) | (TraceConfig), callback: Function): void; /** * Stop monitoring on all processes. Once all child processes have acknowledged the * stopMonitoring request the callback is called. @@ -2203,10 +2408,11 @@ declare namespace Electron { interface Cookie { - // Docs: http://electron.atom.io/docs/api/structures/cookie + // Docs: http://electronjs.org/docs/api/structures/cookie /** - * The domain of the cookie. + * The domain of the cookie; this will be normalized with a preceding dot so that + * it's also valid for subdomains. */ domain?: string; /** @@ -2215,7 +2421,8 @@ declare namespace Electron { */ expirationDate?: number; /** - * Whether the cookie is a host-only cookie. + * Whether the cookie is a host-only cookie; this will only be true if no domain + * was passed. */ hostOnly?: boolean; /** @@ -2247,7 +2454,7 @@ declare namespace Electron { class Cookies extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/cookies + // Docs: http://electronjs.org/docs/api/cookies /** * Emitted when a cookie is changed because it was added, edited, removed, or @@ -2328,7 +2535,7 @@ declare namespace Electron { interface CPUUsage { - // Docs: http://electron.atom.io/docs/api/structures/cpu-usage + // Docs: http://electronjs.org/docs/api/structures/cpu-usage /** * The number of average idle cpu wakeups per second since the last call to @@ -2343,7 +2550,7 @@ declare namespace Electron { interface CrashReport { - // Docs: http://electron.atom.io/docs/api/structures/crash-report + // Docs: http://electronjs.org/docs/api/structures/crash-report date: Date; id: string; @@ -2351,7 +2558,7 @@ declare namespace Electron { interface CrashReporter extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/crash-reporter + // Docs: http://electronjs.org/docs/api/crash-reporter /** * Set an extra parameter to be sent with the crash report. The values specified @@ -2362,8 +2569,10 @@ declare namespace Electron { */ addExtraParameter(key: string, value: string): void; /** - * Returns the date and ID of the last crash report. If no crash reports have been - * sent or the crash reporter has not been started, null is returned. + * Returns the date and ID of the last crash report. Only crash reports that have + * been uploaded will be returned; even if a crash report is present on disk it + * will not be returned until it is uploaded. In the case that there are no + * uploaded reports, null is returned. */ getLastCrashReport(): CrashReport; /** @@ -2419,7 +2628,7 @@ declare namespace Electron { class Debugger extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/debugger + // Docs: http://electronjs.org/docs/api/debugger /** * Emitted when debugging session is terminated. This happens either when @@ -2505,7 +2714,7 @@ declare namespace Electron { interface DesktopCapturer extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/desktop-capturer + // Docs: http://electronjs.org/docs/api/desktop-capturer /** * Starts gathering information about all available desktop media sources, and @@ -2518,7 +2727,7 @@ declare namespace Electron { interface DesktopCapturerSource { - // Docs: http://electron.atom.io/docs/api/structures/desktop-capturer-source + // Docs: http://electronjs.org/docs/api/structures/desktop-capturer-source /** * A unique identifier that will correspond to the id of the matching returned by @@ -2534,8 +2743,8 @@ declare namespace Electron { */ id: string; /** - * A screen source will be named either Entire Screen or Screen , while the - * name of a window source will match the window title. + * A screen source will be named either Entire Screen or Screen , while the name of + * a window source will match the window title. */ name: string; /** @@ -2549,7 +2758,7 @@ declare namespace Electron { interface Dialog extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/dialog + // Docs: http://electronjs.org/docs/api/dialog /** * On macOS, this displays a modal dialog that shows a message and certificate @@ -2610,7 +2819,7 @@ declare namespace Electron { * file selector and a directory selector, so if you set properties to ['openFile', * 'openDirectory'] on these platforms, a directory selector will be shown. */ - showOpenDialog(browserWindow: BrowserWindow, options: OpenDialogOptions, callback?: (filePaths: string[], bookmarks: string[]) => void): string[]; + showOpenDialog(browserWindow: BrowserWindow, options: OpenDialogOptions, callback?: (filePaths: string[], bookmarks: string[]) => void): (string[]) | (undefined); /** * The browserWindow argument allows the dialog to attach itself to a parent * window, making it modal. The filters specifies an array of file types that can @@ -2623,7 +2832,7 @@ declare namespace Electron { * file selector and a directory selector, so if you set properties to ['openFile', * 'openDirectory'] on these platforms, a directory selector will be shown. */ - showOpenDialog(options: OpenDialogOptions, callback?: (filePaths: string[], bookmarks: string[]) => void): string[]; + showOpenDialog(options: OpenDialogOptions, callback?: (filePaths: string[], bookmarks: string[]) => void): (string[]) | (undefined); /** * The browserWindow argument allows the dialog to attach itself to a parent * window, making it modal. The filters specifies an array of file types that can @@ -2631,7 +2840,7 @@ declare namespace Electron { * the API call will be asynchronous and the result will be passed via * callback(filename). */ - showSaveDialog(browserWindow: BrowserWindow, options: SaveDialogOptions, callback?: (filename: string, bookmark: string) => void): string; + showSaveDialog(browserWindow: BrowserWindow, options: SaveDialogOptions, callback?: (filename: string, bookmark: string) => void): (string) | (undefined); /** * The browserWindow argument allows the dialog to attach itself to a parent * window, making it modal. The filters specifies an array of file types that can @@ -2639,12 +2848,12 @@ declare namespace Electron { * the API call will be asynchronous and the result will be passed via * callback(filename). */ - showSaveDialog(options: SaveDialogOptions, callback?: (filename: string, bookmark: string) => void): string; + showSaveDialog(options: SaveDialogOptions, callback?: (filename: string, bookmark: string) => void): (string) | (undefined); } interface Display { - // Docs: http://electron.atom.io/docs/api/structures/display + // Docs: http://electronjs.org/docs/api/structures/display bounds: Rectangle; /** @@ -2670,7 +2879,7 @@ declare namespace Electron { class DownloadItem extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/download-item + // Docs: http://electronjs.org/docs/api/download-item /** * Emitted when the download is in a terminal state. This includes a completed @@ -2773,7 +2982,7 @@ declare namespace Electron { interface FileFilter { - // Docs: http://electron.atom.io/docs/api/structures/file-filter + // Docs: http://electronjs.org/docs/api/structures/file-filter extensions: string[]; name: string; @@ -2781,7 +2990,7 @@ declare namespace Electron { interface GlobalShortcut extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/global-shortcut + // Docs: http://electronjs.org/docs/api/global-shortcut /** * When the accelerator is already taken by other applications, this call will @@ -2811,65 +3020,65 @@ declare namespace Electron { interface GPUFeatureStatus { - // Docs: http://electron.atom.io/docs/api/structures/gpu-feature-status + // Docs: http://electronjs.org/docs/api/structures/gpu-feature-status /** - * Canvas + * Canvas. */ '2d_canvas': string; /** - * Flash + * Flash. */ flash_3d: string; /** - * Flash Stage3D + * Flash Stage3D. */ flash_stage3d: string; /** - * Flash Stage3D Baseline profile + * Flash Stage3D Baseline profile. */ flash_stage3d_baseline: string; /** - * Compositing + * Compositing. */ gpu_compositing: string; /** - * Multiple Raster Threads + * Multiple Raster Threads. */ multiple_raster_threads: string; /** - * Native GpuMemoryBuffers + * Native GpuMemoryBuffers. */ native_gpu_memory_buffers: string; /** - * Rasterization + * Rasterization. */ rasterization: string; /** - * Video Decode + * Video Decode. */ video_decode: string; /** - * Video Encode + * Video Encode. */ video_encode: string; /** - * VPx Video Decode + * VPx Video Decode. */ vpx_decode: string; /** - * WebGL + * WebGL. */ webgl: string; /** - * WebGL2 + * WebGL2. */ webgl2: string; } interface InAppPurchase extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/in-app-purchase + // Docs: http://electronjs.org/docs/api/in-app-purchase /** * Emitted when one or more transactions have been updated. @@ -2917,7 +3126,7 @@ declare namespace Electron { class IncomingMessage extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/incoming-message + // Docs: http://electronjs.org/docs/api/incoming-message /** * Emitted when a request has been canceled during an ongoing HTTP transaction. @@ -2978,7 +3187,7 @@ declare namespace Electron { interface IOCounters { - // Docs: http://electron.atom.io/docs/api/structures/io-counters + // Docs: http://electronjs.org/docs/api/structures/io-counters /** * Then number of I/O other operations. @@ -3008,7 +3217,7 @@ declare namespace Electron { interface IpcMain extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/ipc-main + // Docs: http://electronjs.org/docs/api/ipc-main /** * Listens to channel, when a new message arrives listener would be called with @@ -3033,7 +3242,7 @@ declare namespace Electron { interface IpcRenderer extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/ipc-renderer + // Docs: http://electronjs.org/docs/api/ipc-renderer /** * Listens to channel, when a new message arrives listener would be called with @@ -3083,7 +3292,7 @@ declare namespace Electron { interface JumpListCategory { - // Docs: http://electron.atom.io/docs/api/structures/jump-list-category + // Docs: http://electronjs.org/docs/api/structures/jump-list-category /** * Array of objects if type is tasks or custom, otherwise it should be omitted. @@ -3101,7 +3310,7 @@ declare namespace Electron { interface JumpListItem { - // Docs: http://electron.atom.io/docs/api/structures/jump-list-item + // Docs: http://electronjs.org/docs/api/structures/jump-list-item /** * The command line arguments when program is executed. Should only be set if type @@ -3148,7 +3357,7 @@ declare namespace Electron { interface MemoryInfo { - // Docs: http://electron.atom.io/docs/api/structures/memory-info + // Docs: http://electronjs.org/docs/api/structures/memory-info /** * The maximum amount of memory that has ever been pinned to actual physical RAM. @@ -3177,7 +3386,7 @@ declare namespace Electron { interface MemoryUsageDetails { - // Docs: http://electron.atom.io/docs/api/structures/memory-usage-details + // Docs: http://electronjs.org/docs/api/structures/memory-usage-details count: number; liveSize: number; @@ -3186,7 +3395,7 @@ declare namespace Electron { class Menu { - // Docs: http://electron.atom.io/docs/api/menu + // Docs: http://electronjs.org/docs/api/menu /** * Emitted when a popup is closed either manually or with menu.closePopup(). @@ -3213,7 +3422,7 @@ declare namespace Electron { * Note: The returned Menu instance doesn't support dynamic addition or removal of * menu items. Instance properties can still be dynamically modified. */ - static getApplicationMenu(): Menu | null; + static getApplicationMenu(): (Menu) | (null); /** * Sends the action to the first responder of application. This is used for * emulating default macOS menu behaviors. Usually you would use the role property @@ -3227,7 +3436,7 @@ declare namespace Electron { * Windows and Linux but has no effect on macOS. Note: This API has to be called * after the ready event of app module. */ - static setApplicationMenu(menu: Menu | null): void; + static setApplicationMenu(menu: (Menu) | (null)): void; /** * Appends the menuItem to the menu. */ @@ -3244,13 +3453,13 @@ declare namespace Electron { /** * Pops up this menu as a context menu in the BrowserWindow. */ - popup(options: PopupOptions): void; + popup(options?: PopupOptions): void; items: MenuItem[]; } class MenuItem { - // Docs: http://electron.atom.io/docs/api/menu-item + // Docs: http://electronjs.org/docs/api/menu-item constructor(options: MenuItemConstructorOptions); checked: boolean; @@ -3262,21 +3471,21 @@ declare namespace Electron { interface MimeTypedBuffer { - // Docs: http://electron.atom.io/docs/api/structures/mime-typed-buffer + // Docs: http://electronjs.org/docs/api/structures/mime-typed-buffer /** - * The actual Buffer content + * The actual Buffer content. */ data: Buffer; /** - * The mimeType of the Buffer that you are sending + * The mimeType of the Buffer that you are sending. */ mimeType: string; } class NativeImage { - // Docs: http://electron.atom.io/docs/api/native-image + // Docs: http://electronjs.org/docs/api/native-image /** * Creates an empty NativeImage instance. @@ -3294,7 +3503,14 @@ declare namespace Electron { * Creates a new NativeImage instance from the NSImage that maps to the given image * name. See NSImageName for a list of possible values. The hslShift is applied to * the image with the following rules This means that [-1, 0, 1] will make the - * image completely white and [-1, 1, 0] will make the image completely black. + * image completely white and [-1, 1, 0] will make the image completely black. In + * some cases, the NSImageName doesn't match its string representation; one example + * of this is NSFolderImageName, whose string representation would actually be + * NSFolder. Therefore, you'll need to determine the correct string representation + * for your image before passing it in. This can be done with the following: echo + * -e '#import \nint main() { NSLog(@"%@", SYSTEM_IMAGE_NAME); }' | + * clang -otest -x objective-c -framework Cocoa - && ./test where SYSTEM_IMAGE_NAME + * should be replaced with any value from this list. */ static createFromNamedImage(imageName: string, hslShift: number[]): NativeImage; /** @@ -3343,7 +3559,7 @@ declare namespace Electron { interface Net extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/net + // Docs: http://electronjs.org/docs/api/net /** * Creates a ClientRequest instance using the provided options which are directly @@ -3351,12 +3567,12 @@ declare namespace Electron { * to issue both secure and insecure HTTP requests according to the specified * protocol scheme in the options object. */ - request(options: any | string): ClientRequest; + request(options: (any) | (string)): ClientRequest; } interface NetLog extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/net-log + // Docs: http://electronjs.org/docs/api/net-log /** * Starts recording network events to path. @@ -3379,7 +3595,7 @@ declare namespace Electron { class Notification extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/notification + // Docs: http://electronjs.org/docs/api/notification on(event: 'action', listener: (event: Event, /** @@ -3469,7 +3685,7 @@ declare namespace Electron { interface NotificationAction { - // Docs: http://electron.atom.io/docs/api/structures/notification-action + // Docs: http://electronjs.org/docs/api/structures/notification-action /** * The label for the given action. @@ -3483,7 +3699,7 @@ declare namespace Electron { interface Point { - // Docs: http://electron.atom.io/docs/api/structures/point + // Docs: http://electronjs.org/docs/api/structures/point x: number; y: number; @@ -3491,7 +3707,7 @@ declare namespace Electron { interface PowerMonitor extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/power-monitor + // Docs: http://electronjs.org/docs/api/power-monitor /** * Emitted when the system is about to lock the screen. @@ -3549,7 +3765,7 @@ declare namespace Electron { interface PowerSaveBlocker extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/power-save-blocker + // Docs: http://electronjs.org/docs/api/power-save-blocker isStarted(id: number): boolean; /** @@ -3571,7 +3787,7 @@ declare namespace Electron { interface PrinterInfo { - // Docs: http://electron.atom.io/docs/api/structures/printer-info + // Docs: http://electronjs.org/docs/api/structures/printer-info description: string; isDefault: boolean; @@ -3581,16 +3797,12 @@ declare namespace Electron { interface ProcessMetric { - // Docs: http://electron.atom.io/docs/api/structures/process-metric + // Docs: http://electronjs.org/docs/api/structures/process-metric /** * CPU usage of the process. */ cpu: CPUUsage; - /** - * Memory information for the process. - */ - memory: MemoryInfo; /** * Process id of the process. */ @@ -3603,7 +3815,7 @@ declare namespace Electron { interface Product { - // Docs: http://electron.atom.io/docs/api/structures/product + // Docs: http://electronjs.org/docs/api/structures/product /** * The total size of the content, in bytes. @@ -3642,7 +3854,7 @@ declare namespace Electron { interface Protocol extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/protocol + // Docs: http://electronjs.org/docs/api/protocol /** * Intercepts scheme protocol and uses handler as the protocol's new handler which @@ -3663,7 +3875,7 @@ declare namespace Electron { * Same as protocol.registerStreamProtocol, except that it replaces an existing * protocol handler. */ - interceptStreamProtocol(scheme: string, handler: (request: InterceptStreamProtocolRequest, callback: (stream?: ReadableStream | StreamProtocolResponse) => void) => void, completion?: (error: Error) => void): void; + interceptStreamProtocol(scheme: string, handler: (request: InterceptStreamProtocolRequest, callback: (stream?: (NodeJS.ReadableStream) | (StreamProtocolResponse)) => void) => void, completion?: (error: Error) => void): void; /** * Intercepts scheme protocol and uses handler as the protocol's new handler which * sends a String as a response. @@ -3680,15 +3892,15 @@ declare namespace Electron { * with either a Buffer object or an object that has the data, mimeType, and * charset properties. Example: */ - registerBufferProtocol(scheme: string, handler: (request: RegisterBufferProtocolRequest, callback: (buffer?: Buffer | MimeTypedBuffer) => void) => void, completion?: (error: Error) => void): void; + registerBufferProtocol(scheme: string, handler: (request: RegisterBufferProtocolRequest, callback: (buffer?: (Buffer) | (MimeTypedBuffer)) => void) => void, completion?: (error: Error) => void): void; /** * Registers a protocol of scheme that will send the file as a response. The * handler will be called with handler(request, callback) when a request is going * to be created with scheme. completion will be called with completion(null) when * scheme is successfully registered or completion(error) when failed. To handle * the request, the callback should be called with either the file's path or an - * object that has a path property, e.g. callback(filePath) or callback({path: - * filePath}). When callback is called with nothing, a number, or an object that + * object that has a path property, e.g. callback(filePath) or callback({ path: + * filePath }). When callback is called with nothing, a number, or an object that * has an error property, the request will fail with the error number you * specified. For the available error numbers you can use, please see the net error * list. By default the scheme is treated like http:, which is parsed differently @@ -3732,7 +3944,7 @@ declare namespace Electron { * that implements the readable stream API (emits data/end/error events). For * example, here's how a file could be returned: */ - registerStreamProtocol(scheme: string, handler: (request: RegisterStreamProtocolRequest, callback: (stream?: ReadableStream | StreamProtocolResponse) => void) => void, completion?: (error: Error) => void): void; + registerStreamProtocol(scheme: string, handler: (request: RegisterStreamProtocolRequest, callback: (stream?: (NodeJS.ReadableStream) | (StreamProtocolResponse)) => void) => void, completion?: (error: Error) => void): void; /** * Registers a protocol of scheme that will send a String as a response. The usage * is the same with registerFileProtocol, except that the callback should be called @@ -3752,29 +3964,29 @@ declare namespace Electron { interface Rectangle { - // Docs: http://electron.atom.io/docs/api/structures/rectangle + // Docs: http://electronjs.org/docs/api/structures/rectangle /** - * The height of the rectangle (must be an integer) + * The height of the rectangle (must be an integer). */ height: number; /** - * The width of the rectangle (must be an integer) + * The width of the rectangle (must be an integer). */ width: number; /** - * The x coordinate of the origin of the rectangle (must be an integer) + * The x coordinate of the origin of the rectangle (must be an integer). */ x: number; /** - * The y coordinate of the origin of the rectangle (must be an integer) + * The y coordinate of the origin of the rectangle (must be an integer). */ y: number; } interface Referrer { - // Docs: http://electron.atom.io/docs/api/structures/referrer + // Docs: http://electronjs.org/docs/api/structures/referrer /** * Can be default, unsafe-url, no-referrer-when-downgrade, no-referrer, origin, @@ -3790,7 +4002,7 @@ declare namespace Electron { interface Remote extends MainInterface { - // Docs: http://electron.atom.io/docs/api/remote + // Docs: http://electronjs.org/docs/api/remote getCurrentWebContents(): WebContents; /** @@ -3813,7 +4025,7 @@ declare namespace Electron { interface RemoveClientCertificate { - // Docs: http://electron.atom.io/docs/api/structures/remove-client-certificate + // Docs: http://electronjs.org/docs/api/structures/remove-client-certificate /** * Origin of the server whose associated client certificate must be removed from @@ -3828,7 +4040,7 @@ declare namespace Electron { interface RemovePassword { - // Docs: http://electron.atom.io/docs/api/structures/remove-password + // Docs: http://electronjs.org/docs/api/structures/remove-password /** * When provided, the authentication info related to the origin will only be @@ -3860,7 +4072,7 @@ declare namespace Electron { interface Screen extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/screen + // Docs: http://electronjs.org/docs/api/screen /** * Emitted when newDisplay has been added. @@ -3911,7 +4123,7 @@ declare namespace Electron { * relative to the display nearest to window. If window is null, scaling will be * performed to the display nearest to rect. */ - dipToScreenRect(window: BrowserWindow | null, rect: Rectangle): Rectangle; + dipToScreenRect(window: (BrowserWindow) | (null), rect: Rectangle): Rectangle; getAllDisplays(): Display[]; /** * The current absolute position of the mouse pointer. @@ -3930,44 +4142,44 @@ declare namespace Electron { * relative to the display nearest to window. If window is null, scaling will be * performed to the display nearest to rect. */ - screenToDipRect(window: BrowserWindow | null, rect: Rectangle): Rectangle; + screenToDipRect(window: (BrowserWindow) | (null), rect: Rectangle): Rectangle; } interface ScrubberItem { - // Docs: http://electron.atom.io/docs/api/structures/scrubber-item + // Docs: http://electronjs.org/docs/api/structures/scrubber-item /** - * The image to appear in this item + * The image to appear in this item. */ icon?: NativeImage; /** - * The text to appear in this item + * The text to appear in this item. */ label?: string; } interface SegmentedControlSegment { - // Docs: http://electron.atom.io/docs/api/structures/segmented-control-segment + // Docs: http://electronjs.org/docs/api/structures/segmented-control-segment /** - * Whether this segment is selectable. Default: true + * Whether this segment is selectable. Default: true. */ enabled?: boolean; /** - * The image to appear in this segment + * The image to appear in this segment. */ icon?: NativeImage; /** - * The text to appear in this segment + * The text to appear in this segment. */ label?: string; } class Session extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/session + // Docs: http://electronjs.org/docs/api/session /** * If partition starts with persist:, the page will use a persistent session @@ -4007,7 +4219,7 @@ declare namespace Electron { /** * Clears the session’s HTTP authentication cache. */ - clearAuthCache(options: RemovePassword | RemoveClientCertificate, callback?: Function): void; + clearAuthCache(options: (RemovePassword) | (RemoveClientCertificate), callback?: Function): void; /** * Clears the session’s HTTP cache. */ @@ -4066,12 +4278,18 @@ declare namespace Electron { * Downloads under the respective app folder. */ setDownloadPath(path: string): void; + /** + * Sets the handler which can be used to respond to permission checks for the + * session. Returning true will allow the permission and false will reject it. To + * clear the handler, call setPermissionCheckHandler(null). + */ + setPermissionCheckHandler(handler: ((webContents: WebContents, permission: string, requestingOrigin: string, details: PermissionCheckHandlerDetails) => boolean) | (null)): void; /** * Sets the handler which can be used to respond to permission requests for the * session. Calling callback(true) will allow the permission and callback(false) * will reject it. To clear the handler, call setPermissionRequestHandler(null). */ - setPermissionRequestHandler(handler: (webContents: WebContents, permission: string, callback: (permissionGranted: boolean) => void, details: PermissionRequestHandlerDetails) => void | null): void; + setPermissionRequestHandler(handler: ((webContents: WebContents, permission: string, callback: (permissionGranted: boolean) => void, details: PermissionRequestHandlerDetails) => void) | (null)): void; /** * Adds scripts that will be executed on ALL web contents that are associated with * this session just before normal preload scripts run. @@ -4100,7 +4318,7 @@ declare namespace Electron { interface Shell { - // Docs: http://electron.atom.io/docs/api/shell + // Docs: http://electronjs.org/docs/api/shell /** * Play the beep sound. @@ -4140,7 +4358,7 @@ declare namespace Electron { interface ShortcutDetails { - // Docs: http://electron.atom.io/docs/api/structures/shortcut-details + // Docs: http://electronjs.org/docs/api/structures/shortcut-details /** * The Application User Model ID. Default is empty. @@ -4176,7 +4394,7 @@ declare namespace Electron { interface Size { - // Docs: http://electron.atom.io/docs/api/structures/size + // Docs: http://electronjs.org/docs/api/structures/size height: number; width: number; @@ -4184,25 +4402,25 @@ declare namespace Electron { interface StreamProtocolResponse { - // Docs: http://electron.atom.io/docs/api/structures/stream-protocol-response + // Docs: http://electronjs.org/docs/api/structures/stream-protocol-response /** - * A Node.js readable stream representing the response body + * A Node.js readable stream representing the response body. */ - data: ReadableStream; + data: NodeJS.ReadableStream; /** - * An object containing the response headers + * An object containing the response headers. */ headers: Headers; /** - * The HTTP response code + * The HTTP response code. */ statusCode: number; } interface SystemPreferences extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/system-preferences + // Docs: http://electronjs.org/docs/api/system-preferences on(event: 'accent-color-changed', listener: (event: Event, /** @@ -4224,6 +4442,30 @@ declare namespace Electron { * The new RGBA color the user assigned to be their system accent color. */ newColor: string) => void): this; + /** + * NOTE: This event is only emitted after you have called + * startAppLevelAppearanceTrackingOS + */ + on(event: 'appearance-changed', listener: ( + /** + * Can be `dark` or `light` + */ + newAppearance: ('dark' | 'light')) => void): this; + once(event: 'appearance-changed', listener: ( + /** + * Can be `dark` or `light` + */ + newAppearance: ('dark' | 'light')) => void): this; + addListener(event: 'appearance-changed', listener: ( + /** + * Can be `dark` or `light` + */ + newAppearance: ('dark' | 'light')) => void): this; + removeListener(event: 'appearance-changed', listener: ( + /** + * Can be `dark` or `light` + */ + newAppearance: ('dark' | 'light')) => void): this; on(event: 'color-changed', listener: (event: Event) => void): this; once(event: 'color-changed', listener: (event: Event) => void): this; addListener(event: 'color-changed', listener: (event: Event) => void): this; @@ -4252,8 +4494,41 @@ declare namespace Electron { * used, `false` otherwise. */ invertedColorScheme: boolean) => void): this; + /** + * Important: In order to properly leverage this API, you must set the + * NSMicrophoneUsageDescription and NSCameraUsageDescription strings in your app's + * Info.plist file. The values for these keys will be used to populate the + * permission dialogs so that the user will be properly informed as to the purpose + * of the permission request. See Electron Application Distribution for more + * information about how to set these in the context of Electron. This user consent + * was not required until macOS 10.14 Mojave, so this method will always return + * true if your system is running 10.13 High Sierra or lower. + */ + askForMediaAccess(mediaType: 'microphone' | 'camera'): Promise; getAccentColor(): string; + /** + * Gets the macOS appearance setting that you have declared you want for your + * application, maps to NSApplication.appearance. You can use the + * setAppLevelAppearance API to set this value. + */ + getAppLevelAppearance(): ('dark' | 'light' | 'unknown'); getColor(color: '3d-dark-shadow' | '3d-face' | '3d-highlight' | '3d-light' | '3d-shadow' | 'active-border' | 'active-caption' | 'active-caption-gradient' | 'app-workspace' | 'button-text' | 'caption-text' | 'desktop' | 'disabled-text' | 'highlight' | 'highlight-text' | 'hotlight' | 'inactive-border' | 'inactive-caption' | 'inactive-caption-gradient' | 'inactive-caption-text' | 'info-background' | 'info-text' | 'menu' | 'menu-highlight' | 'menubar' | 'menu-text' | 'scrollbar' | 'window' | 'window-frame' | 'window-text'): string; + /** + * Gets the macOS appearance setting that is currently applied to your application, + * maps to NSApplication.effectiveAppearance Please note that until Electron is + * built targeting the 10.14 SDK, your application's effectiveAppearance will + * default to 'light' and won't inherit the OS preference. In the interim in order + * for your application to inherit the OS preference you must set the + * NSRequiresAquaSystemAppearance key in your apps Info.plist to false. If you are + * using electron-packager or electron-forge just set the enableDarwinDarkMode + * packager option to true. See the Electron Packager API for more details. + */ + getEffectiveAppearance(): ('dark' | 'light' | 'unknown'); + /** + * This user consent was not required until macOS 10.14 Mojave, so this method will + * always return granted if your system is running 10.13 High Sierra or lower. + */ + getMediaAccessStatus(mediaType: string): ('not-determined' | 'granted' | 'denied' | 'restricted' | 'unknown'); /** * Some popular key and types are: */ @@ -4266,6 +4541,7 @@ declare namespace Electron { isDarkMode(): boolean; isInvertedColorScheme(): boolean; isSwipeTrackingFromScrollEventsEnabled(): boolean; + isTrustedAccessibilityClient(prompt: boolean): boolean; /** * Posts event as native notifications of macOS. The userInfo is an Object that * contains the user information dictionary sent along with the notification. @@ -4290,6 +4566,11 @@ declare namespace Electron { * global value of a key previously set with setUserDefault. */ removeUserDefault(key: string): void; + /** + * Sets the appearance setting for your application, this should override the + * system default and override the value of getEffectiveAppearance. + */ + setAppLevelAppearance(appearance: 'dark' | 'light'): void; /** * Set the value of key in NSUserDefaults. Note that type should match actual type * of value. An exception is thrown if they don't. Some popular key and types are: @@ -4333,7 +4614,7 @@ declare namespace Electron { interface Task { - // Docs: http://electron.atom.io/docs/api/structures/task + // Docs: http://electronjs.org/docs/api/structures/task /** * The command line arguments when program is executed. @@ -4368,7 +4649,7 @@ declare namespace Electron { interface ThumbarButton { - // Docs: http://electron.atom.io/docs/api/structures/thumbar-button + // Docs: http://electronjs.org/docs/api/structures/thumbar-button click: Function; /** @@ -4388,7 +4669,7 @@ declare namespace Electron { class TouchBarButton extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/touch-bar-button + // Docs: http://electronjs.org/docs/api/touch-bar-button constructor(options: TouchBarButtonConstructorOptions); backgroundColor: string; @@ -4398,7 +4679,7 @@ declare namespace Electron { class TouchBarColorPicker extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/touch-bar-color-picker + // Docs: http://electronjs.org/docs/api/touch-bar-color-picker constructor(options: TouchBarColorPickerConstructorOptions); availableColors: string[]; @@ -4407,14 +4688,14 @@ declare namespace Electron { class TouchBarGroup extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/touch-bar-group + // Docs: http://electronjs.org/docs/api/touch-bar-group constructor(options: TouchBarGroupConstructorOptions); } class TouchBarLabel extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/touch-bar-label + // Docs: http://electronjs.org/docs/api/touch-bar-label constructor(options: TouchBarLabelConstructorOptions); label: string; @@ -4423,7 +4704,7 @@ declare namespace Electron { class TouchBarPopover extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/touch-bar-popover + // Docs: http://electronjs.org/docs/api/touch-bar-popover constructor(options: TouchBarPopoverConstructorOptions); icon: NativeImage; @@ -4432,7 +4713,7 @@ declare namespace Electron { class TouchBarScrubber extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/touch-bar-scrubber + // Docs: http://electronjs.org/docs/api/touch-bar-scrubber constructor(options: TouchBarScrubberConstructorOptions); continuous: boolean; @@ -4445,7 +4726,7 @@ declare namespace Electron { class TouchBarSegmentedControl extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/touch-bar-segmented-control + // Docs: http://electronjs.org/docs/api/touch-bar-segmented-control constructor(options: TouchBarSegmentedControlConstructorOptions); segments: SegmentedControlSegment[]; @@ -4455,7 +4736,7 @@ declare namespace Electron { class TouchBarSlider extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/touch-bar-slider + // Docs: http://electronjs.org/docs/api/touch-bar-slider constructor(options: TouchBarSliderConstructorOptions); label: string; @@ -4466,14 +4747,14 @@ declare namespace Electron { class TouchBarSpacer extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/touch-bar-spacer + // Docs: http://electronjs.org/docs/api/touch-bar-spacer constructor(options: TouchBarSpacerConstructorOptions); } class TouchBar extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/touch-bar + // Docs: http://electronjs.org/docs/api/touch-bar constructor(options: TouchBarConstructorOptions); escapeItem: (TouchBarButton | TouchBarColorPicker | TouchBarGroup | TouchBarLabel | TouchBarPopover | TouchBarScrubber | TouchBarSegmentedControl | TouchBarSlider | TouchBarSpacer | null); @@ -4490,7 +4771,7 @@ declare namespace Electron { interface TraceCategoriesAndOptions { - // Docs: http://electron.atom.io/docs/api/structures/trace-categories-and-options + // Docs: http://electronjs.org/docs/api/structures/trace-categories-and-options /** * – is a filter to control what category groups should be traced. A filter can @@ -4517,7 +4798,7 @@ declare namespace Electron { interface TraceConfig { - // Docs: http://electron.atom.io/docs/api/structures/trace-config + // Docs: http://electronjs.org/docs/api/structures/trace-config excluded_categories?: string[]; included_categories?: string[]; @@ -4526,7 +4807,7 @@ declare namespace Electron { interface Transaction { - // Docs: http://electron.atom.io/docs/api/structures/transaction + // Docs: http://electronjs.org/docs/api/structures/transaction /** * The error code if an error occurred while processing the transaction. @@ -4558,7 +4839,7 @@ declare namespace Electron { class Tray extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/tray + // Docs: http://electronjs.org/docs/api/tray /** * Emitted when the tray balloon is clicked. @@ -4810,7 +5091,7 @@ declare namespace Electron { * The bounds of tray icon. */ bounds: Rectangle) => void): this; - constructor(image: NativeImage | string); + constructor(image: (NativeImage) | (string)); /** * Destroys the tray icon immediately. */ @@ -4834,7 +5115,7 @@ declare namespace Electron { /** * Sets the context menu for this icon. */ - setContextMenu(menu: Menu | null): void; + setContextMenu(menu: (Menu) | (null)): void; /** * Sets when the tray's icon background becomes highlighted (in blue). Note: You * can use highlightMode with a BrowserWindow by toggling between 'never' and @@ -4850,11 +5131,11 @@ declare namespace Electron { /** * Sets the image associated with this tray icon. */ - setImage(image: NativeImage | string): void; + setImage(image: (NativeImage) | (string)): void; /** * Sets the image associated with this tray icon when pressed on macOS. */ - setPressedImage(image: NativeImage | string): void; + setPressedImage(image: (NativeImage) | (string)): void; /** * Sets the title displayed aside of the tray icon in the status bar (Support ANSI * colors). @@ -4868,7 +5149,7 @@ declare namespace Electron { interface UploadBlob { - // Docs: http://electron.atom.io/docs/api/structures/upload-blob + // Docs: http://electronjs.org/docs/api/structures/upload-blob /** * UUID of blob data to upload. @@ -4882,7 +5163,7 @@ declare namespace Electron { interface UploadData { - // Docs: http://electron.atom.io/docs/api/structures/upload-data + // Docs: http://electronjs.org/docs/api/structures/upload-data /** * UUID of blob data. Use method to retrieve the data. @@ -4900,7 +5181,7 @@ declare namespace Electron { interface UploadFile { - // Docs: http://electron.atom.io/docs/api/structures/upload-file + // Docs: http://electronjs.org/docs/api/structures/upload-file /** * Path of file to be uploaded. @@ -4926,7 +5207,7 @@ declare namespace Electron { interface UploadRawData { - // Docs: http://electron.atom.io/docs/api/structures/upload-raw-data + // Docs: http://electronjs.org/docs/api/structures/upload-raw-data /** * Data to be uploaded. @@ -4940,7 +5221,7 @@ declare namespace Electron { class WebContents extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/web-contents + // Docs: http://electronjs.org/docs/api/web-contents static fromId(id: number): WebContents; static getAllWebContents(): WebContents[]; @@ -5164,22 +5445,22 @@ declare namespace Electron { */ on(event: 'did-attach-webview', listener: (event: Event, /** - * The guest web contents that is used by the ``. + * The guest web contents that is used by the ` */ webContents: WebContents) => void): this; once(event: 'did-attach-webview', listener: (event: Event, /** - * The guest web contents that is used by the ``. + * The guest web contents that is used by the ` */ webContents: WebContents) => void): this; addListener(event: 'did-attach-webview', listener: (event: Event, /** - * The guest web contents that is used by the ``. + * The guest web contents that is used by the ` */ webContents: WebContents) => void): this; removeListener(event: 'did-attach-webview', listener: (event: Event, /** - * The guest web contents that is used by the ``. + * The guest web contents that is used by the ` */ webContents: WebContents) => void): this; /** @@ -5190,22 +5471,22 @@ declare namespace Electron { /** * Theme color is in format of '#rrggbb'. It is `null` when no theme color is set. */ - color: string | null) => void): this; + color: (string) | (null)) => void): this; once(event: 'did-change-theme-color', listener: (event: Event, /** * Theme color is in format of '#rrggbb'. It is `null` when no theme color is set. */ - color: string | null) => void): this; + color: (string) | (null)) => void): this; addListener(event: 'did-change-theme-color', listener: (event: Event, /** * Theme color is in format of '#rrggbb'. It is `null` when no theme color is set. */ - color: string | null) => void): this; + color: (string) | (null)) => void): this; removeListener(event: 'did-change-theme-color', listener: (event: Event, /** * Theme color is in format of '#rrggbb'. It is `null` when no theme color is set. */ - color: string | null) => void): this; + color: (string) | (null)) => void): this; /** * This event is like did-finish-load but emitted when the load failed or was * cancelled, e.g. window.stop() is invoked. The full list of error codes and their @@ -5394,6 +5675,35 @@ declare namespace Electron { isMainFrame: boolean, frameProcessId: number, frameRoutingId: number) => void): this; + /** + * Emitted after a server side redirect occurs during navigation. For example a + * 302 redirect. This event can not be prevented, if you want to prevent redirects + * you should checkout out the will-redirect event above. + */ + on(event: 'did-redirect-navigation', listener: (event: Event, + url: string, + isInPlace: boolean, + isMainFrame: boolean, + frameProcessId: number, + frameRoutingId: number) => void): this; + once(event: 'did-redirect-navigation', listener: (event: Event, + url: string, + isInPlace: boolean, + isMainFrame: boolean, + frameProcessId: number, + frameRoutingId: number) => void): this; + addListener(event: 'did-redirect-navigation', listener: (event: Event, + url: string, + isInPlace: boolean, + isMainFrame: boolean, + frameProcessId: number, + frameRoutingId: number) => void): this; + removeListener(event: 'did-redirect-navigation', listener: (event: Event, + url: string, + isInPlace: boolean, + isMainFrame: boolean, + frameProcessId: number, + frameRoutingId: number) => void): this; /** * Corresponds to the points in time when the spinner of the tab started spinning. */ @@ -5405,22 +5715,26 @@ declare namespace Electron { * Emitted when any frame (including main) starts navigating. isInplace will be * true for in-page navigations. */ - on(event: 'did-start-navigation', listener: (url: string, + on(event: 'did-start-navigation', listener: (event: Event, + url: string, isInPlace: boolean, isMainFrame: boolean, frameProcessId: number, frameRoutingId: number) => void): this; - once(event: 'did-start-navigation', listener: (url: string, + once(event: 'did-start-navigation', listener: (event: Event, + url: string, isInPlace: boolean, isMainFrame: boolean, frameProcessId: number, frameRoutingId: number) => void): this; - addListener(event: 'did-start-navigation', listener: (url: string, + addListener(event: 'did-start-navigation', listener: (event: Event, + url: string, isInPlace: boolean, isMainFrame: boolean, frameProcessId: number, frameRoutingId: number) => void): this; - removeListener(event: 'did-start-navigation', listener: (url: string, + removeListener(event: 'did-start-navigation', listener: (event: Event, + url: string, isInPlace: boolean, isMainFrame: boolean, frameProcessId: number, @@ -5648,6 +5962,76 @@ declare namespace Electron { removeListener(event: 'plugin-crashed', listener: (event: Event, name: string, version: string) => void): this; + /** + * Emitted when remote.getBuiltin() is called in the renderer process. Calling + * event.preventDefault() will prevent the module from being returned. Custom value + * can be returned by setting event.returnValue. + */ + on(event: 'remote-get-builtin', listener: (event: Event, + moduleName: string) => void): this; + once(event: 'remote-get-builtin', listener: (event: Event, + moduleName: string) => void): this; + addListener(event: 'remote-get-builtin', listener: (event: Event, + moduleName: string) => void): this; + removeListener(event: 'remote-get-builtin', listener: (event: Event, + moduleName: string) => void): this; + /** + * Emitted when remote.getCurrentWebContents() is called in the renderer process. + * Calling event.preventDefault() will prevent the object from being returned. + * Custom value can be returned by setting event.returnValue. + */ + on(event: 'remote-get-current-web-contents', listener: (event: Event) => void): this; + once(event: 'remote-get-current-web-contents', listener: (event: Event) => void): this; + addListener(event: 'remote-get-current-web-contents', listener: (event: Event) => void): this; + removeListener(event: 'remote-get-current-web-contents', listener: (event: Event) => void): this; + /** + * Emitted when remote.getCurrentWindow() is called in the renderer process. + * Calling event.preventDefault() will prevent the object from being returned. + * Custom value can be returned by setting event.returnValue. + */ + on(event: 'remote-get-current-window', listener: (event: Event) => void): this; + once(event: 'remote-get-current-window', listener: (event: Event) => void): this; + addListener(event: 'remote-get-current-window', listener: (event: Event) => void): this; + removeListener(event: 'remote-get-current-window', listener: (event: Event) => void): this; + /** + * Emitted when remote.getGlobal() is called in the renderer process. Calling + * event.preventDefault() will prevent the global from being returned. Custom value + * can be returned by setting event.returnValue. + */ + on(event: 'remote-get-global', listener: (event: Event, + globalName: string) => void): this; + once(event: 'remote-get-global', listener: (event: Event, + globalName: string) => void): this; + addListener(event: 'remote-get-global', listener: (event: Event, + globalName: string) => void): this; + removeListener(event: 'remote-get-global', listener: (event: Event, + globalName: string) => void): this; + /** + * Emitted when .getWebContents() is called in the renderer process. + * Calling event.preventDefault() will prevent the object from being returned. + * Custom value can be returned by setting event.returnValue. + */ + on(event: 'remote-get-guest-web-contents', listener: (event: Event, + guestWebContents: WebContents) => void): this; + once(event: 'remote-get-guest-web-contents', listener: (event: Event, + guestWebContents: WebContents) => void): this; + addListener(event: 'remote-get-guest-web-contents', listener: (event: Event, + guestWebContents: WebContents) => void): this; + removeListener(event: 'remote-get-guest-web-contents', listener: (event: Event, + guestWebContents: WebContents) => void): this; + /** + * Emitted when remote.require() is called in the renderer process. Calling + * event.preventDefault() will prevent the module from being returned. Custom value + * can be returned by setting event.returnValue. + */ + on(event: 'remote-require', listener: (event: Event, + moduleName: string) => void): this; + once(event: 'remote-require', listener: (event: Event, + moduleName: string) => void): this; + addListener(event: 'remote-require', listener: (event: Event, + moduleName: string) => void): this; + removeListener(event: 'remote-require', listener: (event: Event, + moduleName: string) => void): this; /** * Emitted when the unresponsive web page becomes responsive again. */ @@ -5727,8 +6111,7 @@ declare namespace Electron { */ webPreferences: any, /** - * The other `` parameters such as the `src` URL. This object can be - * modified to adjust the parameters of the guest page. + * The other ` */ params: any) => void): this; once(event: 'will-attach-webview', listener: (event: Event, @@ -5738,8 +6121,7 @@ declare namespace Electron { */ webPreferences: any, /** - * The other `` parameters such as the `src` URL. This object can be - * modified to adjust the parameters of the guest page. + * The other ` */ params: any) => void): this; addListener(event: 'will-attach-webview', listener: (event: Event, @@ -5749,8 +6131,7 @@ declare namespace Electron { */ webPreferences: any, /** - * The other `` parameters such as the `src` URL. This object can be - * modified to adjust the parameters of the guest page. + * The other ` */ params: any) => void): this; removeListener(event: 'will-attach-webview', listener: (event: Event, @@ -5760,8 +6141,7 @@ declare namespace Electron { */ webPreferences: any, /** - * The other `` parameters such as the `src` URL. This object can be - * modified to adjust the parameters of the guest page. + * The other ` */ params: any) => void): this; /** @@ -5790,6 +6170,36 @@ declare namespace Electron { once(event: 'will-prevent-unload', listener: (event: Event) => void): this; addListener(event: 'will-prevent-unload', listener: (event: Event) => void): this; removeListener(event: 'will-prevent-unload', listener: (event: Event) => void): this; + /** + * Emitted as a server side redirect occurs during navigation. For example a 302 + * redirect. This event will be emitted after did-start-navigation and always + * before the did-redirect-navigation event for the same navigation. Calling + * event.preventDefault() will prevent the navigation (not just the redirect). + */ + on(event: 'will-redirect', listener: (event: Event, + url: string, + isInPlace: boolean, + isMainFrame: boolean, + frameProcessId: number, + frameRoutingId: number) => void): this; + once(event: 'will-redirect', listener: (event: Event, + url: string, + isInPlace: boolean, + isMainFrame: boolean, + frameProcessId: number, + frameRoutingId: number) => void): this; + addListener(event: 'will-redirect', listener: (event: Event, + url: string, + isInPlace: boolean, + isMainFrame: boolean, + frameProcessId: number, + frameRoutingId: number) => void): this; + removeListener(event: 'will-redirect', listener: (event: Event, + url: string, + isInPlace: boolean, + isMainFrame: boolean, + frameProcessId: number, + frameRoutingId: number) => void): this; /** * Adds the specified path to DevTools workspace. Must be used after DevTools * creation: @@ -5895,6 +6305,7 @@ declare namespace Electron { getPrinters(): PrinterInfo[]; getProcessId(): number; getTitle(): string; + getType(): ('backgroundPage' | 'window' | 'browserView' | 'remote' | 'webview' | 'offscreen'); getURL(): string; getUserAgent(): string; getWebRTCIPHandlingPolicy(): string; @@ -5953,6 +6364,7 @@ declare namespace Electron { invalidate(): void; isAudioMuted(): boolean; isCrashed(): boolean; + isCurrentlyAudible(): boolean; isDestroyed(): boolean; isDevToolsFocused(): boolean; isDevToolsOpened(): boolean; @@ -5967,7 +6379,7 @@ declare namespace Electron { * relative to the root of your application. For instance an app structure like * this: Would require code like this */ - loadFile(filePath: string): void; + loadFile(filePath: string, options?: LoadFileOptions): void; /** * Loads the url in the window. The url must contain the protocol prefix, e.g. the * http:// or file://. If the load should bypass http cache then use the pragma @@ -5992,8 +6404,8 @@ declare namespace Electron { * Prints window's web page. When silent is set to true, Electron will pick the * system's default printer if deviceName is empty and the default settings for * printing. Calling window.print() in web page is equivalent to calling - * webContents.print({silent: false, printBackground: false, deviceName: ''}). Use - * page-break-before: always; CSS style to force to print to a new page. + * webContents.print({ silent: false, printBackground: false, deviceName: '' }). + * Use page-break-before: always; CSS style to force to print to a new page. */ print(options?: PrintOptions, callback?: (success: boolean) => void): void; /** @@ -6054,6 +6466,11 @@ declare namespace Electron { * Mute the audio on the current web page. */ setAudioMuted(muted: boolean): void; + /** + * Controls whether or not this WebContents will throttle animations and timers + * when the page becomes backgrounded. This also affects the Page Visibility API. + */ + setBackgroundThrottling(allowed: boolean): void; /** * Uses the devToolsWebContents as the target WebContents to show devtools. The * devToolsWebContents must not have done any navigation, and it should not be used @@ -6131,6 +6548,10 @@ declare namespace Electron { * If offscreen rendering is enabled and painting, stop painting. */ stopPainting(): void; + /** + * Takes a V8 heap snapshot and saves it to filePath. + */ + takeHeapSnapshot(filePath: string): Promise; /** * Toggles the developer tools. */ @@ -6158,7 +6579,7 @@ declare namespace Electron { interface WebFrame extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/web-frame + // Docs: http://electronjs.org/docs/api/web-frame /** * Attempts to free memory that is no longer being used (like images from a @@ -6176,7 +6597,7 @@ declare namespace Electron { */ executeJavaScript(code: string, userGesture?: boolean, callback?: (result: any) => void): Promise; /** - * Work like executeJavaScript but evaluates scripts in isolated context. + * Work like executeJavaScript but evaluates scripts in an isolated context. */ executeJavaScriptInIsolatedWorld(worldId: number, scripts: WebSource[], userGesture?: boolean, callback?: (result: any) => void): void; findFrameByName(name: string): WebFrame; @@ -6279,7 +6700,7 @@ declare namespace Electron { class WebRequest extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/web-request + // Docs: http://electronjs.org/docs/api/web-request /** * The listener will be called with listener(details) when a server initiated @@ -6373,7 +6794,7 @@ declare namespace Electron { interface WebSource { - // Docs: http://electron.atom.io/docs/api/structures/web-source + // Docs: http://electronjs.org/docs/api/structures/web-source code: string; /** @@ -6385,7 +6806,7 @@ declare namespace Electron { interface WebviewTag extends HTMLElement { - // Docs: http://electron.atom.io/docs/api/webview-tag + // Docs: http://electronjs.org/docs/api/webview-tag /** * Fired when a load has committed. This includes navigation within the current @@ -6509,11 +6930,6 @@ declare namespace Electron { */ addEventListener(event: 'crashed', listener: (event: Event) => void, useCapture?: boolean): this; removeEventListener(event: 'crashed', listener: (event: Event) => void): this; - /** - * Fired when the gpu process is crashed. - */ - addEventListener(event: 'gpu-crashed', listener: (event: Event) => void, useCapture?: boolean): this; - removeEventListener(event: 'gpu-crashed', listener: (event: Event) => void): this; /** * Fired when a plugin process is crashed. */ @@ -6597,6 +7013,10 @@ declare namespace Electron { * Executes editing command delete in page. */ delete(): void; + /** + * Initiates a download of the resource at url without navigating. + */ + downloadURL(url: string): void; /** * Evaluates code in page. If userGesture is set, it will create the user gesture * context in the page. HTML APIs like requestFullScreen, which require user @@ -6611,7 +7031,21 @@ declare namespace Electron { getTitle(): string; getURL(): string; getUserAgent(): string; + /** + * It depends on the remote module, it is therefore not available when this module + * is disabled. + */ getWebContents(): WebContents; + /** + * Sends a request to get current zoom factor, the callback will be called with + * callback(zoomFactor). + */ + getZoomFactor(callback: (zoomFactor: number) => void): void; + /** + * Sends a request to get current zoom level, the callback will be called with + * callback(zoomLevel). + */ + getZoomLevel(callback: (zoomLevel: number) => void): void; /** * Makes the guest page go back. */ @@ -6646,9 +7080,11 @@ declare namespace Electron { inspectServiceWorker(): void; isAudioMuted(): boolean; isCrashed(): boolean; + isCurrentlyAudible(): boolean; isDevToolsFocused(): boolean; isDevToolsOpened(): boolean; isLoading(): boolean; + isLoadingMainFrame(): boolean; isWaitingForResponse(): boolean; /** * Loads the url in the webview, the url must contain the protocol prefix, e.g. the @@ -6716,10 +7152,18 @@ declare namespace Electron { * Set guest page muted. */ setAudioMuted(muted: boolean): void; + /** + * Sets the maximum and minimum layout-based (i.e. non-visual) zoom level. + */ + setLayoutZoomLevelLimits(minimumLevel: number, maximumLevel: number): void; /** * Overrides the user agent for the guest page. */ setUserAgent(userAgent: string): void; + /** + * Sets the maximum and minimum pinch-to-zoom level. + */ + setVisualZoomLevelLimits(minimumLevel: number, maximumLevel: number): void; /** * Changes the zoom factor to the specified factor. Zoom factor is zoom percent * divided by 100, so 300% = 3.0. @@ -6728,7 +7172,8 @@ declare namespace Electron { /** * Changes the zoom level to the specified level. The original size is 0 and each * increment above or below represents zooming 20% larger or smaller to default - * limits of 300% and 50% of original size, respectively. + * limits of 300% and 50% of original size, respectively. The formula for this is + * scale := 1.2 ^ level. */ setZoomLevel(level: number): void; /** @@ -6781,6 +7226,11 @@ declare namespace Electron { * RuntimeEnabledFeatures.json5 file. */ enableblinkfeatures?: string; + /** + * When this attribute is false the guest page in webview will not have access to + * the remote module. The remote module is avaiable by default. + */ + enableremotemodule?: string; /** * Sets the referrer URL for the guest page. */ @@ -7054,7 +7504,7 @@ declare namespace Electron { * visual effects, you can also leave it undefined so the executable's icon will be * used. */ - icon?: NativeImage | string; + icon?: (NativeImage) | (string); /** * Whether window should be shown when created. Default is true. */ @@ -7091,9 +7541,8 @@ declare namespace Electron { enableLargerThanScreen?: boolean; /** * Window's background color as a hexadecimal value, like #66CD00 or #FFF or - * #80FFFFFF (alpha is supported). Default is #FFF (white). If transparent is set - * to true, only values with transparent (#00-------) or opaque (#FF-----) alpha - * values are respected. + * #80FFFFFF (alpha is supported if transparent is set to true). Default is #FFF + * (white). */ backgroundColor?: string; /** @@ -7320,7 +7769,7 @@ declare namespace Electron { } interface CrashReporterStartOptions { - companyName?: string; + companyName: string; /** * URL that crash reports will be sent to as POST. */ @@ -7422,7 +7871,8 @@ declare namespace Electron { */ value?: string; /** - * The domain of the cookie. Empty by default if omitted. + * The domain of the cookie; this will be normalized with a preceding dot so that + * it's also valid for subdomains. Empty by default if omitted. */ domain?: string; /** @@ -7476,7 +7926,7 @@ declare namespace Electron { /** * - */ - icon?: NativeImage | string; + icon?: (NativeImage) | (string); title: string; content: string; } @@ -7518,7 +7968,7 @@ declare namespace Electron { /** * Sets the image associated with this dock icon. */ - setIcon: (image: NativeImage | string) => void; + setIcon: (image: (NativeImage) | (string)) => void; } interface EnableNetworkEmulationOptions { @@ -7716,6 +8166,7 @@ declare namespace Electron { interface InterceptHttpProtocolRequest { url: string; + headers: Headers; referrer: string; method: string; uploadData: UploadData[]; @@ -7772,11 +8223,26 @@ declare namespace Electron { isMainFrame: boolean; } + interface LoadFileOptions { + /** + * Passed to url.format(). + */ + query?: Query; + /** + * Passed to url.format(). + */ + search?: string; + /** + * Passed to url.format(). + */ + hash?: string; + } + interface LoadURLOptions { /** * An HTTP Referrer url. */ - httpReferrer?: string | Referrer; + httpReferrer?: (string) | (Referrer); /** * A user agent originating the request. */ @@ -7785,7 +8251,7 @@ declare namespace Electron { * Extra headers separated by "\n" */ extraHeaders?: string; - postData?: UploadRawData[] | UploadFile[] | UploadBlob[]; + postData?: (UploadRawData[]) | (UploadFile[]) | (UploadBlob[]); /** * Base url (with trailing path separator) for files to be loaded by the data url. * This is needed only if the specified url is a data url and needs to load other @@ -7856,7 +8322,7 @@ declare namespace Electron { label?: string; sublabel?: string; accelerator?: Accelerator; - icon?: NativeImage | string; + icon?: (NativeImage) | (string); /** * If false, the menu item will be greyed out and unclickable. */ @@ -7879,17 +8345,36 @@ declare namespace Electron { * type: 'submenu' can be omitted. If the value is not a then it will be * automatically converted to one using Menu.buildFromTemplate. */ - submenu?: MenuItemConstructorOptions[] | Menu; + submenu?: (MenuItemConstructorOptions[]) | (Menu); /** * Unique within a single menu. If defined then it can be used as a reference to * this item by the position attribute. */ id?: string; /** - * This field allows fine-grained definition of the specific location within a - * given menu. + * Inserts this item before the item with the specified label. If the referenced + * item doesn't exist the item will be inserted at the end of the menu. Also + * implies that the menu item in question should be placed in the same “group” as + * the item. + */ + before?: string[]; + /** + * Inserts this item after the item with the specified label. If the referenced + * item doesn't exist the item will be inserted at the end of the menu. */ - position?: string; + after?: string[]; + /** + * Provides a means for a single context menu to declare the placement of their + * containing group before the containing group of the item with the specified + * label. + */ + beforeGroupContaining?: string[]; + /** + * Provides a means for a single context menu to declare the placement of their + * containing group after the containing group of the item with the specified + * label. + */ + afterGroupContaining?: string[]; } interface MessageBoxOptions { @@ -7935,7 +8420,7 @@ declare namespace Electron { * The index of the button to be used to cancel the dialog, via the Esc key. By * default this is assigned to the first button with "cancel" or "no" as the label. * If no such labeled buttons exist and this option is not set, 0 will be used as - * the return value or callback response. This option is ignored on Windows. + * the return value or callback response. */ cancelId?: number; /** @@ -7993,7 +8478,7 @@ declare namespace Electron { /** * An icon to use in the notification. */ - icon?: string | NativeImage; + icon?: (string) | (NativeImage); /** * Whether or not to add an inline reply option to the notification. */ @@ -8093,6 +8578,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; responseHeaders: ResponseHeaders; fromCache: boolean; @@ -8151,7 +8637,7 @@ declare namespace Electron { } interface OnHeadersReceivedResponse { - cancel: boolean; + cancel?: boolean; /** * When provided, the server is assumed to have responded with these headers. */ @@ -8242,7 +8728,11 @@ declare namespace Electron { /** * true to bring the opened application to the foreground. The default is true. */ - activate: boolean; + activate?: boolean; + /** + * The working directory. + */ + workingDirectory?: string; } interface PageFaviconUpdatedEvent extends Event { @@ -8267,8 +8757,8 @@ declare namespace Electron { */ screenSize: Size; /** - * Position the view on the screen (screenPosition == mobile) (default: {x: 0, y: - * 0}). + * Position the view on the screen (screenPosition == mobile) (default: { x: 0, y: + * 0 }). */ viewPosition: Point; /** @@ -8298,11 +8788,26 @@ declare namespace Electron { quantity: number; } + interface PermissionCheckHandlerDetails { + /** + * The security orign of the media check. + */ + securityOrigin: string; + /** + * The type of media access being requested, can be video, audio or unknown + */ + mediaType: ('video' | 'audio' | 'unknown'); + } + interface PermissionRequestHandlerDetails { /** * The url of the openExternal request. */ externalURL: string; + /** + * The types of media access being requested, elements can be video or audio + */ + mediaTypes: Array<'video' | 'audio'>; } interface PluginCrashedEvent extends Event { @@ -8359,7 +8864,7 @@ declare namespace Electron { * Specify page size of the generated PDF. Can be A3, A4, A5, Legal, Letter, * Tabloid or an Object containing height and width in microns. */ - pageSize?: string | Size; + pageSize?: (string) | (Size); /** * Whether to print CSS backgrounds. */ @@ -8376,23 +8881,19 @@ declare namespace Electron { interface ProcessMemoryInfo { /** - * The amount of memory currently pinned to actual physical RAM. - */ - workingSetSize: number; - /** - * The maximum amount of memory that has ever been pinned to actual physical RAM. + * and The amount of memory currently pinned to actual physical RAM in Kilobytes. */ - peakWorkingSetSize: number; + residentSet: number; /** * The amount of memory not shared by other processes, such as JS heap or HTML - * content. + * content in Kilobytes. */ - privateBytes: number; + private: number; /** * The amount of memory shared between processes, typically memory consumed by the - * Electron code itself. + * Electron code itself in Kilobytes. */ - sharedBytes: number; + shared: number; } interface ProgressBarOptions { @@ -8437,6 +8938,7 @@ declare namespace Electron { interface RegisterHttpProtocolRequest { url: string; + headers: Headers; referrer: string; method: string; uploadData: UploadData[]; @@ -8704,8 +9206,8 @@ declare namespace Electron { } interface TouchBarConstructorOptions { - items: Array; - escapeItem?: TouchBarButton | TouchBarColorPicker | TouchBarGroup | TouchBarLabel | TouchBarPopover | TouchBarScrubber | TouchBarSegmentedControl | TouchBarSlider | TouchBarSpacer | null; + items: Array<(TouchBarButton) | (TouchBarColorPicker) | (TouchBarGroup) | (TouchBarLabel) | (TouchBarPopover) | (TouchBarScrubber) | (TouchBarSegmentedControl) | (TouchBarSlider) | (TouchBarSpacer)>; + escapeItem?: (TouchBarButton) | (TouchBarColorPicker) | (TouchBarGroup) | (TouchBarLabel) | (TouchBarPopover) | (TouchBarScrubber) | (TouchBarSegmentedControl) | (TouchBarSlider) | (TouchBarSpacer) | (null); } interface TouchBarGroupConstructorOptions { @@ -8871,6 +9373,13 @@ declare namespace Electron { electron?: string; } + interface VisibleOnAllWorkspacesOptions { + /** + * Sets whether the window should be visible above fullscreen windows + */ + visibleOnFullScreen?: boolean; + } + interface WillNavigateEvent extends Event { url: string; } @@ -8964,6 +9473,9 @@ declare namespace Electron { interface Options { } + interface Query { + } + interface RequestHeaders { } @@ -9004,6 +9516,10 @@ declare namespace Electron { * currently experimental and may change or be removed in future Electron releases. */ sandbox?: boolean; + /** + * Whether to enable the module. Default is true. + */ + enableRemoteModule?: boolean; /** * Sets the session used by the page. Instead of passing the Session object * directly, you can also choose to use the partition option instead, which accepts @@ -9072,10 +9588,6 @@ declare namespace Electron { * Enables Chromium's experimental features. Default is false. */ // experimentalFeatures?: boolean; ### VSCODE CHANGE (https://github.com/electron/electron/blob/master/docs/tutorial/security.md) ### - /** - * Enables Chromium's experimental canvas features. Default is false. - */ - experimentalCanvasFeatures?: boolean; /** * Enables scroll bounce (rubber banding) effect on macOS. Default is false. */ @@ -9131,8 +9643,7 @@ declare namespace Electron { * content to ensure the loaded content cannot tamper with the preload script and * any Electron APIs being used. This option uses the same technique used by . You * can access this context in the dev tools by selecting the 'Electron Isolated - * Context' entry in the combo box at the top of the Console tab. This option is - * currently experimental and may change or be removed in future Electron releases. + * Context' entry in the combo box at the top of the Console tab. */ contextIsolation?: boolean; /** @@ -9144,11 +9655,11 @@ declare namespace Electron { nativeWindowOpen?: boolean; /** * Whether to enable the . Defaults to the value of the nodeIntegration option. The - * preload script configured for the will have node integration enabled - * when it is executed so you should ensure remote/untrusted content is not able to - * create a tag with a possibly malicious preload script. You can use the + * preload script configured for the will have node integration enabled when it is + * executed so you should ensure remote/untrusted content is not able to create a + * tag with a possibly malicious preload script. You can use the * will-attach-webview event on to strip away the preload script and to validate or - * alter the 's initial settings. + * alter the 's initial settings. */ webviewTag?: boolean; /** @@ -9230,7 +9741,7 @@ interface Document { declare namespace NodeJS { interface Process extends EventEmitter { - // Docs: http://electron.atom.io/docs/api/process + // Docs: http://electronjs.org/docs/api/process // ### BEGIN VSCODE MODIFICATION ### // /** @@ -9250,6 +9761,12 @@ declare namespace NodeJS { */ crash(): void; getCPUUsage(): Electron.CPUUsage; + /** + * Indicates the creation time of the application. The time is represented as + * number of milliseconds since epoch. It returns null if it is unable to get the + * process creation time. + */ + getCreationTime(): (number) | (null); /** * Returns an object with V8 heap statistics. Note that all statistics are reported * in Kilobytes. @@ -9258,7 +9775,12 @@ declare namespace NodeJS { getIOCounters(): Electron.IOCounters; /** * Returns an object giving memory usage statistics about the current process. Note - * that all statistics are reported in Kilobytes. + * that all statistics are reported in Kilobytes. This api should be called after + * app ready. Chromium does not provide residentSet value for macOS. This is + * because macOS performs in-memory compression of pages that haven't been recently + * used. As a result the resident set size value is not what one would expect. + * private memory is more representative of the actual pre-compression memory usage + * of the process on macOS. */ getProcessMemoryInfo(): Electron.ProcessMemoryInfo; /** @@ -9275,6 +9797,10 @@ declare namespace NodeJS { * whichever is lower for the current process. */ setFdLimit(maxDescriptors: number): void; + /** + * Takes a V8 heap snapshot and saves it to filePath. + */ + takeHeapSnapshot(filePath: string): boolean; /** * A Boolean. When app is started by being passed as parameter to the default app, * this property is true in the main process, otherwise it is undefined. @@ -9300,6 +9826,11 @@ declare namespace NodeJS { * A String representing the path to the resources directory. */ resourcesPath?: string; + /** + * A Boolean. When the renderer process is sandboxed, this property is true, + * otherwise it is undefined. + */ + sandboxed?: boolean; /** * A Boolean that controls whether or not deprecation warnings will be thrown as * exceptions. Setting this to true will throw errors for deprecations. This @@ -9335,4 +9866,4 @@ declare namespace NodeJS { electron: string; chrome: string; } -} +} \ No newline at end of file diff --git a/src/typings/onigasm-umd.d.ts b/src/typings/onigasm-umd.d.ts new file mode 100644 index 000000000000..24396aafa9f9 --- /dev/null +++ b/src/typings/onigasm-umd.d.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module "onigasm-umd" { + + function loadWASM(data: string | ArrayBuffer): Promise; + + class OnigString { + constructor(content: string); + readonly content: string; + readonly dispose?: () => void; + } + + class OnigScanner { + constructor(patterns: string[]); + findNextMatchSync(string: string | OnigString, startPosition: number): IOnigMatch; + } + + export interface IOnigCaptureIndex { + index: number + start: number + end: number + length: number + } + + export interface IOnigMatch { + index: number + captureIndices: IOnigCaptureIndex[] + scanner: OnigScanner + } +} \ No newline at end of file diff --git a/src/typings/require.d.ts b/src/typings/require.d.ts index 76916d851317..5b98dc2f4bc7 100644 --- a/src/typings/require.d.ts +++ b/src/typings/require.d.ts @@ -17,7 +17,12 @@ declare const enum LoaderEventType { NodeEndEvaluatingScript = 32, NodeBeginNativeRequire = 33, - NodeEndNativeRequire = 34 + NodeEndNativeRequire = 34, + + CachedDataFound = 60, + CachedDataMissed = 61, + CachedDataRejected = 62, + CachedDataCreated = 63, } declare class LoaderEvent { diff --git a/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css b/src/typings/vscode-windows-ca-certs.d.ts similarity index 88% rename from src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css rename to src/typings/vscode-windows-ca-certs.d.ts index 06e13f322fb5..37b2282827ae 100644 --- a/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css +++ b/src/typings/vscode-windows-ca-certs.d.ts @@ -3,6 +3,4 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .code-inset { - z-index: 10; -} +declare module 'vscode-windows-ca-certs'; diff --git a/src/sql/workbench/api/electron-browser/sqlExtensionHost.contribution.ts b/src/typings/vsda.d.ts similarity index 82% rename from src/sql/workbench/api/electron-browser/sqlExtensionHost.contribution.ts rename to src/typings/vsda.d.ts index 91c1b1982bbf..5a0ca7212e82 100644 --- a/src/sql/workbench/api/electron-browser/sqlExtensionHost.contribution.ts +++ b/src/typings/vsda.d.ts @@ -3,4 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'sql/workbench/api/electron-browser/mainThreadModalDialog'; \ No newline at end of file +declare module 'vsda' { + export class signer { + sign(arg: any): any; + } +} diff --git a/src/typings/xterm-addon-search.d.ts b/src/typings/xterm-addon-search.d.ts new file mode 100644 index 000000000000..2f3be6f82bd2 --- /dev/null +++ b/src/typings/xterm-addon-search.d.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +// HACK: gulp-tsb doesn't play nice with importing from typings +// import { Terminal, ITerminalAddon } from 'xterm'; + +declare module 'xterm-addon-search' { + /** + * Options for a search. + */ + export interface ISearchOptions { + /** + * Whether the search term is a regex. + */ + regex?: boolean; + + /** + * Whether to search for a whole word, the result is only valid if it's + * suppounded in "non-word" characters such as `_`, `(`, `)` or space. + */ + wholeWord?: boolean; + + /** + * Whether the search is case sensitive. + */ + caseSensitive?: boolean; + + /** + * Whether to do an indcremental search, this will expand the selection if it + * still matches the term the user typed. Note that this only affects + * `findNext`, not `findPrevious`. + */ + incremental?: boolean; + } + + /** + * An xterm.js addon that provides search functionality. + */ + export class SearchAddon { + /** + * Activates the addon + * @param terminal The terminal the addon is being loaded in. + */ + public activate(terminal: any): void; + + /** + * Disposes the addon. + */ + public dispose(): void; + + /** + * Search forwards for the next result that matches the search term and + * options. + * @param term The search term. + * @param searchOptions The options for the search. + */ + public findNext(term: string, searchOptions?: ISearchOptions): boolean; + + /** + * Search backwards for the previous result that matches the search term and + * options. + * @param term The search term. + * @param searchOptions The options for the search. + */ + public findPrevious(term: string, searchOptions?: ISearchOptions): boolean; + } +} diff --git a/src/typings/xterm-addon-web-links.d.ts b/src/typings/xterm-addon-web-links.d.ts new file mode 100644 index 000000000000..da348f8c82eb --- /dev/null +++ b/src/typings/xterm-addon-web-links.d.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +// HACK: gulp-tsb doesn't play nice with importing from typings +// import { Terminal, ITerminalAddon } from 'xterm'; +interface ILinkMatcherOptions { + /** + * The index of the link from the regex.match(text) call. This defaults to 0 + * (for regular expressions without capture groups). + */ + matchIndex?: number; + + /** + * A callback that validates whether to create an individual link, pass + * whether the link is valid to the callback. + */ + validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void; + + /** + * A callback that fires when the mouse hovers over a link for a moment. + */ + tooltipCallback?: (event: MouseEvent, uri: string) => boolean | void; + + /** + * A callback that fires when the mouse leaves a link. Note that this can + * happen even when tooltipCallback hasn't fired for the link yet. + */ + leaveCallback?: () => void; + + /** + * The priority of the link matcher, this defines the order in which the link + * matcher is evaluated relative to others, from highest to lowest. The + * default value is 0. + */ + priority?: number; + + /** + * A callback that fires when the mousedown and click events occur that + * determines whether a link will be activated upon click. This enables + * only activating a link when a certain modifier is held down, if not the + * mouse event will continue propagation (eg. double click to select word). + */ + willLinkActivate?: (event: MouseEvent, uri: string) => boolean; +} + +declare module 'xterm-addon-web-links' { + /** + * An xterm.js addon that enables web links. + */ + export class WebLinksAddon { + /** + * Creates a new web links addon. + * @param handler The callback when the link is called. + * @param options Options for the link matcher. + */ + constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions); + + /** + * Activates the addon + * @param terminal The terminal the addon is being loaded in. + */ + public activate(terminal: any): void; + + /** + * Disposes the addon. + */ + public dispose(): void; + } +} diff --git a/src/typings/vscode-xterm.d.ts b/src/typings/xterm.d.ts similarity index 91% rename from src/typings/vscode-xterm.d.ts rename to src/typings/xterm.d.ts index 01e47b16b27b..486f1d42c0ad 100644 --- a/src/typings/vscode-xterm.d.ts +++ b/src/typings/xterm.d.ts @@ -9,7 +9,7 @@ /// -declare module 'vscode-xterm' { +declare module 'xterm' { /** * A string representing text font weight. */ @@ -76,31 +76,6 @@ declare module 'vscode-xterm' { */ drawBoldTextInBrightColors?: boolean; - /** - * Whether to enable the rendering of bold text. - * - * @deprecated Use fontWeight and fontWeightBold instead. - */ - enableBold?: boolean; - - /** - * What character atlas implementation to use. The character atlas caches drawn characters, - * speeding up rendering significantly. However, it can introduce some minor rendering - * artifacts. - * - * - 'none': Don't use an atlas. - * - 'static': Generate an atlas when the terminal starts or is reconfigured. This atlas will - * only contain ASCII characters in 16 colors. - * - 'dynamic': Generate an atlas using a LRU cache as characters are requested. Limited to - * ASCII characters (for now), but supports 256 colors. For characters covered by the static - * cache, it's slightly slower in comparison, since there's more overhead involved in - * managing the cache. - * - * Currently defaults to 'static'. This option may be removed in the future. If it is, passed - * parameters will be ignored. - */ - experimentalCharAtlas?: 'none' | 'static' | 'dynamic'; - /** * The font size used to render text. */ @@ -706,13 +681,6 @@ declare module 'vscode-xterm' { */ dispose(): void; - /** - * Destroys the terminal and detaches it from the DOM. - * - * @deprecated Use dispose() instead. - */ - destroy(): void; - /** * Scroll the display of the terminal * @param amount The number of lines to scroll down (negative scroll up). @@ -775,7 +743,7 @@ declare module 'vscode-xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode'): boolean; + getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode'): boolean; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -826,7 +794,7 @@ declare module 'vscode-xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode', value: boolean): void; + setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key. @@ -982,7 +950,7 @@ declare module 'vscode-xterm' { * * @param x The character index to get. */ - getCell(x: number): IBufferCell; + getCell(x: number): IBufferCell | undefined; /** * Gets the line as a string. Note that this is gets only the string for the line, not taking @@ -1013,8 +981,10 @@ declare module 'vscode-xterm' { } + + // Modifications to official .d.ts below -declare module 'vscode-xterm' { +declare module 'xterm' { interface TerminalCore { debug: boolean; @@ -1037,42 +1007,7 @@ declare module 'vscode-xterm' { fire(e: T): void; } - interface ISearchOptions { - /** - * Whether the find should be done as a regex. - */ - regex?: boolean; - /** - * Whether only whole words should match. - */ - wholeWord?: boolean; - /** - * Whether find should pay attention to case. - */ - caseSensitive?: boolean; - } - interface Terminal { _core: TerminalCore; - - webLinksInit(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void; - - /** - * Find the next instance of the term, then scroll to and select it. If it - * doesn't exist, do nothing. - * @param term The search term. - * @param findOptions Regex, whole word, and case sensitive options. - * @return Whether a result was found. - */ - findNext(term: string, findOptions: ISearchOptions): boolean; - - /** - * Find the previous instance of the term, then scroll to and select it. If it - * doesn't exist, do nothing. - * @param term The search term. - * @param findOptions Regex, whole word, and case sensitive options. - * @return Whether a result was found. - */ - findPrevious(term: string, findOptions: ISearchOptions): boolean; } -} +} \ No newline at end of file diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index a4434d722a65..c6cb3b7a80c9 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -24,7 +24,7 @@ export class ContextSubMenu extends SubmenuAction { export interface IContextMenuDelegate { getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number; }; - getActions(): Array; + getActions(): ReadonlyArray; getActionViewItem?(action: IAction): IActionViewItem | undefined; getActionsContext?(event?: IContextMenuEvent): any; getKeyBinding?(action: IAction): ResolvedKeybinding | undefined; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 02a6055c3a8d..fd4bcebba8a3 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -11,7 +11,7 @@ import { TimeoutTimer } from 'vs/base/common/async'; import { CharCode } from 'vs/base/common/charCode'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { coalesce } from 'vs/base/common/arrays'; @@ -905,10 +905,9 @@ export const EventHelper = { } }; -export interface IFocusTracker { +export interface IFocusTracker extends Disposable { onDidFocus: Event; onDidBlur: Event; - dispose(): void; } export function saveParentsScrollTop(node: Element): number[] { @@ -929,21 +928,20 @@ export function restoreParentsScrollTop(node: Element, state: number[]): void { } } -class FocusTracker implements IFocusTracker { +class FocusTracker extends Disposable implements IFocusTracker { - private _onDidFocus = new Emitter(); - readonly onDidFocus: Event = this._onDidFocus.event; + private readonly _onDidFocus = this._register(new Emitter()); + public readonly onDidFocus: Event = this._onDidFocus.event; - private _onDidBlur = new Emitter(); - readonly onDidBlur: Event = this._onDidBlur.event; - - private disposables: IDisposable[] = []; + private readonly _onDidBlur = this._register(new Emitter()); + public readonly onDidBlur: Event = this._onDidBlur.event; constructor(element: HTMLElement | Window) { + super(); let hasFocus = isAncestor(document.activeElement, element); let loosingFocus = false; - let onFocus = () => { + const onFocus = () => { loosingFocus = false; if (!hasFocus) { hasFocus = true; @@ -951,7 +949,7 @@ class FocusTracker implements IFocusTracker { } }; - let onBlur = () => { + const onBlur = () => { if (hasFocus) { loosingFocus = true; window.setTimeout(() => { @@ -964,14 +962,8 @@ class FocusTracker implements IFocusTracker { } }; - domEvent(element, EventType.FOCUS, true)(onFocus, null, this.disposables); - domEvent(element, EventType.BLUR, true)(onBlur, null, this.disposables); - } - - dispose(): void { - this.disposables = dispose(this.disposables); - this._onDidFocus.dispose(); - this._onDidBlur.dispose(); + this._register(domEvent(element, EventType.FOCUS, true)(onFocus)); + this._register(domEvent(element, EventType.BLUR, true)(onBlur)); } } diff --git a/src/vs/base/browser/globalMouseMoveMonitor.ts b/src/vs/base/browser/globalMouseMoveMonitor.ts index f4fd8b538236..b29d574cfd40 100644 --- a/src/vs/base/browser/globalMouseMoveMonitor.ts +++ b/src/vs/base/browser/globalMouseMoveMonitor.ts @@ -6,7 +6,7 @@ import * as dom from 'vs/base/browser/dom'; import { IframeUtils } from 'vs/base/browser/iframe'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; export interface IStandardMouseMoveEventData { leftButton: boolean; @@ -36,24 +36,16 @@ export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData, }; } -export class GlobalMouseMoveMonitor extends Disposable { +export class GlobalMouseMoveMonitor implements IDisposable { - private hooks: IDisposable[]; - private mouseMoveEventMerger: IEventMerger | null; - private mouseMoveCallback: IMouseMoveCallback | null; - private onStopCallback: IOnStopCallback | null; - - constructor() { - super(); - this.hooks = []; - this.mouseMoveEventMerger = null; - this.mouseMoveCallback = null; - this.onStopCallback = null; - } + private readonly hooks = new DisposableStore(); + private mouseMoveEventMerger: IEventMerger | null = null; + private mouseMoveCallback: IMouseMoveCallback | null = null; + private onStopCallback: IOnStopCallback | null = null; public dispose(): void { this.stopMonitoring(false); - super.dispose(); + this.hooks.dispose(); } public stopMonitoring(invokeStopCallback: boolean): void { @@ -63,10 +55,10 @@ export class GlobalMouseMoveMonitor extends Disposable { } // Unhook - this.hooks = dispose(this.hooks); + this.hooks.clear(); this.mouseMoveEventMerger = null; this.mouseMoveCallback = null; - let onStopCallback = this.onStopCallback; + const onStopCallback = this.onStopCallback; this.onStopCallback = null; if (invokeStopCallback && onStopCallback) { @@ -74,8 +66,8 @@ export class GlobalMouseMoveMonitor extends Disposable { } } - public isMonitoring() { - return this.hooks.length > 0; + public isMonitoring(): boolean { + return !!this.mouseMoveEventMerger; } public startMonitoring( @@ -93,32 +85,32 @@ export class GlobalMouseMoveMonitor extends Disposable { let windowChain = IframeUtils.getSameOriginWindowChain(); for (const element of windowChain) { - this.hooks.push(dom.addDisposableThrottledListener(element.window.document, 'mousemove', + this.hooks.add(dom.addDisposableThrottledListener(element.window.document, 'mousemove', (data: R) => this.mouseMoveCallback!(data), (lastEvent: R, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent) )); - this.hooks.push(dom.addDisposableListener(element.window.document, 'mouseup', (e: MouseEvent) => this.stopMonitoring(true))); + this.hooks.add(dom.addDisposableListener(element.window.document, 'mouseup', (e: MouseEvent) => this.stopMonitoring(true))); } if (IframeUtils.hasDifferentOriginAncestor()) { let lastSameOriginAncestor = windowChain[windowChain.length - 1]; // We might miss a mouse up if it happens outside the iframe // This one is for Chrome - this.hooks.push(dom.addDisposableListener(lastSameOriginAncestor.window.document, 'mouseout', (browserEvent: MouseEvent) => { + this.hooks.add(dom.addDisposableListener(lastSameOriginAncestor.window.document, 'mouseout', (browserEvent: MouseEvent) => { let e = new StandardMouseEvent(browserEvent); if (e.target.tagName.toLowerCase() === 'html') { this.stopMonitoring(true); } })); // This one is for FF - this.hooks.push(dom.addDisposableListener(lastSameOriginAncestor.window.document, 'mouseover', (browserEvent: MouseEvent) => { + this.hooks.add(dom.addDisposableListener(lastSameOriginAncestor.window.document, 'mouseover', (browserEvent: MouseEvent) => { let e = new StandardMouseEvent(browserEvent); if (e.target.tagName.toLowerCase() === 'html') { this.stopMonitoring(true); } })); // This one is for IE - this.hooks.push(dom.addDisposableListener(lastSameOriginAncestor.window.document.body, 'mouseleave', (browserEvent: MouseEvent) => { + this.hooks.add(dom.addDisposableListener(lastSameOriginAncestor.window.document.body, 'mouseleave', (browserEvent: MouseEvent) => { this.stopMonitoring(true); })); } diff --git a/src/vs/base/browser/hash.ts b/src/vs/base/browser/hash.ts deleted file mode 100644 index 222332b2f822..000000000000 --- a/src/vs/base/browser/hash.ts +++ /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. - *--------------------------------------------------------------------------------------------*/ - -export function createSHA1(content: string): Thenable { - if (typeof require !== 'undefined') { - const _crypto: typeof crypto = require.__$__nodeRequire('crypto'); - return Promise.resolve(_crypto['createHash']('sha1').update(content).digest('hex')); - } - return crypto.subtle.digest('SHA-1', new TextEncoder().encode(content)).then(buffer => { - // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Converting_a_digest_to_a_hex_string - return Array.prototype.map.call(new Uint8Array(buffer), (value: number) => `00${value.toString(16)}`.slice(-2)).join(''); - }); -} \ No newline at end of file diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index d400bbadf36e..e4e315cd21b0 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -17,7 +17,7 @@ import { cloneAndChange } from 'vs/base/common/objects'; export interface IContentActionHandler { callback: (content: string, event?: IMouseEvent) => void; - disposeables: IDisposable[]; + readonly disposeables: IDisposable[]; } export interface RenderOptions { diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index ea3d32745ffc..92345a2e4582 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -6,7 +6,7 @@ import 'vs/css!./actionbar'; import * as platform from 'vs/base/common/platform'; import * as nls from 'vs/nls'; -import { Disposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { SelectBox, ISelectOptionItem, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox'; import { IAction, IActionRunner, Action, IActionChangeEvent, ActionRunner, IRunEvent } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; @@ -16,16 +16,14 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { Event, Emitter } from 'vs/base/common/event'; -import { asArray } from 'vs/base/common/arrays'; -export interface IActionViewItem { +export interface IActionViewItem extends IDisposable { actionRunner: IActionRunner; setActionContext(context: any): void; render(element: HTMLElement): void; isEnabled(): boolean; focus(fromRight?: boolean): void; blur(): void; - dispose(): void; } export interface IBaseActionViewItemOptions { @@ -261,6 +259,9 @@ export class ActionViewItem extends BaseActionViewItem { this.label.setAttribute('role', 'menuitem'); } else { this.label.setAttribute('role', 'button'); + + // TODO @misolori remove before shipping stable + this.label.setAttribute('data-title', this._action.id); } } @@ -593,8 +594,8 @@ export class ActionBar extends Disposable implements IActionRunner { return this.domNode; } - push(arg: IAction | IAction[], options: IActionOptions = {}): void { - const actions: IAction[] = asArray(arg); + push(arg: IAction | ReadonlyArray, options: IActionOptions = {}): void { + const actions: ReadonlyArray = Array.isArray(arg) ? arg : [arg]; let index = types.isNumber(options.index) ? options.index : null; diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 24f6dafa8ad0..e96ce6f31912 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -9,7 +9,7 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle import { commonPrefixLength } from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; -import { dispose, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import 'vs/css!./breadcrumbsWidget'; @@ -134,14 +134,14 @@ export class BreadcrumbsWidget { } private _updateDimensions(dim: dom.Dimension): IDisposable { - let disposables: IDisposable[] = []; - disposables.push(dom.modify(() => { + const disposables = new DisposableStore(); + disposables.add(dom.modify(() => { this._dimension = dim; this._domNode.style.width = `${dim.width}px`; this._domNode.style.height = `${dim.height}px`; - disposables.push(this._updateScrollbar()); + disposables.add(this._updateScrollbar()); })); - return combinedDisposable(disposables); + return disposables; } private _updateScrollbar(): IDisposable { diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts index d51cb04707eb..e0fb0490dc10 100644 --- a/src/vs/base/browser/ui/centered/centeredViewLayout.ts +++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts @@ -7,7 +7,7 @@ import { SplitView, Orientation, ISplitViewStyles, IView as ISplitViewView } fro import { $ } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { IView } from 'vs/base/browser/ui/grid/gridview'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; export interface CenteredViewState { @@ -48,7 +48,7 @@ export interface ICenteredViewStyles extends ISplitViewStyles { background: Color; } -export class CenteredViewLayout { +export class CenteredViewLayout implements IDisposable { private splitView?: SplitView; private width: number = 0; @@ -56,7 +56,7 @@ export class CenteredViewLayout { private style: ICenteredViewStyles; private didLayout = false; private emptyViews: ISplitViewView[] | undefined; - private splitViewDisposables: IDisposable[] = []; + private readonly splitViewDisposables = new DisposableStore(); constructor(private container: HTMLElement, private view: IView, public readonly state: CenteredViewState = { leftMarginRatio: GOLDEN_RATIO.leftMarginRatio, rightMarginRatio: GOLDEN_RATIO.rightMarginRatio }) { this.container.appendChild(this.view.element); @@ -117,13 +117,13 @@ export class CenteredViewLayout { styles: this.style }); - this.splitViewDisposables.push(this.splitView.onDidSashChange(() => { + this.splitViewDisposables.add(this.splitView.onDidSashChange(() => { if (this.splitView) { this.state.leftMarginRatio = this.splitView.getViewSize(0) / this.width; this.state.rightMarginRatio = this.splitView.getViewSize(2) / this.width; } })); - this.splitViewDisposables.push(this.splitView.onDidSashReset(() => { + this.splitViewDisposables.add(this.splitView.onDidSashReset(() => { this.state.leftMarginRatio = GOLDEN_RATIO.leftMarginRatio; this.state.rightMarginRatio = GOLDEN_RATIO.rightMarginRatio; this.resizeMargins(); @@ -138,7 +138,7 @@ export class CenteredViewLayout { if (this.splitView) { this.container.removeChild(this.splitView.el); } - this.splitViewDisposables = dispose(this.splitViewDisposables); + this.splitViewDisposables.clear(); if (this.splitView) { this.splitView.dispose(); } @@ -153,7 +153,7 @@ export class CenteredViewLayout { } dispose(): void { - this.splitViewDisposables = dispose(this.splitViewDisposables); + this.splitViewDisposables.dispose(); if (this.splitView) { this.splitView.dispose(); diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index 47adfc1499a8..8dbd54cf58ef 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -12,7 +12,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import * as objects from 'vs/base/common/objects'; import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export interface ICheckboxOpts extends ICheckboxStyles { readonly actionClassName?: string; @@ -31,19 +31,19 @@ const defaultOpts = { export class CheckboxActionViewItem extends BaseActionViewItem { private checkbox: Checkbox; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); render(container: HTMLElement): void { this.element = container; - this.disposables = dispose(this.disposables); + this.disposables.clear(); this.checkbox = new Checkbox({ actionClassName: this._action.class, isChecked: this._action.checked, title: this._action.label }); - this.disposables.push(this.checkbox); - this.checkbox.onChange(() => this._action.checked = this.checkbox.checked, this, this.disposables); + this.disposables.add(this.checkbox); + this.disposables.add(this.checkbox.onChange(() => this._action.checked = this.checkbox.checked, this)); this.element.appendChild(this.checkbox.domNode); } @@ -64,10 +64,9 @@ export class CheckboxActionViewItem extends BaseActionViewItem { } dipsose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } - } export class Checkbox extends Widget { diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index bc28c7e3fd77..5fca8286bb89 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -5,7 +5,7 @@ import 'vs/css!./contextview'; import * as DOM from 'vs/base/browser/dom'; -import { IDisposable, dispose, toDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Range } from 'vs/base/common/range'; export interface IAnchor { @@ -128,21 +128,21 @@ export class ContextView extends Disposable { this.container = container; this.container.appendChild(this.view); - const toDisposeOnSetContainer: IDisposable[] = []; + const toDisposeOnSetContainer = new DisposableStore(); ContextView.BUBBLE_UP_EVENTS.forEach(event => { - toDisposeOnSetContainer.push(DOM.addStandardDisposableListener(this.container!, event, (e: Event) => { + toDisposeOnSetContainer.add(DOM.addStandardDisposableListener(this.container!, event, (e: Event) => { this.onDOMEvent(e, false); })); }); ContextView.BUBBLE_DOWN_EVENTS.forEach(event => { - toDisposeOnSetContainer.push(DOM.addStandardDisposableListener(this.container!, event, (e: Event) => { + toDisposeOnSetContainer.add(DOM.addStandardDisposableListener(this.container!, event, (e: Event) => { this.onDOMEvent(e, true); }, true)); }); - this.toDisposeOnSetContainer = combinedDisposable(toDisposeOnSetContainer); + this.toDisposeOnSetContainer = toDisposeOnSetContainer; } } @@ -260,12 +260,13 @@ export class ContextView extends Disposable { } hide(data?: any): void { - if (this.delegate && this.delegate.onHide) { - this.delegate.onHide(data); - } - + const delegate = this.delegate; this.delegate = null; + if (delegate && delegate.onHide) { + delegate.onHide(data); + } + if (this.toDisposeOnClean) { this.toDisposeOnClean.dispose(); this.toDisposeOnClean = null; diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index a3bd38835b5a..0834eb8927b2 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -192,12 +192,12 @@ export interface IContextMenuProvider { } export interface IActionProvider { - getActions(): IAction[]; + getActions(): ReadonlyArray; } export interface IDropdownMenuOptions extends IBaseDropdownOptions { contextMenuProvider: IContextMenuProvider; - actions?: IAction[]; + actions?: ReadonlyArray; actionProvider?: IActionProvider; menuClassName?: string; } @@ -205,7 +205,7 @@ export interface IDropdownMenuOptions extends IBaseDropdownOptions { export class DropdownMenu extends BaseDropdown { private _contextMenuProvider: IContextMenuProvider; private _menuOptions: IMenuOptions; - private _actions: IAction[]; + private _actions: ReadonlyArray; private actionProvider?: IActionProvider; private menuClassName: string; @@ -226,7 +226,7 @@ export class DropdownMenu extends BaseDropdown { return this._menuOptions; } - private get actions(): IAction[] { + private get actions(): ReadonlyArray { if (this.actionProvider) { return this.actionProvider.getActions(); } @@ -234,7 +234,7 @@ export class DropdownMenu extends BaseDropdown { return this._actions; } - private set actions(actions: IAction[]) { + private set actions(actions: ReadonlyArray) { this._actions = actions; } @@ -275,7 +275,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { private clazz: string | undefined; private anchorAlignmentProvider: (() => AnchorAlignment) | undefined; - constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); + constructor(action: IAction, menuActions: ReadonlyArray, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment) { super(null, action); diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 4e2d1a196ec5..092ab6f41c91 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -5,7 +5,7 @@ import 'vs/css!./gridview'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { tail2 as tail, equals } from 'vs/base/common/arrays'; import { orthogonal, IView, GridView, Sizing as GridViewSizing, Box, IGridViewStyles } from './gridview'; import { Event, Emitter } from 'vs/base/common/event'; @@ -191,12 +191,10 @@ export interface IGridOptions { proportionalLayout?: boolean; } -export class Grid implements IDisposable { +export class Grid extends Disposable { protected gridview: GridView; private views = new Map(); - private disposables: IDisposable[] = []; - get orientation(): Orientation { return this.gridview.orientation; } set orientation(orientation: Orientation) { this.gridview.orientation = orientation; } @@ -214,10 +212,11 @@ export class Grid implements IDisposable { sashResetSizing: Sizing = Sizing.Distribute; constructor(view: T, options: IGridOptions = {}) { + super(); this.gridview = new GridView(options); - this.disposables.push(this.gridview); + this._register(this.gridview); - this.gridview.onDidSashReset(this.doResetViewSize, this, this.disposables); + this._register(this.gridview.onDidSashReset(this.doResetViewSize, this)); this._addView(view, 0, [0]); } @@ -375,10 +374,6 @@ export class Grid implements IDisposable { this.gridview.distributeViewSizes(parentLocation); } } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } export interface ISerializableView extends IView { diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 1d72186b6b6a..44fc80ba5646 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -28,7 +28,8 @@ export interface IInputOptions extends IInputBoxStyles { readonly type?: string; readonly validationOptions?: IInputValidationOptions; readonly flexibleHeight?: boolean; - readonly actions?: IAction[]; + readonly actions?: ReadonlyArray; + // {{SQL CARBON EDIT}} Candidate for addition to vscode readonly min?: string; @@ -99,7 +100,7 @@ export class InputBox extends Widget { private placeholder: string; private ariaLabel: string; private validation?: IInputValidator; - private state: string | null = 'idle'; + private state: 'idle' | 'open' | 'closed' = 'idle'; private cachedHeight: number | null; // {{SQL CARBON EDIT}} - Add showValidationMessage and set inputBackground, inputForeground, and inputBorder as protected @@ -422,8 +423,6 @@ export class InputBox extends Widget { let div: HTMLElement; let layout = () => div.style.width = dom.getTotalWidth(this.element) + 'px'; - this.state = 'open'; - this.contextViewProvider.showContextView({ getAnchor: () => this.element, anchorAlignment: AnchorAlignment.RIGHT, @@ -454,18 +453,25 @@ export class InputBox extends Widget { return null; }, + onHide: () => { + this.state = 'closed'; + }, layout: layout }); + + this.state = 'open'; } private _hideMessage(): void { - if (!this.contextViewProvider || this.state !== 'open') { + if (!this.contextViewProvider) { return; } - this.state = 'idle'; + if (this.state === 'open') { + this.contextViewProvider.hideContextView(); + } - this.contextViewProvider.hideContextView(); + this.state = 'idle'; } private onValueChange(): void { @@ -555,7 +561,7 @@ export class InputBox extends Widget { this.contextViewProvider = undefined; this.message = null; this.validation = undefined; - this.state = null; + this.state = null!; // StrictNullOverride: nulling out ok in dispose this.actionbar = undefined; super.dispose(); diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css index aec27d7afd37..15660767c2cc 100644 --- a/src/vs/base/browser/ui/menu/menu.css +++ b/src/vs/base/browser/ui/menu/menu.css @@ -133,7 +133,7 @@ } .monaco-menu .monaco-action-bar.vertical .action-item { - border: 1px solid transparent; /* prevents jumping behaviour on hover or focus */ + border: thin solid transparent; /* prevents jumping behaviour on hover or focus */ } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index d65d930df31f..3fda64e3d95e 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -12,7 +12,7 @@ import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; @@ -59,7 +59,7 @@ export interface IMenuStyles { } export class SubmenuAction extends Action { - constructor(label: string, public entries: Array, cssClass?: string) { + constructor(label: string, public entries: ReadonlyArray, cssClass?: string) { super(!!cssClass ? cssClass : 'submenu', label, '', true); } } @@ -71,15 +71,14 @@ interface ISubMenuData { export class Menu extends ActionBar { private mnemonics: Map>; - private menuDisposables: IDisposable[]; + private readonly menuDisposables: DisposableStore; private scrollableElement: DomScrollableElement; private menuElement: HTMLElement; private scrollTopHold: number | undefined; private readonly _onScroll: Emitter; - constructor(container: HTMLElement, actions: IAction[], options: IMenuOptions = {}) { - + constructor(container: HTMLElement, actions: ReadonlyArray, options: IMenuOptions = {}) { addClass(container, 'monaco-menu-container'); container.setAttribute('role', 'presentation'); const menuElement = document.createElement('div'); @@ -103,7 +102,7 @@ export class Menu extends ActionBar { this.actionsList.tabIndex = 0; - this.menuDisposables = []; + this.menuDisposables = this._register(new DisposableStore()); addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => { const event = new StandardKeyboardEvent(e); @@ -115,7 +114,7 @@ export class Menu extends ActionBar { }); if (options.enableMnemonics) { - this.menuDisposables.push(addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => { + this.menuDisposables.add(addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => { const key = e.key.toLocaleLowerCase(); if (this.mnemonics.has(key)) { EventHelper.stop(e, true); @@ -217,9 +216,9 @@ export class Menu extends ActionBar { menuElement.style.maxHeight = `${Math.max(10, window.innerHeight - container.getBoundingClientRect().top - 30)}px`; - this.scrollableElement.onScroll(() => { + this.menuDisposables.add(this.scrollableElement.onScroll(() => { this._onScroll.fire(); - }, this, this.menuDisposables); + }, this)); this._register(addDisposableListener(this.menuElement, EventType.SCROLL, (e: ScrollEvent) => { if (this.scrollTopHold !== undefined) { @@ -557,7 +556,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { const isSelected = this.element && hasClass(this.element, 'focused'); const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor; const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : this.menuStyle.backgroundColor; - const border = isSelected && this.menuStyle.selectionBorderColor ? `1px solid ${this.menuStyle.selectionBorderColor}` : null; + const border = isSelected && this.menuStyle.selectionBorderColor ? `thin solid ${this.menuStyle.selectionBorderColor}` : null; this.item.style.color = fgColor ? `${fgColor}` : null; this.check.style.backgroundColor = fgColor ? `${fgColor}` : null; @@ -575,14 +574,14 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { private mysubmenu: Menu | null; private submenuContainer: HTMLElement | undefined; private submenuIndicator: HTMLElement; - private submenuDisposables: IDisposable[] = []; + private readonly submenuDisposables = this._register(new DisposableStore()); private mouseOver: boolean; private showScheduler: RunOnceScheduler; private hideScheduler: RunOnceScheduler; constructor( action: IAction, - private submenuActions: IAction[], + private submenuActions: ReadonlyArray, private parentData: ISubMenuData, private submenuOptions?: IMenuOptions ) { @@ -675,7 +674,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.parentData.submenu = undefined; if (this.submenuContainer) { - this.submenuDisposables = dispose(this.submenuDisposables); + this.submenuDisposables.clear(); this.submenuContainer = undefined; } } @@ -708,7 +707,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`; } - this.submenuDisposables.push(addDisposableListener(this.submenuContainer, EventType.KEY_UP, e => { + this.submenuDisposables.add(addDisposableListener(this.submenuContainer, EventType.KEY_UP, e => { let event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.LeftArrow)) { EventHelper.stop(e, true); @@ -720,12 +719,12 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.parentData.submenu = undefined; } - this.submenuDisposables = dispose(this.submenuDisposables); + this.submenuDisposables.clear(); this.submenuContainer = undefined; } })); - this.submenuDisposables.push(addDisposableListener(this.submenuContainer, EventType.KEY_DOWN, e => { + this.submenuDisposables.add(addDisposableListener(this.submenuContainer, EventType.KEY_DOWN, e => { let event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.LeftArrow)) { EventHelper.stop(e, true); @@ -733,7 +732,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { })); - this.submenuDisposables.push(this.parentData.submenu.onDidCancel(() => { + this.submenuDisposables.add(this.parentData.submenu.onDidCancel(() => { this.parentData.parent.focus(); if (this.parentData.submenu) { @@ -741,7 +740,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.parentData.submenu = undefined; } - this.submenuDisposables = dispose(this.submenuDisposables); + this.submenuDisposables.clear(); this.submenuContainer = undefined; })); @@ -781,7 +780,6 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { } if (this.submenuContainer) { - this.submenuDisposables = dispose(this.submenuDisposables); this.submenuContainer = undefined; } } diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index c534ba5ba018..8589ca21738f 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -15,7 +15,7 @@ import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; import { asArray } from 'vs/base/common/arrays'; @@ -29,7 +29,7 @@ export interface IMenuBarOptions { } export interface MenuBarMenu { - actions: IAction[]; + actions: ReadonlyArray; label: string; } @@ -48,7 +48,7 @@ export class MenuBar extends Disposable { buttonElement: HTMLElement; titleElement: HTMLElement; label: string; - actions?: IAction[]; + actions?: ReadonlyArray; }[]; private overflowMenu: { @@ -896,7 +896,7 @@ interface IModifierKeyStatus { class ModifierKeyEmitter extends Emitter { - private _subscriptions: IDisposable[] = []; + private readonly _subscriptions = new DisposableStore(); private _keyStatus: IModifierKeyStatus; private static instance: ModifierKeyEmitter; @@ -909,7 +909,7 @@ class ModifierKeyEmitter extends Emitter { ctrlKey: false }; - this._subscriptions.push(domEvent(document.body, 'keydown', true)(e => { + this._subscriptions.add(domEvent(document.body, 'keydown', true)(e => { const event = new StandardKeyboardEvent(e); if (e.altKey && !this._keyStatus.altKey) { @@ -933,7 +933,7 @@ class ModifierKeyEmitter extends Emitter { } })); - this._subscriptions.push(domEvent(document.body, 'keyup', true)(e => { + this._subscriptions.add(domEvent(document.body, 'keyup', true)(e => { if (!e.altKey && this._keyStatus.altKey) { this._keyStatus.lastKeyReleased = 'alt'; } else if (!e.ctrlKey && this._keyStatus.ctrlKey) { @@ -957,21 +957,21 @@ class ModifierKeyEmitter extends Emitter { } })); - this._subscriptions.push(domEvent(document.body, 'mousedown', true)(e => { + this._subscriptions.add(domEvent(document.body, 'mousedown', true)(e => { this._keyStatus.lastKeyPressed = undefined; })); - this._subscriptions.push(domEvent(document.body, 'mouseup', true)(e => { + this._subscriptions.add(domEvent(document.body, 'mouseup', true)(e => { this._keyStatus.lastKeyPressed = undefined; })); - this._subscriptions.push(domEvent(document.body, 'mousemove', true)(e => { + this._subscriptions.add(domEvent(document.body, 'mousemove', true)(e => { if (e.buttons) { this._keyStatus.lastKeyPressed = undefined; } })); - this._subscriptions.push(domEvent(window, 'blur')(e => { + this._subscriptions.add(domEvent(window, 'blur')(e => { this._keyStatus.lastKeyPressed = undefined; this._keyStatus.lastKeyReleased = undefined; this._keyStatus.altKey = false; @@ -992,6 +992,6 @@ class ModifierKeyEmitter extends Emitter { dispose() { super.dispose(); - this._subscriptions = dispose(this._subscriptions); + this._subscriptions.dispose(); } } diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css index d583733048ed..d9cc1b7a4fa2 100644 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css +++ b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css @@ -1,7 +1,7 @@ @font-face { font-family: "octicons"; - src: url("./octicons.ttf?91284a5a76ea88faeb754359b7f7cd03") format("truetype"), -url("./octicons.svg?91284a5a76ea88faeb754359b7f7cd03#octicons") format("svg"); + src: url("./octicons.ttf?1b0f2a9535896866c74dd24eedeb4374") format("truetype"), +url("./octicons.svg?1b0f2a9535896866c74dd24eedeb4374#octicons") format("svg"); } .octicon, .mega-octicon { @@ -235,10 +235,14 @@ url("./octicons.svg?91284a5a76ea88faeb754359b7f7cd03#octicons") format("svg"); .octicon-zap:before { content: "\26a1" } .octicon-archive:before { content: "\f101" } .octicon-arrow-both:before { content: "\f102" } -.octicon-eye-closed:before { content: "\f103" } -.octicon-fold-down:before { content: "\f104" } -.octicon-fold-up:before { content: "\f105" } -.octicon-github-action:before { content: "\f106" } -.octicon-play:before { content: "\f107" } -.octicon-remote:before { content: "\f108" } -.octicon-request-changes:before { content: "\f109" } +.octicon-error:before { content: "\f103" } +.octicon-eye-closed:before { content: "\f104" } +.octicon-fold-down:before { content: "\f105" } +.octicon-fold-up:before { content: "\f106" } +.octicon-github-action:before { content: "\f107" } +.octicon-info-outline:before { content: "\f108" } +.octicon-play:before { content: "\f109" } +.octicon-remote:before { content: "\f10a" } +.octicon-request-changes:before { content: "\f10b" } +.octicon-smiley-outline:before { content: "\f10c" } +.octicon-warning:before { content: "\f10d" } diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg index af8396097861..3f4ab4f18079 100644 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg +++ b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg @@ -42,10 +42,10 @@ horiz-adv-x="625" d=" M312.5 632.5L0 257.5H187.5V7.5H437.5V257.5H625L312.5 632.5z" /> + horiz-adv-x="1000" d=" M881.8625 -40.39375L697.0187500000001 374.6875V593.4375H751.7062500000001V648.125H259.51625V593.4375H314.20375V374.6875L129.906875 -40.39375C113.500625 -76.4874999999999 140.2975 -117.5 179.6725 -117.5H832.6437500000001C872.01875 -117.5 898.26875 -76.4874999999999 882.40625 -40.39375H881.8625zM300.531875 210.625L368.89125 374.6875V593.4375H642.3312500000001V374.6875L710.6875 210.625H300.531875V210.625zM532.95375 320H587.64125V265.3125H532.95375V320V320zM478.26625 374.6875H423.57875V429.375H478.26625V374.6875V374.6875zM478.26625 538.75H532.95375V484.0625H478.26625V538.75V538.75zM478.26625 702.8125H423.57875V757.5H478.26625V702.8125V702.8125z" /> + horiz-adv-x="875" d=" M816.66875 115.83125V57.5H0V115.83125L42.5833125 149.6687499999999C87.5 194.58125 89.833125 298.416875 112 407.5C156.916875 627.416875 350 699.166875 350 699.166875C350 731.25 376.25 757.5 408.333125 757.5C440.416875 757.5 466.666875 731.25 466.666875 699.166875C466.666875 699.166875 664.4187499999999 627.416875 709.33125 407.5C731.5 297.833125 733.8312500000001 194 778.75 149.6687499999999L817.25 115.83125H816.66875zM408.333125 -117.5C473.083125 -117.5 525 -65.5812499999999 525 -0.8312500000001H291.666875C291.666875 -65.5812499999999 343.583125 -117.5 408.333125 -117.5V-117.5z" /> @@ -166,8 +166,11 @@ - + + + horiz-adv-x="1000" d=" M500 757.5C257.6925 757.5 62.5 562.3075 62.5 320C62.5 77.69375 257.6925 -117.5 500 -117.5C742.30625 -117.5 937.5 77.69375 937.5 320C937.5 562.3075 742.30625 757.5 500 757.5zM560.57625 10.3812499999999H432.6925V447.884375H560.57625V10.3812499999999zM560.57625 508.460625H432.6925V636.346875H560.57625V508.460625z" /> @@ -392,7 +398,7 @@ unicode="" horiz-adv-x="1000" d=" M625 745V695L656.25 632.5L375 445H137.5C110 445 95.625 411.875 116.25 391.25L312.5 195L62.5 -117.5L375 132.5L571.25 -63.75A31.25 31.25 0 0 1 625 -42.5V195L812.5 476.25L875 445H925C952.5 445 966.875 478.125 946.25 498.75L678.75 766.25A31.25 31.25 0 0 1 625 745z" /> + horiz-adv-x="937.5" d=" M605.46875 101.25H769.53125V648.125H605.46875V101.25V101.25zM386.71875 210.625H550.78125V648.125H386.71875V210.625V210.625zM167.96875 -8.125H332.03125V648.125H167.96875V-8.125V-8.125zM113.28125 -62.8125H824.21875V702.8125H113.28125V-62.8125V-62.8125zM824.21875 757.5H113.28125C83.09375 757.5 58.59375 733 58.59375 702.8125V-62.8125C58.59375 -93 83.09375 -117.5 113.28125 -117.5H824.21875C854.40625 -117.5 878.90625 -93 878.90625 -62.8125V702.8125C878.90625 733 854.40625 757.5 824.21875 757.5V757.5V757.5z" /> @@ -422,7 +428,7 @@ unicode="" horiz-adv-x="1000" d=" M299.375 438.125C315 453.75 315 480 299.375 495.625C279.375 516.25 269.3750000000001 543.125 269.3750000000001 570C269.3750000000001 596.875 279.3750000000001 623.75 299.3750000000001 644.375C315.0000000000001 660.625 315.0000000000001 686.25 299.3750000000001 701.875A38.3125 38.3125 0 0 1 271.2500000000001 713.75C261.2500000000001 713.75 250.6250000000001 710 243.1250000000001 701.875C207.5000000000001 665.625 190 617.5 190 570C190 522.5 208.125 474.375 243.1250000000001 438.1250000000001C258.7500000000001 422.5000000000001 284.3750000000001 422.5000000000001 299.3750000000001 438.1250000000001zM145.625 787.5A40.6875 40.6875 0 0 1 88.125 787.5C30 727.5 0.625 648.75 0.625 570.625C0.625 491.875 30 413.125 88.125 353.125C103.75 336.875 129.375 336.875 145 353.125S160.625 395.625 145 411.875C102.5 455.6249999999999 81.25 513.125 81.25 570.625C81.25 628.1249999999999 102.5 685.6249999999999 145 729.3749999999999A41.25 41.25 0 0 1 145.625 787.4999999999999zM501.25 468.7500000000001A101.25 101.25 0 1 1 400 570C399.3750000000001 514.375 445 468.75 501.25 468.75zM911.875 786.875A39.25 39.25 0 0 1 855 786.875C839.375 770.625 839.375 744.375 855 728.125C897.5 684.375 918.75 626.875 918.75 569.375C918.75 511.875 897.5 455 855 410.625C839.375 394.375 839.375 368.1250000000001 855 351.875A40.6875 40.6875 0 0 1 912.5 351.875C970.625 411.875 1000 490.625 1000 569.375A315.5 315.5 0 0 1 911.875 786.875zM501.25 387.5C475.6249999999999 387.5 449.375 393.75 426.25 406.25L229.375 -116.8749999999999H322.5L376.25 -54.3749999999999H626.25L678.75 -116.8749999999999H771.875L575.625 406.25C551.875 393.75 526.8750000000001 387.5 501.2500000000001 387.5zM500.625 357.5L563.75 132.5H438.75L500.625 357.5zM376.25 8.125L438.75 70.625H563.75L626.25 8.125H376.25zM700.625 701.875C685 686.25 685 660 700.625 644.375C720.6250000000001 623.75 730.6250000000001 596.875 730.6250000000001 570C730.6250000000001 543.125 720.6250000000001 516.25 700.625 495.6250000000001C685 479.3750000000001 685 453.7500000000001 700.625 438.1250000000001A39.375 39.375 0 0 1 756.8750000000001 438.1250000000001C792.5000000000001 474.3750000000001 810 522.5 810 570C810 617.5 792.5000000000001 665.625 756.8750000000001 701.875A39.625 39.625 0 0 1 700.625 701.875z" /> + + horiz-adv-x="1000" d=" M500 757.5C258.375 757.5 62.5 561.625 62.5 320C62.5 78.375 258.375 -117.5 500 -117.5C741.625 -117.5 937.5 78.375 937.5 320C937.5 561.625 741.625 757.5 500 757.5zM653.125 582.5C689.375 582.5 718.75 543.3125 718.75 495C718.75 446.6875 689.375 407.5 653.125 407.5C616.875 407.5 587.5 446.6875 587.5 495C587.5 543.3125 616.875 582.5 653.125 582.5zM346.875 582.5C383.125 582.5 412.5 543.3125 412.5 495C412.5 446.6875 383.125 407.5 346.875 407.5C310.625 407.5 281.25 446.6875 281.25 495C281.25 543.3125 310.625 582.5 346.875 582.5zM778.1875 221.5625C736.4375 103.8125 624.6875 24.6875 500 24.6875C375.3125 24.6875 263.5 103.8125 221.8125 221.5C215.75 238.5625 224.6875 257.3125 241.75 263.375C258.9375 269.4375 277.625 260.4375 283.625 243.375C316.0625 151.8125 403 90.25 499.9375 90.25C596.875 90.25 683.75 151.75 716.25 243.375C722.3125 260.4375 741.25 269.4375 758.125 263.375C775.25 257.375 784.25 238.625 778.1875 221.5625z" /> @@ -525,7 +534,7 @@ horiz-adv-x="1000" d=" M998.75 309.375L938.125 -62.5000000000001C927.5 -148.75 820.625 -180 750 -180H355.625C343.125 -180 331.8750000000001 -176.875 322.5 -171.25L232.5 -117.5H125C58.75 -117.5 0 -58.75 0 7.5V257.5C0 323.7500000000001 58.75 383.75 125 382.5H250C306.875 382.5 336.875 410.625 399.3750000000001 479.375C456.2500000000001 541.875 454.3750000000001 591.875 438.7500000000001 683.75C433.75 715 442.5 746.25 465 772.5C489.375 801.875 526.25 820 562.5 820C676.875 820 750 588.125 750 506.875L748.75 445.625H876.25C948.75 445.625 998.1249999999998 395.625 1000 322.5C1000 315.625 998.75 309.375 998.75 309.375zM875.625 383.75H751.25C707.5 383.75 686.875 401.25 686.875 444.375L688.75 508.75C688.75 588.125 615.625 758.75 563.75 758.75C532.5 758.75 496.2499999999999 727.5 501.25 696.25C516.875 597.5 522.5 522.5 445.625 437.5C381.875 366.875 335 320 250 320V-55L354.375 -117.5H750C795.625 -117.5 871.875 -98.125 875 -55L876.25 -53.75L938.75 321.25C936.875 361.25 914.9999999999998 383.75 876.25 383.75H875.625z" /> + horiz-adv-x="1000" d=" M307.5 359.920625C321.71875 345.701875 377.5 287.18625 377.5 287.18625L408.125 318.9050000000001L360 368.6706250000001L452.421875 467.108125C452.421875 467.108125 410.859375 507.576875 428.90625 491.7175C446.40625 556.795625 430.546875 628.983125 381.328125 679.8425C332.109375 730.155 262.65625 746.56125 200.3125 729.608125L305.859375 620.233125L277.96875 513.045625L174.609375 484.608125L69.0625 593.983125C52.1095625 529.451875 67.96875 457.81125 117.1875 407.4987500000001C168.59375 353.9050000000001 242.421875 338.5925000000001 307.5 359.920625V359.920625zM659.6875 253.826875L532.265625 128.0437499999999L742.2687500000001 -89.6125000000001C759.21875 -107.65625 782.1875 -116.40625 804.6125 -116.40625C827.03125 -116.40625 849.4562500000001 -107.65625 866.95625 -89.6125000000001C901.40625 -54.0625 901.40625 3.35625 866.95625 38.90625L659.6875 253.826875V253.826875zM937.5 619.140625L803.51875 757.5L408.671875 349.53125L456.796875 299.765625L221.09375 55.8625L166.953125 26.875L90.9375 -97.2687499999999L110.078125 -117.5L230.390625 -38.75L258.28125 17.03125L494.53125 260.9375L542.65625 211.171875L937.5 619.140625V619.140625z" /> @@ -556,6 +565,9 @@ + diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.ttf b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.ttf index 8c29d3383086aa5e948dae2b66b2fe4c8e04c5e8..4eb17f9045c32dfdff2f5489fe76b3ed7323c733 100644 GIT binary patch delta 4137 zcmZvfdvp}#8OEQP%{|#oHrecMZp$W{?1n%>_MVU!5EBKtXcb6M1R}X{Nr)kkfas={ zA|NUl6RH}$zdwR6xc$8X?t+BPp@elM+{gNW5wG#S#`G8~3=_bFK`M#N* zeDl7~`@EBb&*%gD_3fTmAE0dmFn?)hb9b;kV<`}K8Zf)sSFUSYJ@b4kkTn3TKHt&W z+>&1!E#kF2k4Oh6ESaeqkB^b!j?UixzHcvm$oqW>=s&DnwX}JTWknf~$bumKe~0y|cr3#mxp`r>gPrsGx& z#^A*`JkFRe#dbW55UNm&O}L*6o`4u`$FFc09r!t3MiEwEIhNr89EAtF@dUE55|a>s zA3-!C8NG0z2f4_=Qz*qew4)DiBOhDvOB}!q%*XSvp%c{@k0()yFk<~^Vq6lLi99%w zgV!+|Q!xem@DuzRt8ovq@DlEV57%P>Ucx+3L0X9g3^XyX-Oz@h;jwxZoHL}GGh;ilN3}xM5Tgt0O41#A|L_^)&@jS z!770WDX`11q&j9i!%)e6=Vgp4p@WkJY@5!M)lj5a}+(Un?Lavw+2 z6s$Rj>lCa$hw?k3luDVh}#sj0SGxJGz5r+3R(k%OeUd8 zK*(eg+64r?TSDW2SfZeXKr}08CJ=WjXe$tW&N*^Jhk=l?CiEJJRs~%LLRJ-_|3I`W z=tK~5A%va;A#Y5u(wJke@$=CHOaf{;2SbSsFp3i=jAp8`F`QL{&T%J|&uh|S}i z3<;r|K@2G9YY?&+2ptY$qk>)sA;UoEdJrH1d(Ln``6+#vP!Bp;F zQ_yT7WJ(Eb7vhM51`HviPiVyu#}zbXh~Fu2kMU5tBlZ$Ue^Ah>Axorxdhwh(9W5?hqd-X!8(%Qqb@rK335BAwE&C2|)ZAD3crZ1BkyU*cl*(6zmZY zpDFN)F(+e^`4~rEDDbqgHDgjN3Jk|9*gY_upkN=taH4`81;a@S_7)5$E7)Z)oT6aA z!EmaAod?5d3icojn-%Ov7}=m;U&6>ed^wUEwkV7+0unYWj8JnDwk?bdDA>R-Ld{9= zrcq~g#HMk?m`K>xFhb=?*x)d-Nx@c!5vowariYO&3asOvwI*R7#K<-UJ0eE-C=%Hl z$37J-*ZcTTFR&s{VhCU3do4|yWqfSCHP&yAGjBEj)nc>MT9#Wz)2F7dPk$vNA!Ay` z(Twk{wbqT+Q<+_vuV#Lo)snS4Yt+_n`#QVK;c`?t7CN>$PCGN4_0EINb2*E0cI2GP ztbJu}!OUAwHwij+LJYD20+FLZ_iFn@doGEt2 ziZ>P?EkQ}77LKQnI-mego0+R+NovK_^dDIVoz~AOS8<-x93%Y}Ig1dr4 zq3c6?LZ`wAFA0Ac>4+SO>d}m-D>^ZnH90yEJyUg{I-`1ib#L_t)n}`}AFRwO&|_Oo zck37Ex8IW@^*_l~x^Bjdgr7k2kfo?K+FbWN`+s0){ST{&*6 zIoVmHd7Q~+YmUoX9=^s^HKV|vT_i7dhus)lVV{Q z3X2l5tu}kGFc1k_Ltc+xzhnRYp?&*4+qW;Ozjv`(Vt2dk`g;zy+tI3fMrUfTU+mVm z^VDdx(0)-LJ3DIU&H03URqZ(nZCDfYGp zv?T){ncbPiq1+objjpLY7nzmSpZ||at)sZa9k?^on&NMrnQQJ^k>BahYw`M=8O!e- zGMmFQ^Yr=$MmqxM3pUv1RGkkDCdC@!BdS*7`7$GOJX5ioee#_LVmiNS~NU#lf)Z)?Z36WTk2aa-#3g!tB;o>e`A(;t|t znbVeawXG^&wWfFFvaZ&_J=GFl(JSSq#cH_)>_6=@6~^7$6@T0ab8y1u^CAE98Npa-Ti&%J7>S^ zd7t-r&)_TOsTa&Wfmk14>;%xUbZvLI8g5LF;`i`92$(udd1EgNuI}~WU0E5SpyC;0S zesR{MeQ+#1u@zE0}=E zh~Yupg;|(`O}G#DqZ7wbj;DCD!O@dK{s~cSwzr`GW-}%@QKG^8y4Z0_ytzral8gUeu!0Ai5++g0sI7qkdM_E zk1*=dfDWW#BV6c3A+m84AuPahY{swQ!IO9yKf_G?052gAYtVv996=o#5$nf7PRtm_ zpa^b^#aYZo8=`m-cjFCgz>koNSFs*JOvk-AjfXJ}PCSNU^kFGZ;dN9Y6Ly@#F-Fmb zCftELF&B^G8SF<5Ucd>Q#INus9GHrG@BkL03tO-qJ7EEFGd07Ay~_Zo&v~F?xrPBj z#fzHTug#ewuC*0hMIt6Yp4zglR`nAKyo#h!ZuGs?LhK1R1u^=Lp?!U8Y&CI zOjA%}kg*!74N?kpsttt)@oFeRNI*j|LdrChC8R<_fkJ{BN)-~)P_&RL4USu<(_OKr z*sIZ~ypdWBMGYCRp}Zk=8VVg!uc722VGYF(Y0xkYAQ24{0zz|AFf|}e8YT&(8JMOv z%ohlKLBY&{sJSJ~A&8nQ!fb-5c_z#&h?-l19&3BX#PUJ*rfHaGkUKQYILMtEoUyKE z1Y!r-o2g+ELR3r%(-ESkmM}3Pvo%an$Q%um71FL@+Ct`Pn7|NqC&E;Q%-1lfAsrfJ zRI(7Tp2~E_wsC)vh6xYp)G+lSDs+S<08ybMv;)Wk8X5ycX@k%rAd59L3rLrSwgFM^ zLueonrC~xVfh=WFP#d}mWSNHk0$Hx1(?HY>2|Wj*P9ivDwcB0BYHNjkV!e8)4H~)- zWV42T1nJk%nIKy<=(Rqu2VzIrQ?E|wR**+D^esqCLkEKlYUpKTXch?-hLe}ky0 zC3HH-f1&3=l%5IQ53*B3AA~TB?P^0ygzVPP93gu(v`NT54Gj~rUqkDJJg1?FLJn$h z#Jc1N*wxD((_o>MaLkXLW$%=R&I&oBp~pg0dKnqbD+Bf7q4UHUfSwl;Q{7FM|hg{Ln<{^L9(C{H2XlVVA z4>c?TkdHJh2ar!REDVrOH7pSU$Y<;jRtw1I8rBWS7aF{7t;im4KgZr*H8^G+&mKSd z4&ZvSh7|?ZQ#7nCxIRY1Due5(8rB-j>#gmqeWdo{c*pn$$6u?PS+}e1V%?4Uhr=!51>wiSmm3@nTN|!Kk|L4F{ziA> z(Z*{{Elo3;7Bnqs>TP!#oQ~dQ>oNaDm#PQK z^L>rYP5=LGGa^l4tF55u0o$6J2Wnl8w9NDrySFGR*pO>@w8fVkS6<-Ede5JfXgEL?ws?GKJQ}Ug81)S2RMrWkL=5zXt zEki@YLqo<5Ha9;i_WO%%)t*4WbMqrxwfWXarg7s|j`_{4-R3ub&&>n3|6k+r-&|;0 zqZSaOf3vMIuW)?`BRD3*2*i)B{EcHwh&DPComN-BC)Eg6G)JPQ%Np#TW1o2F?BPDI z-S_?*4aH_T-LI^WgI%wXGw6 z_w90ZPBC0umDu=_Pn+>?2cyaHwcDrIj0^G8JNk`y^)q*x@&0EQ81YYcoiwf8d;j7{ R-qPK-bd@n++uGd={0DE3c%1+M diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index 37a058e7096e..44f97573d434 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./sash'; -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isIPad } from 'vs/base/browser/browser'; import { isMacintosh } from 'vs/base/common/platform'; import * as types from 'vs/base/common/types'; @@ -95,14 +95,14 @@ export class Sash extends Disposable { linkedSash: Sash | undefined = undefined; - private orthogonalStartSashDisposables: IDisposable[] = []; + private readonly orthogonalStartSashDisposables = this._register(new DisposableStore()); private _orthogonalStartSash: Sash | undefined; get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; } set orthogonalStartSash(sash: Sash | undefined) { - this.orthogonalStartSashDisposables = dispose(this.orthogonalStartSashDisposables); + this.orthogonalStartSashDisposables.clear(); if (sash) { - sash.onDidEnablementChange(this.onOrthogonalStartSashEnablementChange, this, this.orthogonalStartSashDisposables); + this.orthogonalStartSashDisposables.add(sash.onDidEnablementChange(this.onOrthogonalStartSashEnablementChange, this)); this.onOrthogonalStartSashEnablementChange(sash.state); } else { this.onOrthogonalStartSashEnablementChange(SashState.Disabled); @@ -111,14 +111,14 @@ export class Sash extends Disposable { this._orthogonalStartSash = sash; } - private orthogonalEndSashDisposables: IDisposable[] = []; + private readonly orthogonalEndSashDisposables = this._register(new DisposableStore()); private _orthogonalEndSash: Sash | undefined; get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; } set orthogonalEndSash(sash: Sash | undefined) { - this.orthogonalEndSashDisposables = dispose(this.orthogonalEndSashDisposables); + this.orthogonalEndSashDisposables.clear(); if (sash) { - sash.onDidEnablementChange(this.onOrthogonalEndSashEnablementChange, this, this.orthogonalEndSashDisposables); + this.orthogonalEndSashDisposables.add(sash.onDidEnablementChange(this.onOrthogonalEndSashEnablementChange, this)); this.onOrthogonalEndSashEnablementChange(sash.state); } else { this.onOrthogonalEndSashEnablementChange(SashState.Disabled); @@ -384,9 +384,6 @@ export class Sash extends Disposable { dispose(): void { super.dispose(); - this.orthogonalStartSashDisposables = dispose(this.orthogonalStartSashDisposables); - this.orthogonalEndSashDisposables = dispose(this.orthogonalEndSashDisposables); - if (this.el && this.el.parentElement) { this.el.parentElement.removeChild(this.el); } diff --git a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts index eda689c79a16..10c3d6dc1a20 100644 --- a/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +++ b/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts @@ -108,6 +108,12 @@ export abstract class AbstractScrollbar extends Widget { this._sliderMouseDown(e, () => { /*nothing to do*/ }); } }); + + this.onclick(this.slider.domNode, e => { + if (e.leftButton) { + e.stopPropagation(); + } + }); } // ----------------- Update state diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index 21c83e4cb170..2ce97a42ce55 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -14,11 +14,12 @@ import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { SelectBoxNative } from 'vs/base/browser/ui/selectBox/selectBoxNative'; import { SelectBoxList } from 'vs/base/browser/ui/selectBox/selectBoxCustom'; import { isMacintosh } from 'vs/base/common/platform'; +import { IDisposable } from 'vs/base/common/lifecycle'; // Public SelectBox interface - Calls routed to appropriate select implementation class -export interface ISelectBoxDelegate { +export interface ISelectBoxDelegate extends IDisposable { // Public SelectBox Interface readonly onDidSelect: Event; @@ -27,7 +28,6 @@ export interface ISelectBoxDelegate { setAriaLabel(label: string): void; focus(): void; blur(): void; - dispose(): void; // Delegated Widget interface render(container: HTMLElement): void; @@ -147,5 +147,4 @@ export class SelectBox extends Widget implements ISelectBoxDelegate { return option; } - } diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index ed5149a0cd5f..0c80f04675ed 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -5,7 +5,7 @@ import 'vs/css!./selectBoxCustom'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { KeyCode, KeyCodeUtils } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -85,7 +85,7 @@ class SelectListRenderer implements IListRenderer { +export class SelectBoxList extends Disposable implements ISelectBoxDelegate, IListVirtualDelegate { private static readonly DEFAULT_DROPDOWN_MINIMUM_BOTTOM_MARGIN = 32; private static readonly DEFAULT_DROPDOWN_MINIMUM_TOP_MARGIN = 2; @@ -98,7 +98,6 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate; - private toDispose: IDisposable[]; private styles: ISelectBoxStyles; private listRenderer: SelectListRenderer; private contextViewProvider: IContextViewProvider; @@ -117,7 +116,7 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate(); - this.toDispose.push(this._onDidSelect); + this._register(this._onDidSelect); this.styles = styles; @@ -191,18 +190,21 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate { + this._register(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => { this.selected = e.target.selectedIndex; this._onDidSelect.fire({ index: e.target.selectedIndex, selected: e.target.value }); + if (!!this.options[this.selected] && !!this.options[this.selected].text) { + this.selectElement.title = this.options[this.selected].text; + } })); // Have to implement both keyboard and mouse controllers to handle disabled options // Intercept mouse events to override normal select actions on parents - this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.CLICK, (e) => { + this._register(dom.addDisposableListener(this.selectElement, dom.EventType.CLICK, (e) => { dom.EventHelper.stop(e); if (this._isVisible) { @@ -212,13 +214,13 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate { + this._register(dom.addDisposableListener(this.selectElement, dom.EventType.MOUSE_DOWN, (e) => { dom.EventHelper.stop(e); })); // Intercept keyboard handling - this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + this._register(dom.addDisposableListener(this.selectElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); let showDropDown = false; @@ -288,6 +290,9 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate this.selectList.length > 0) .map(e => new StandardKeyboardEvent(e)); - onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Enter).on(e => this.onEnter(e), this, this.toDispose); - onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(e => this.onEscape(e), this, this.toDispose); - onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.UpArrow).on(this.onUpArrow, this, this.toDispose); - onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.DownArrow).on(this.onDownArrow, this, this.toDispose); - onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageDown).on(this.onPageDown, this, this.toDispose); - onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageUp).on(this.onPageUp, this, this.toDispose); - onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Home).on(this.onHome, this, this.toDispose); - onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.End).on(this.onEnd, this, this.toDispose); - onSelectDropDownKeyDown.filter(e => (e.keyCode >= KeyCode.KEY_0 && e.keyCode <= KeyCode.KEY_Z) || (e.keyCode >= KeyCode.US_SEMICOLON && e.keyCode <= KeyCode.NUMPAD_DIVIDE)).on(this.onCharacter, this, this.toDispose); + this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Enter).on(e => this.onEnter(e), this)); + this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(e => this.onEscape(e), this)); + this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.UpArrow).on(this.onUpArrow, this)); + this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.DownArrow).on(this.onDownArrow, this)); + this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageDown).on(this.onPageDown, this)); + this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageUp).on(this.onPageUp, this)); + this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Home).on(this.onHome, this)); + this._register(onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.End).on(this.onEnd, this)); + this._register(onSelectDropDownKeyDown.filter(e => (e.keyCode >= KeyCode.KEY_0 && e.keyCode <= KeyCode.KEY_Z) || (e.keyCode >= KeyCode.US_SEMICOLON && e.keyCode <= KeyCode.NUMPAD_DIVIDE)).on(this.onCharacter, this)); // SetUp list mouse controller - control navigation, disabled items, focus - Event.chain(domEvent(this.selectList.getHTMLElement(), 'mouseup')) + this._register(Event.chain(domEvent(this.selectList.getHTMLElement(), 'mouseup')) .filter(() => this.selectList.length > 0) - .on(e => this.onMouseUp(e), this, this.toDispose); + .on(e => this.onMouseUp(e), this)); + - this.toDispose.push( - this.selectList.onDidBlur(_ => this.onListBlur()), - this.selectList.onMouseOver(e => typeof e.index !== 'undefined' && this.selectList.setFocus([e.index])), - this.selectList.onFocusChange(e => this.onListFocus(e)) - ); + this._register(this.selectList.onDidBlur(_ => this.onListBlur())); + this._register(this.selectList.onMouseOver(e => typeof e.index !== 'undefined' && this.selectList.setFocus([e.index]))); + this._register(this.selectList.onFocusChange(e => this.onListFocus(e))); this.selectList.getHTMLElement().setAttribute('aria-label', this.selectBoxOptions.ariaLabel || ''); this.selectList.getHTMLElement().setAttribute('aria-expanded', 'true'); @@ -816,7 +820,11 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate; - private toDispose: IDisposable[]; private styles: ISelectBoxStyles; constructor(options: ISelectOptionItem[], selected: number, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) { - this.toDispose = []; + super(); this.selectBoxOptions = selectBoxOptions || Object.create(null); this.options = []; @@ -36,8 +35,7 @@ export class SelectBoxNative implements ISelectBoxDelegate { this.selectElement.setAttribute('aria-label', this.selectBoxOptions.ariaLabel); } - this._onDidSelect = new Emitter(); - this.toDispose.push(this._onDidSelect); + this._onDidSelect = this._register(new Emitter()); this.styles = styles; @@ -47,7 +45,7 @@ export class SelectBoxNative implements ISelectBoxDelegate { private registerListeners() { - this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => { + this._register(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => { this.selectElement.title = e.target.value; this._onDidSelect.fire({ index: e.target.selectedIndex, @@ -55,7 +53,7 @@ export class SelectBoxNative implements ISelectBoxDelegate { }); })); - this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => { + this._register(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => { let showSelect = false; if (isMacintosh) { @@ -169,8 +167,4 @@ export class SelectBoxNative implements ISelectBoxDelegate { return option; } - - public dispose(): void { - this.toDispose = dispose(this.toDispose); - } } diff --git a/src/vs/base/browser/ui/splitview/panelview.ts b/src/vs/base/browser/ui/splitview/panelview.ts index ba6bec16d7c9..621eaff0f067 100644 --- a/src/vs/base/browser/ui/splitview/panelview.ts +++ b/src/vs/base/browser/ui/splitview/panelview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./panelview'; -import { IDisposable, dispose, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -37,7 +37,7 @@ export interface IPanelStyles { * Subclasses wouldn't be able to set own properties * before the `render()` call, thus forbiding their use. */ -export abstract class Panel implements IView { +export abstract class Panel extends Disposable implements IView { private static readonly HEADER_SIZE = 22; @@ -55,11 +55,9 @@ export abstract class Panel implements IView { private styles: IPanelStyles = {}; private animationTimer: number | undefined = undefined; - private _onDidChange = new Emitter(); + private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; - protected disposables: IDisposable[] = []; - get draggableElement(): HTMLElement { return this.header; } @@ -114,6 +112,7 @@ export abstract class Panel implements IView { width: number; constructor(options: IPanelOptions = {}) { + super(); this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded; this.ariaHeaderLabel = options.ariaHeaderLabel || ''; this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120; @@ -172,26 +171,26 @@ export abstract class Panel implements IView { this.renderHeader(this.header); const focusTracker = trackFocus(this.header); - this.disposables.push(focusTracker); - focusTracker.onDidFocus(() => addClass(this.header, 'focused'), null, this.disposables); - focusTracker.onDidBlur(() => removeClass(this.header, 'focused'), null, this.disposables); + this._register(focusTracker); + this._register(focusTracker.onDidFocus(() => addClass(this.header, 'focused'), null)); + this._register(focusTracker.onDidBlur(() => removeClass(this.header, 'focused'), null)); this.updateHeader(); const onHeaderKeyDown = Event.chain(domEvent(this.header, 'keydown')) .map(e => new StandardKeyboardEvent(e)); - onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space) - .event(() => this.setExpanded(!this.isExpanded()), null, this.disposables); + this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space) + .event(() => this.setExpanded(!this.isExpanded()), null)); - onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow) - .event(() => this.setExpanded(false), null, this.disposables); + this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow) + .event(() => this.setExpanded(false), null)); - onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow) - .event(() => this.setExpanded(true), null, this.disposables); + this._register(onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow) + .event(() => this.setExpanded(true), null)); - domEvent(this.header, 'click') - (() => this.setExpanded(!this.isExpanded()), null, this.disposables); + this._register(domEvent(this.header, 'click') + (() => this.setExpanded(!this.isExpanded()), null)); this.body = append(this.element, $('.panel-body')); this.renderBody(this.body); @@ -234,12 +233,6 @@ export abstract class Panel implements IView { protected abstract renderHeader(container: HTMLElement): void; protected abstract renderBody(container: HTMLElement): void; protected abstract layoutBody(height: number, width: number): void; - - dispose(): void { - this.disposables = dispose(this.disposables); - - this._onDidChange.dispose(); - } } interface IDndContext { @@ -397,24 +390,24 @@ export class PanelView extends Disposable { } addPanel(panel: Panel, size: number, index = this.splitview.length): void { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); // https://github.com/Microsoft/vscode/issues/59950 let shouldAnimate = false; - disposables.push(scheduleAtNextAnimationFrame(() => shouldAnimate = true)); + disposables.add(scheduleAtNextAnimationFrame(() => shouldAnimate = true)); - Event.filter(panel.onDidChange, () => shouldAnimate) - (this.setupAnimation, this, disposables); + disposables.add(Event.filter(panel.onDidChange, () => shouldAnimate) + (this.setupAnimation, this)); - const panelItem = { panel, disposable: combinedDisposable(disposables) }; + const panelItem = { panel, disposable: disposables }; this.panelItems.splice(index, 0, panelItem); panel.width = this.width; this.splitview.addView(panel, size, index); if (this.dnd) { const draggable = new PanelDraggable(panel, this.dnd, this.dndContext); - disposables.push(draggable); - draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop, disposables); + disposables.add(draggable); + disposables.add(draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop)); } } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 2f33b06bdd52..2e0aaffd83b6 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./splitview'; -import { IDisposable, combinedDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as types from 'vs/base/common/types'; import * as dom from 'vs/base/browser/dom'; @@ -199,7 +199,7 @@ export class SplitView extends Disposable { const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size)); const containerDisposable = toDisposable(() => this.viewContainer.removeChild(container)); - const disposable = combinedDisposable([onChangeDisposable, containerDisposable]); + const disposable = combinedDisposable(onChangeDisposable, containerDisposable); const layoutContainer = this.orientation === Orientation.VERTICAL ? () => item.container.style.height = `${item.size}px` @@ -245,7 +245,7 @@ export class SplitView extends Disposable { const onEndDisposable = onEnd(this.onSashEnd, this); const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash))); - const disposable = combinedDisposable([onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash]); + const disposable = combinedDisposable(onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash); const sashItem: ISashItem = { sash, disposable }; this.sashItems.splice(index - 1, 0, sashItem); @@ -369,10 +369,10 @@ export class SplitView extends Disposable { const index = firstIndex(this.sashItems, item => item.sash === sash); // This way, we can press Alt while we resize a sash, macOS style! - const disposable = combinedDisposable([ + const disposable = combinedDisposable( domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState.current, e.altKey)), domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState.current, false)) - ]); + ); const resetSashDragState = (start: number, alt: boolean) => { const sizes = this.viewItems.map(i => i.size); diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 7d08f03117bb..1c5d0b888fbc 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -114,7 +114,7 @@ export class ToolBar extends Disposable { this.actionBar.setAriaLabel(label); } - setActions(primaryActions: IAction[], secondaryActions?: IAction[]): () => void { + setActions(primaryActions: ReadonlyArray, secondaryActions?: ReadonlyArray): () => void { return () => { let primaryActionsToSet = primaryActions ? primaryActions.slice(0) : []; @@ -169,7 +169,7 @@ class ToggleMenuAction extends Action { static readonly ID = 'toolbar.toggle.more'; - private _menuActions: IAction[]; + private _menuActions: ReadonlyArray; private toggleDropdownMenu: () => void; constructor(toggleDropdownMenu: () => void, title?: string) { @@ -185,11 +185,11 @@ class ToggleMenuAction extends Action { return Promise.resolve(true); } - get menuActions() { + get menuActions(): ReadonlyArray { return this._menuActions; } - set menuActions(actions: IAction[]) { + set menuActions(actions: ReadonlyArray) { this._menuActions = actions; } } \ No newline at end of file diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 8a75a27d5c52..c4bd897e36b2 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; export interface ITelemetryData { @@ -29,14 +29,13 @@ export interface IActionRunner extends IDisposable { onDidBeforeRun: Event; } -export interface IActionViewItem { +export interface IActionViewItem extends IDisposable { actionRunner: IActionRunner; setActionContext(context: any): void; render(element: any /* HTMLElement */): void; isEnabled(): boolean; focus(): void; blur(): void; - dispose(): void; } export interface IActionChangeEvent { @@ -48,9 +47,9 @@ export interface IActionChangeEvent { radio?: boolean; } -export class Action implements IAction { +export class Action extends Disposable implements IAction { - protected _onDidChange = new Emitter(); + protected _onDidChange = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; protected _id: string; @@ -63,6 +62,7 @@ export class Action implements IAction { protected _actionCallback?: (event?: any) => Promise; constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Promise) { + super(); this._id = id; this._label = label; this._cssClass = cssClass; @@ -171,10 +171,6 @@ export class Action implements IAction { return Promise.resolve(true); } - - dispose() { - this._onDidChange.dispose(); - } } export interface IRunEvent { @@ -217,8 +213,8 @@ export class RadioGroup extends Disposable { constructor(readonly actions: Action[]) { super(); - this._register(combinedDisposable(actions.map(action => { - return action.onDidChange(e => { + for (const action of actions) { + this._register(action.onDidChange(e => { if (e.checked && action.checked) { for (const candidate of actions) { if (candidate !== action) { @@ -226,7 +222,7 @@ export class RadioGroup extends Disposable { } } } - }); - }))); + })); + } } } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 27dde86362c5..131df405be4b 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -6,7 +6,7 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; export function isThenable(obj: any): obj is Promise { @@ -415,11 +415,11 @@ export class Limiter { this._onFinished = new Emitter(); } - public get onFinished(): Event { + get onFinished(): Event { return this._onFinished.event; } - public get size(): number { + get size(): number { return this._size; // return this.runningPromises + this.outstandingPromises.length; } @@ -455,7 +455,7 @@ export class Limiter { } } - public dispose(): void { + dispose(): void { this._onFinished.dispose(); } } @@ -475,35 +475,30 @@ export class Queue extends Limiter { * by disposing them once the queue is empty. */ export class ResourceQueue { - private queues: { [path: string]: Queue }; - - constructor() { - this.queues = Object.create(null); - } + private queues: Map> = new Map(); - public queueFor(resource: URI): Queue { + queueFor(resource: URI): Queue { const key = resource.toString(); - if (!this.queues[key]) { + if (!this.queues.has(key)) { const queue = new Queue(); queue.onFinished(() => { queue.dispose(); - delete this.queues[key]; + this.queues.delete(key); }); - this.queues[key] = queue; + this.queues.set(key, queue); } - return this.queues[key]; + return this.queues.get(key)!; } } -export class TimeoutTimer extends Disposable { +export class TimeoutTimer implements IDisposable { private _token: any; constructor(); constructor(runner: () => void, timeout: number); constructor(runner?: () => void, timeout?: number) { - super(); this._token = -1; if (typeof runner === 'function' && typeof timeout === 'number') { @@ -513,7 +508,6 @@ export class TimeoutTimer extends Disposable { dispose(): void { this.cancel(); - super.dispose(); } cancel(): void { @@ -543,18 +537,16 @@ export class TimeoutTimer extends Disposable { } } -export class IntervalTimer extends Disposable { +export class IntervalTimer implements IDisposable { private _token: any; constructor() { - super(); this._token = -1; } dispose(): void { this.cancel(); - super.dispose(); } cancel(): void { diff --git a/src/vs/base/common/cache.ts b/src/vs/base/common/cache.ts index 74ef0199ffd5..bb43d5473075 100644 --- a/src/vs/base/common/cache.ts +++ b/src/vs/base/common/cache.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IDisposable } from 'vs/base/common/lifecycle'; -export interface CacheResult { +export interface CacheResult extends IDisposable { promise: Promise; - dispose(): void; } export class Cache { diff --git a/src/vs/base/common/errorsWithActions.ts b/src/vs/base/common/errorsWithActions.ts index e69f58757d1c..86a4d5c6ac55 100644 --- a/src/vs/base/common/errorsWithActions.ts +++ b/src/vs/base/common/errorsWithActions.ts @@ -6,11 +6,11 @@ import { IAction } from 'vs/base/common/actions'; export interface IErrorOptions { - actions?: IAction[]; + actions?: ReadonlyArray; } export interface IErrorWithActions { - actions?: IAction[]; + actions?: ReadonlyArray; } export function isErrorWithActions(obj: any): obj is IErrorWithActions { diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index c34b499e89fd..2e3b1f545984 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -5,7 +5,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { once as onceFn } from 'vs/base/common/functional'; -import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; /** @@ -13,12 +13,11 @@ import { LinkedList } from 'vs/base/common/linkedList'; * can be subscribed. The event is the subscriber function itself. */ export interface Event { - (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable; + (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable; } export namespace Event { - const _disposable = { dispose() { } }; - export const None: Event = function () { return _disposable; }; + export const None: Event = () => Disposable.None; /** * Given an event, returns another event which only fires once. @@ -86,7 +85,7 @@ export namespace Event { * whenever any of the provided events emit. */ export function any(...events: Event[]): Event { - return (listener, thisArgs = null, disposables?) => combinedDisposable(events.map(event => event(e => listener.call(thisArgs, e), null, disposables))); + return (listener, thisArgs = null, disposables?) => combinedDisposable(...events.map(event => event(e => listener.call(thisArgs, e), null, disposables))); } /** @@ -477,7 +476,7 @@ export class Emitter { */ get event(): Event { if (!this._event) { - this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]) => { + this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => { if (!this._listeners) { this._listeners = new LinkedList(); } @@ -522,7 +521,9 @@ export class Emitter { } } }; - if (Array.isArray(disposables)) { + if (disposables instanceof DisposableStore) { + disposables.add(result); + } else if (Array.isArray(disposables)) { disposables.push(result); } diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 6b2326412704..1ee7e3331cfb 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -458,14 +458,14 @@ export function parse(arg1: string | IExpression | IRelativePattern, options: IG if (parsedPattern === NULL) { return FALSE; } - const resultPattern = function (path: string, basename: string) { + const resultPattern: ParsedPattern & { allBasenames?: string[]; allPaths?: string[]; } = function (path: string, basename: string) { return !!parsedPattern(path, basename); }; if (parsedPattern.allBasenames) { - (resultPattern).allBasenames = parsedPattern.allBasenames; + resultPattern.allBasenames = parsedPattern.allBasenames; } if (parsedPattern.allPaths) { - (resultPattern).allPaths = parsedPattern.allPaths; + resultPattern.allPaths = parsedPattern.allPaths; } return resultPattern; } diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 178e9c0ee34b..984c36aaaa62 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -5,6 +5,44 @@ import { once } from 'vs/base/common/functional'; +/** + * Enables logging of potentially leaked disposables. + * + * A disposable is considered leaked if it is not disposed or not registered as the child of + * another disposable. This tracking is very simple an only works for classes that either + * extend Disposable or use a DisposableStore. This means there are a lot of false positives. + */ +const TRACK_DISPOSABLES = false; + +const __is_disposable_tracked__ = '__is_disposable_tracked__'; + +function markTracked(x: T): void { + if (!TRACK_DISPOSABLES) { + return; + } + + if (x && x !== Disposable.None) { + try { + x[__is_disposable_tracked__] = true; + } catch { + // noop + } + } +} + +function trackDisposable(x: T): void { + if (!TRACK_DISPOSABLES) { + return; + } + + const stack = new Error().stack!; + setTimeout(() => { + if (!x[__is_disposable_tracked__]) { + console.log(stack); + } + }, 3000); +} + export interface IDisposable { dispose(): void; } @@ -15,56 +53,94 @@ export function isDisposable(thing: E): thing is E & IDisposab } export function dispose(disposable: T): T; -export function dispose(...disposables: Array): T[]; -export function dispose(disposables: T[]): T[]; -export function dispose(first: T | T[], ...rest: T[]): T | T[] | undefined { - if (Array.isArray(first)) { - first.forEach(d => d && d.dispose()); +export function dispose(disposable: T | undefined): T | undefined; +export function dispose(disposables: Array): Array; +export function dispose(disposables: ReadonlyArray): ReadonlyArray; +export function dispose(disposables: T | T[] | undefined): T | T[] | undefined { + if (Array.isArray(disposables)) { + disposables.forEach(d => { + if (d) { + markTracked(d); + d.dispose(); + } + }); return []; - } else if (rest.length === 0) { - if (first) { - first.dispose(); - return first; - } - return undefined; + } else if (disposables) { + markTracked(disposables); + disposables.dispose(); + return disposables; } else { - dispose(first); - dispose(rest); - return []; + return undefined; } } -export function combinedDisposable(disposables: IDisposable[]): IDisposable { +export function combinedDisposable(...disposables: IDisposable[]): IDisposable { + disposables.forEach(markTracked); return { dispose: () => dispose(disposables) }; } export function toDisposable(fn: () => void): IDisposable { - return { dispose() { fn(); } }; + return { dispose: fn }; +} + +export class DisposableStore implements IDisposable { + private _toDispose = new Set(); + private _isDisposed = false; + + /** + * Dispose of all registered disposables and mark this object as disposed. + * + * Any future disposables added to this object will be disposed of on `add`. + */ + public dispose(): void { + markTracked(this); + this._isDisposed = true; + this.clear(); + } + + /** + * Dispose of all registered disposables but do not mark this object as disposed. + */ + public clear(): void { + this._toDispose.forEach(item => item.dispose()); + this._toDispose.clear(); + } + + public add(t: T): T { + if (!t) { + return t; + } + + markTracked(t); + if (this._isDisposed) { + console.warn(new Error('Registering disposable on object that has already been disposed of').stack); + t.dispose(); + } else { + this._toDispose.add(t); + } + + return t; + } } export abstract class Disposable implements IDisposable { static None = Object.freeze({ dispose() { } }); - protected _toDispose: IDisposable[] = []; - protected get toDispose(): IDisposable[] { return this._toDispose; } + private readonly _store = new DisposableStore(); - private _lifecycle_disposable_isDisposed = false; + constructor() { + trackDisposable(this); + } public dispose(): void { - this._lifecycle_disposable_isDisposed = true; - this._toDispose = dispose(this._toDispose); + markTracked(this); + + this._store.dispose(); } protected _register(t: T): T { - if (this._lifecycle_disposable_isDisposed) { - console.warn('Registering disposable on object that has already been disposed.'); - t.dispose(); - } else { - this._toDispose.push(t); - } - - return t; + return this._store.add(t); } } @@ -74,22 +150,23 @@ export interface IReference extends IDisposable { export abstract class ReferenceCollection { - private references: { [key: string]: { readonly object: T; counter: number; } } = Object.create(null); + private references: Map = new Map(); constructor() { } acquire(key: string): IReference { - let reference = this.references[key]; + let reference = this.references.get(key); if (!reference) { - reference = this.references[key] = { counter: 0, object: this.createReferencedObject(key) }; + reference = { counter: 0, object: this.createReferencedObject(key) }; + this.references.set(key, reference); } const { object } = reference; const dispose = once(() => { - if (--reference.counter === 0) { - this.destroyReferencedObject(key, reference.object); - delete this.references[key]; + if (--reference!.counter === 0) { + this.destroyReferencedObject(key, reference!.object); + this.references.delete(key); } }); diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index f2501bf249a5..84e014a4878e 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -96,6 +96,16 @@ const _empty = ''; const _slash = '/'; const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; +function _isQueryStringScheme(scheme: string) { + switch (scheme.toLowerCase()) { + case 'http': + case 'https': + case 'ftp': + return true; + } + return false; +} + /** * Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986. * This class is a simple parser which creates the basic component parts @@ -282,14 +292,14 @@ export class URI implements UriComponents { static parse(value: string, _strict: boolean = false): URI { const match = _regexp.exec(value); if (!match) { - return new _URI(_empty, _empty, _empty, _empty, _empty); + return new _URI(_empty, _empty, _empty, _empty, _empty, _strict); } return new _URI( match[2] || _empty, - decodeURIComponent(match[4] || _empty), - decodeURIComponent(match[5] || _empty), - decodeURIComponent(match[7] || _empty), - decodeURIComponent(match[9] || _empty), + decodeURIComponentFast(match[4] || _empty, false, false), + decodeURIComponentFast(match[5] || _empty, true, false), + decodeURIComponentFast(match[7] || _empty, false, _isQueryStringScheme(match[2])), + decodeURIComponentFast(match[9] || _empty, false, false), _strict ); } @@ -321,7 +331,7 @@ export class URI implements UriComponents { // normalize to fwd-slashes on windows, // on other systems bwd-slashes are valid - // filename character, eg /f\oo/ba\r.txt + // filename character, e.g. /f\oo/ba\r.txt if (isWindows) { path = path.replace(/\\/g, _slash); } @@ -385,8 +395,8 @@ export class URI implements UriComponents { return data; } else { const result = new _URI(data); - result._fsPath = (data).fsPath; result._formatted = (data).external; + result._fsPath = (data)._sep === _pathSepMarker ? (data).fsPath : null; return result; } } @@ -402,10 +412,12 @@ export interface UriComponents { interface UriState extends UriComponents { $mid: number; - fsPath: string; external: string; + fsPath: string; + _sep: 1 | undefined; } +const _pathSepMarker = isWindows ? 1 : undefined; // tslint:disable-next-line:class-name class _URI extends URI { @@ -439,6 +451,7 @@ class _URI extends URI { // cached state if (this._fsPath) { res.fsPath = this._fsPath; + res._sep = _pathSepMarker; } if (this._formatted) { res.external = this._formatted; @@ -463,6 +476,84 @@ class _URI extends URI { } } +function isHex(value: string, pos: number): boolean { + if (pos >= value.length) { + return false; + } + const code = value.charCodeAt(pos); + return (code >= CharCode.Digit0 && code <= CharCode.Digit9)// 0-9 + || (code >= CharCode.a && code <= CharCode.f) //a-f + || (code >= CharCode.A && code <= CharCode.F); //A-F +} + + +function decodeURIComponentFast(uriComponent: string, isPath: boolean, isQueryString: boolean): string { + + let res: string | undefined; + let nativeDecodePos = -1; + + for (let pos = 0; pos < uriComponent.length; pos++) { + const code = uriComponent.charCodeAt(pos); + + // decoding needed + if (code === CharCode.PercentSign && isHex(uriComponent, pos + 1) && isHex(uriComponent, pos + 2)) { + + const chA = uriComponent.charCodeAt(pos + 1); + const chB = uriComponent.charCodeAt(pos + 2); + + // when in a path -> check and accept %2f and %2F (fwd slash) + // when in a query string -> check and accept %3D, %26, and %3B (equals, ampersand, semi-colon) + if ( + (isPath && chA === CharCode.Digit2 && (chB === CharCode.F || chB === CharCode.f)) + || + (isQueryString && ( + (chA === CharCode.Digit2 && chB === CharCode.Digit6) // %26 + || + (chA === CharCode.Digit3 && (chB === CharCode.B || chB === CharCode.b || chB === CharCode.D || chB === CharCode.d)) // %3D, %3D + )) + ) { + if (nativeDecodePos !== -1) { + res += decodeURIComponent(uriComponent.substring(nativeDecodePos, pos)); + nativeDecodePos = -1; + } + + if (res !== undefined) { + res += uriComponent.substr(pos, 3); + } + + pos += 2; + continue; + } + + if (res === undefined) { + res = uriComponent.substring(0, pos); + } + if (nativeDecodePos === -1) { + nativeDecodePos = pos; + } + + pos += 2; + + } else { + + if (nativeDecodePos !== -1) { + res += decodeURIComponent(uriComponent.substring(nativeDecodePos, pos)); + nativeDecodePos = -1; + } + + if (res !== undefined) { + res += String.fromCharCode(code); + } + } + } + + if (nativeDecodePos !== -1) { + res += decodeURIComponent(uriComponent.substr(nativeDecodePos)); + } + + return res !== undefined ? res : uriComponent; +} + // reserved characters: https://tools.ietf.org/html/rfc3986#section-2.2 const encodeTable: { [ch: number]: string } = { [CharCode.Colon]: '%3A', // gen-delims @@ -488,7 +579,7 @@ const encodeTable: { [ch: number]: string } = { [CharCode.Space]: '%20', }; -function encodeURIComponentFast(uriComponent: string, allowSlash: boolean): string { +function encodeURIComponentFast(uriComponent: string, isPath: boolean, isQueryString: boolean): string { let res: string | undefined = undefined; let nativeEncodePos = -1; @@ -504,7 +595,8 @@ function encodeURIComponentFast(uriComponent: string, allowSlash: boolean): stri || code === CharCode.Period || code === CharCode.Underline || code === CharCode.Tilde - || (allowSlash && code === CharCode.Slash) + || (isPath && code === CharCode.Slash) // path => allow slash AS-IS + || (isQueryString && (code === CharCode.Equals || code === CharCode.Ampersand || code === CharCode.Semicolon)) // query string => allow &=; ) { // check if we are delaying native encode if (nativeEncodePos !== -1) { @@ -516,6 +608,20 @@ function encodeURIComponentFast(uriComponent: string, allowSlash: boolean): stri res += uriComponent.charAt(pos); } + } else if (code === CharCode.PercentSign && isHex(uriComponent, pos + 1) && isHex(uriComponent, pos + 2)) { + // at percentage encoded value + + // check if we are delaying native encode + if (nativeEncodePos !== -1) { + res += encodeURIComponent(uriComponent.substring(nativeEncodePos, pos)); + nativeEncodePos = -1; + } + // check if we write into a new string (by default we try to return the param) + if (res !== undefined) { + res += uriComponent.substr(pos, 3); + } + pos += 2; + } else { // encoding needed, we need to allocate a new string if (res === undefined) { @@ -604,6 +710,7 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string { let res = ''; let { scheme, authority, path, query, fragment } = uri; + if (scheme) { res += scheme; res += ':'; @@ -620,22 +727,22 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string { authority = authority.substr(idx + 1); idx = userinfo.indexOf(':'); if (idx === -1) { - res += encoder(userinfo, false); + res += encoder(userinfo, false, false); } else { // :@ - res += encoder(userinfo.substr(0, idx), false); + res += encoder(userinfo.substr(0, idx), false, false); res += ':'; - res += encoder(userinfo.substr(idx + 1), false); + res += encoder(userinfo.substr(idx + 1), false, false); } res += '@'; } authority = authority.toLowerCase(); idx = authority.indexOf(':'); if (idx === -1) { - res += encoder(authority, false); + res += encoder(authority, false, false); } else { // : - res += encoder(authority.substr(0, idx), false); + res += encoder(authority.substr(0, idx), false, false); res += authority.substr(idx); } } @@ -653,15 +760,15 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string { } } // encode the rest of the path - res += encoder(path, true); + res += encoder(path, true, false); } if (query) { res += '?'; - res += encoder(query, false); + res += encoder(query, false, _isQueryStringScheme(scheme)); } if (fragment) { res += '#'; - res += !skipEncoding ? encodeURIComponentFast(fragment, false) : fragment; + res += !skipEncoding ? encodeURIComponentFast(fragment, false, false) : fragment; } return res; } diff --git a/src/vs/base/common/uriIpc.ts b/src/vs/base/common/uriIpc.ts index 96fe0791051e..7cfc81703bde 100644 --- a/src/vs/base/common/uriIpc.ts +++ b/src/vs/base/common/uriIpc.ts @@ -10,6 +10,51 @@ export interface IURITransformer { transformIncoming(uri: UriComponents): UriComponents; transformOutgoing(uri: UriComponents): UriComponents; transformOutgoingURI(uri: URI): URI; + transformOutgoingScheme(scheme: string): string; +} + +export interface UriParts { + scheme: string; + authority?: string; + path?: string; +} + +export interface IRawURITransformer { + transformIncoming(uri: UriParts): UriParts; + transformOutgoing(uri: UriParts): UriParts; + transformOutgoingScheme(scheme: string): string; +} + +function toJSON(uri: URI): UriComponents { + return uri.toJSON(); +} + +export class URITransformer implements IURITransformer { + + private readonly _uriTransformer: IRawURITransformer; + + constructor(uriTransformer: IRawURITransformer) { + this._uriTransformer = uriTransformer; + } + + public transformIncoming(uri: UriComponents): UriComponents { + const result = this._uriTransformer.transformIncoming(uri); + return (result === uri ? uri : toJSON(URI.from(result))); + } + + public transformOutgoing(uri: UriComponents): UriComponents { + const result = this._uriTransformer.transformOutgoing(uri); + return (result === uri ? uri : toJSON(URI.from(result))); + } + + public transformOutgoingURI(uri: URI): URI { + const result = this._uriTransformer.transformOutgoing(uri); + return (result === uri ? uri : URI.from(result)); + } + + public transformOutgoingScheme(scheme: string): string { + return this._uriTransformer.transformOutgoingScheme(scheme); + } } export const DefaultURITransformer: IURITransformer = new class { @@ -24,6 +69,10 @@ export const DefaultURITransformer: IURITransformer = new class { transformOutgoingURI(uri: URI): URI { return uri; } + + transformOutgoingScheme(scheme: string): string { + return scheme; + } }; function _transformOutgoingURIs(obj: any, transformer: IURITransformer, depth: number): any { diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 1655a685ed1e..31a402286f41 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -4,16 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { transformErrorForSerialization } from 'vs/base/common/errors'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { getAllPropertyNames } from 'vs/base/common/types'; const INITIALIZE = '$initialize'; -export interface IWorker { +export interface IWorker extends IDisposable { getId(): number; postMessage(message: string): void; - dispose(): void; } export interface IWorkerCallback { diff --git a/src/vs/base/node/config.ts b/src/vs/base/node/config.ts index 8ad630fc4f6a..1f861e37deeb 100644 --- a/src/vs/base/node/config.ts +++ b/src/vs/base/node/config.ts @@ -6,7 +6,7 @@ import * as fs from 'fs'; import { dirname } from 'vs/base/common/path'; import * as objects from 'vs/base/common/objects'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { statLink } from 'vs/base/node/pfs'; @@ -41,20 +41,17 @@ export interface IConfigOptions { * - delayed processing of changes to accomodate for lots of changes * - configurable defaults */ -export class ConfigWatcher implements IConfigWatcher, IDisposable { +export class ConfigWatcher extends Disposable implements IConfigWatcher { private cache: T; private parseErrors: json.ParseError[]; private disposed: boolean; private loaded: boolean; private timeoutHandle: NodeJS.Timer | null; - private disposables: IDisposable[]; private readonly _onDidUpdateConfiguration: Emitter>; constructor(private _path: string, private options: IConfigOptions = { defaultConfig: Object.create(null), onError: error => console.error(error) }) { - this.disposables = []; - - this._onDidUpdateConfiguration = new Emitter>(); - this.disposables.push(this._onDidUpdateConfiguration); + super(); + this._onDidUpdateConfiguration = this._register(new Emitter>()); this.registerWatcher(); this.initAsync(); @@ -111,10 +108,10 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { try { this.parseErrors = []; res = this.options.parse ? this.options.parse(raw, this.parseErrors) : json.parse(raw, this.parseErrors); + return res || this.options.defaultConfig; } catch (error) { - // Ignore parsing errors - return this.options.defaultConfig; + return this.options.defaultConfig; // Ignore parsing errors } } @@ -125,7 +122,7 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { this.watch(parentFolder, true); // Check if the path is a symlink and watch its target if so - this.handleSymbolicLink().then(undefined, error => { /* ignore error */ }); + this.handleSymbolicLink().then(undefined, () => { /* ignore error */ }); } private async handleSymbolicLink(): Promise { @@ -143,9 +140,9 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { } if (isFolder) { - this.disposables.push(watchFolder(path, (type, path) => path === this._path ? this.onConfigFileChange() : undefined, error => this.options.onError(error))); + this._register(watchFolder(path, (type, path) => path === this._path ? this.onConfigFileChange() : undefined, error => this.options.onError(error))); } else { - this.disposables.push(watchFile(path, (type, path) => this.onConfigFileChange(), error => this.options.onError(error))); + this._register(watchFile(path, () => this.onConfigFileChange(), error => this.options.onError(error))); } } @@ -187,6 +184,6 @@ export class ConfigWatcher implements IConfigWatcher, IDisposable { dispose(): void { this.disposed = true; - this.disposables = dispose(this.disposables); + super.dispose(); } } \ No newline at end of file diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index a4c4af66359b..9313a89adb00 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -226,7 +226,7 @@ export function readFile(path: string, encoding?: string): Promise } = Object.create(null); +const writeFilePathQueues: Map> = new Map(); export function writeFile(path: string, data: string, options?: IWriteFileOptions): Promise; export function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Promise; @@ -249,18 +249,20 @@ function toQueueKey(path: string): string { } function ensureWriteFileQueue(queueKey: string): Queue { - let writeFileQueue = writeFilePathQueue[queueKey]; - if (!writeFileQueue) { - writeFileQueue = new Queue(); - writeFilePathQueue[queueKey] = writeFileQueue; - - const onFinish = Event.once(writeFileQueue.onFinished); - onFinish(() => { - delete writeFilePathQueue[queueKey]; - writeFileQueue.dispose(); - }); + const existingWriteFileQueue = writeFilePathQueues.get(queueKey); + if (existingWriteFileQueue) { + return existingWriteFileQueue; } + const writeFileQueue = new Queue(); + writeFilePathQueues.set(queueKey, writeFileQueue); + + const onFinish = Event.once(writeFileQueue.onFinished); + onFinish(() => { + writeFilePathQueues.delete(queueKey); + writeFileQueue.dispose(); + }); + return writeFileQueue; } diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index a98bcc4de42e..fa93ee0f0097 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -5,6 +5,7 @@ import * as path from 'vs/base/common/path'; import * as fs from 'fs'; +import { promisify } from 'util'; import * as cp from 'child_process'; import * as nls from 'vs/nls'; import * as Types from 'vs/base/common/types'; @@ -404,7 +405,7 @@ export function createQueuedSender(childProcess: cp.ChildProcess): IQueuedSender } export namespace win32 { - export function findExecutable(command: string, cwd?: string, paths?: string[]): string { + export async function findExecutable(command: string, cwd?: string, paths?: string[]): Promise { // If we have an absolute path then we take it. if (path.isAbsolute(command)) { return command; @@ -435,15 +436,15 @@ export namespace win32 { } else { fullPath = path.join(cwd, pathEntry, command); } - if (fs.existsSync(fullPath)) { + if (await promisify(fs.exists)(fullPath)) { return fullPath; } let withExtension = fullPath + '.com'; - if (fs.existsSync(withExtension)) { + if (await promisify(fs.exists)(withExtension)) { return withExtension; } withExtension = fullPath + '.exe'; - if (fs.existsSync(withExtension)) { + if (await promisify(fs.exists)(withExtension)) { return withExtension; } } diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 4148a623b43c..5ec8df2f7c48 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -11,13 +11,12 @@ import * as platform from 'vs/base/common/platform'; declare var process: any; -export interface ISocket { +export interface ISocket extends IDisposable { onData(listener: (e: VSBuffer) => void): IDisposable; onClose(listener: () => void): IDisposable; onEnd(listener: () => void): IDisposable; write(buffer: VSBuffer): void; end(): void; - dispose(): void; } let emptyBuffer: VSBuffer | null = null; @@ -407,7 +406,7 @@ export class Client extends IPCClient { /** * Will ensure no messages are lost if there are no event listeners. */ -function createBufferedEvent(source: Event): Event { +export function createBufferedEvent(source: Event): Event { let emitter: Emitter; let hasListeners = false; let isDeliveringMessages = false; @@ -514,7 +513,7 @@ class Queue { * Same as Protocol, but will actually track messages and acks. * Moreover, it will ensure no messages are lost if there are no event listeners. */ -export class PersistentProtocol { +export class PersistentProtocol implements IMessagePassingProtocol { private _isReconnecting: boolean; diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index adc9cfbf31f6..29b8a4795447 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -464,7 +464,7 @@ export class ChannelClient implements IChannelClient, IDisposable { }; const cancellationTokenListener = cancellationToken.onCancellationRequested(cancel); - disposable = combinedDisposable([toDisposable(cancel), cancellationTokenListener]); + disposable = combinedDisposable(toDisposable(cancel), cancellationTokenListener); this.activeRequests.add(disposable); }); diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 5a9b6f76d461..5a79572f419e 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { Socket, Server as NetServer, createConnection, createServer } from 'net'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc'; import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; import { generateUuid } from 'vs/base/common/uuid'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; -import { ISocket, Protocol, Client } from 'vs/base/parts/ipc/common/ipc.net'; +import { ISocket, Protocol, Client, ChunkStream } from 'vs/base/parts/ipc/common/ipc.net'; export class NodeSocket implements ISocket { public readonly socket: Socket; @@ -65,6 +65,197 @@ export class NodeSocket implements ISocket { } } +const enum Constants { + MinHeaderByteSize = 2 +} + +const enum ReadState { + PeekHeader = 1, + ReadHeader = 2, + ReadBody = 3, + Fin = 4 +} + +/** + * See https://tools.ietf.org/html/rfc6455#section-5.2 + */ +export class WebSocketNodeSocket extends Disposable implements ISocket { + + public readonly socket: NodeSocket; + private readonly _incomingData: ChunkStream; + private readonly _onData = this._register(new Emitter()); + + private readonly _state = { + state: ReadState.PeekHeader, + readLen: Constants.MinHeaderByteSize, + mask: 0 + }; + + constructor(socket: NodeSocket) { + super(); + this.socket = socket; + this._incomingData = new ChunkStream(); + this._register(this.socket.onData(data => this._acceptChunk(data))); + } + + public dispose(): void { + this.socket.dispose(); + } + + public onData(listener: (e: VSBuffer) => void): IDisposable { + return this._onData.event(listener); + } + + public onClose(listener: () => void): IDisposable { + return this.socket.onClose(listener); + } + + public onEnd(listener: () => void): IDisposable { + return this.socket.onEnd(listener); + } + + public write(buffer: VSBuffer): void { + let headerLen = Constants.MinHeaderByteSize; + if (buffer.byteLength < 126) { + headerLen += 0; + } else if (buffer.byteLength < 2 ** 16) { + headerLen += 2; + } else { + headerLen += 8; + } + const header = VSBuffer.alloc(headerLen); + + header.writeUInt8(0b10000010, 0); + if (buffer.byteLength < 126) { + header.writeUInt8(buffer.byteLength, 1); + } else if (buffer.byteLength < 2 ** 16) { + header.writeUInt8(126, 1); + let offset = 1; + header.writeUInt8((buffer.byteLength >>> 8) & 0b11111111, ++offset); + header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset); + } else { + header.writeUInt8(127, 1); + let offset = 1; + header.writeUInt8(0, ++offset); + header.writeUInt8(0, ++offset); + header.writeUInt8(0, ++offset); + header.writeUInt8(0, ++offset); + header.writeUInt8((buffer.byteLength >>> 24) & 0b11111111, ++offset); + header.writeUInt8((buffer.byteLength >>> 16) & 0b11111111, ++offset); + header.writeUInt8((buffer.byteLength >>> 8) & 0b11111111, ++offset); + header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset); + } + + this.socket.write(VSBuffer.concat([header, buffer])); + } + + public end(): void { + this.socket.end(); + } + + private _acceptChunk(data: VSBuffer): void { + if (data.byteLength === 0) { + return; + } + + this._incomingData.acceptChunk(data); + + while (this._incomingData.byteLength >= this._state.readLen) { + + if (this._state.state === ReadState.PeekHeader) { + // peek to see if we can read the entire header + const peekHeader = this._incomingData.peek(this._state.readLen); + // const firstByte = peekHeader.readUInt8(0); + // const finBit = (firstByte & 0b10000000) >>> 7; + const secondByte = peekHeader.readUInt8(1); + const hasMask = (secondByte & 0b10000000) >>> 7; + const len = (secondByte & 0b01111111); + + this._state.state = ReadState.ReadHeader; + this._state.readLen = Constants.MinHeaderByteSize + (hasMask ? 4 : 0) + (len === 126 ? 2 : 0) + (len === 127 ? 8 : 0); + this._state.mask = 0; + + } else if (this._state.state === ReadState.ReadHeader) { + // read entire header + const header = this._incomingData.read(this._state.readLen); + const secondByte = header.readUInt8(1); + const hasMask = (secondByte & 0b10000000) >>> 7; + let len = (secondByte & 0b01111111); + + let offset = 1; + if (len === 126) { + len = ( + header.readUInt8(++offset) * 2 ** 8 + + header.readUInt8(++offset) + ); + } else if (len === 127) { + len = ( + header.readUInt8(++offset) * 0 + + header.readUInt8(++offset) * 0 + + header.readUInt8(++offset) * 0 + + header.readUInt8(++offset) * 0 + + header.readUInt8(++offset) * 2 ** 24 + + header.readUInt8(++offset) * 2 ** 16 + + header.readUInt8(++offset) * 2 ** 8 + + header.readUInt8(++offset) + ); + } + + let mask = 0; + if (hasMask) { + mask = ( + header.readUInt8(++offset) * 2 ** 24 + + header.readUInt8(++offset) * 2 ** 16 + + header.readUInt8(++offset) * 2 ** 8 + + header.readUInt8(++offset) + ); + } + + this._state.state = ReadState.ReadBody; + this._state.readLen = len; + this._state.mask = mask; + + } else if (this._state.state === ReadState.ReadBody) { + // read body + + const body = this._incomingData.read(this._state.readLen); + unmask(body, this._state.mask); + + this._state.state = ReadState.PeekHeader; + this._state.readLen = Constants.MinHeaderByteSize; + this._state.mask = 0; + + this._onData.fire(body); + } + } + } +} + +function unmask(buffer: VSBuffer, mask: number): void { + if (mask === 0) { + return; + } + let cnt = buffer.byteLength >>> 2; + for (let i = 0; i < cnt; i++) { + const v = buffer.readUInt32BE(i * 4); + buffer.writeUInt32BE(v ^ mask, i * 4); + } + let offset = cnt * 4; + let bytesLeft = buffer.byteLength - offset; + const m3 = (mask >>> 24) & 0b11111111; + const m2 = (mask >>> 16) & 0b11111111; + const m1 = (mask >>> 8) & 0b11111111; + if (bytesLeft >= 1) { + buffer.writeUInt8(buffer.readUInt8(offset) ^ m3, offset); + } + if (bytesLeft >= 2) { + buffer.writeUInt8(buffer.readUInt8(offset + 1) ^ m2, offset + 1); + } + if (bytesLeft >= 3) { + buffer.writeUInt8(buffer.readUInt8(offset + 2) ^ m1, offset + 2); + } +} + export function generateRandomPipeName(): string { const randomSuffix = generateUuid(); if (process.platform === 'win32') { diff --git a/src/vs/base/parts/tree/browser/tree.ts b/src/vs/base/parts/tree/browser/tree.ts index 14630c817409..e13df1cdcb4e 100644 --- a/src/vs/base/parts/tree/browser/tree.ts +++ b/src/vs/base/parts/tree/browser/tree.ts @@ -254,7 +254,7 @@ export interface IDataSource { * * You should not attempt to "move" an element to a different * parent by keeping its ID. The idea here is to have tree location - * related IDs (eg. full file path, in the Explorer example). + * related IDs (e.g. full file path, in the Explorer example). */ getId(tree: ITree, element: any): string; @@ -616,7 +616,7 @@ export interface IActionProvider { hasActions(tree: ITree | null, element: any): boolean; /** - * Returns a promise of an array with the actions of the element that should show up in place right to the element in the tree. + * Returns an array with the actions of the element that should show up in place right to the element in the tree. */ - getActions(tree: ITree | null, element: any): IAction[] | null; + getActions(tree: ITree | null, element: any): ReadonlyArray | null; } diff --git a/src/vs/base/parts/tree/browser/treeModel.ts b/src/vs/base/parts/tree/browser/treeModel.ts index 95b4513d136e..19f72742397f 100644 --- a/src/vs/base/parts/tree/browser/treeModel.ts +++ b/src/vs/base/parts/tree/browser/treeModel.ts @@ -159,7 +159,7 @@ export class ItemRegistry { public register(item: Item): void { Assert.ok(!this.isRegistered(item.id), 'item already registered: ' + item.id); - const disposable = combinedDisposable([ + const disposable = combinedDisposable( this._onDidRevealItem.add(item.onDidReveal), this._onExpandItem.add(item.onExpand), this._onDidExpandItem.add(item.onDidExpand), @@ -171,7 +171,7 @@ export class ItemRegistry { this._onRefreshItemChildren.add(item.onRefreshChildren), this._onDidRefreshItemChildren.add(item.onDidRefreshChildren), this._onDidDisposeItem.add(item.onDidDispose) - ]); + ); this.items[item.id] = { item, disposable }; } diff --git a/src/vs/base/test/browser/hash.test.ts b/src/vs/base/test/browser/hash.test.ts deleted file mode 100644 index cff6dbe9b183..000000000000 --- a/src/vs/base/test/browser/hash.test.ts +++ /dev/null @@ -1,16 +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 { createSHA1 } from 'vs/base/browser/hash'; - -suite('Hash', () => { - test('computeSHA1Hash', async () => { - assert.equal(await createSHA1(''), 'da39a3ee5e6b4b0d3255bfef95601890afd80709'); - assert.equal(await createSHA1('hello world'), '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'); - assert.equal(await createSHA1('da39a3ee5e6b4b0d3255bfef95601890afd80709'), '10a34637ad661d98ba3344717656fcc76209c2f8'); - assert.equal(await createSHA1('2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'), 'd6b0d82cea4269b51572b8fab43adcee9fc3cf9a'); - assert.equal(await createSHA1('öäü_?ß()<>ÖÄÜ'), 'b64beaeff9e317b0193c8e40a2431b210388eba9'); - }); -}); \ No newline at end of file diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 3c40821802be..91e0ca7c609c 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { Event, Emitter, EventBufferer, EventMultiplexer, AsyncEmitter, IWaitUntil, PauseableEmitter } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as Errors from 'vs/base/common/errors'; import { timeout } from 'vs/base/common/async'; @@ -73,6 +73,27 @@ suite('Event', function () { while (bucket.length) { bucket.pop()!.dispose(); } + doc.setText('boo'); + + // noop + subscription.dispose(); + + doc.setText('boo'); + assert.equal(counter.count, 2); + }); + + test('Emitter, store', function () { + + let bucket = new DisposableStore(); + let doc = new Samples.Document3(); + let subscription = doc.onDidChange(counter.onEvent, counter, bucket); + + doc.setText('far'); + doc.setText('boo'); + + // unhook listener + bucket.clear(); + doc.setText('boo'); // noop subscription.dispose(); diff --git a/src/vs/base/test/common/lifecycle.test.ts b/src/vs/base/test/common/lifecycle.test.ts index c083c3bdb89c..b713c5912a14 100644 --- a/src/vs/base/test/common/lifecycle.test.ts +++ b/src/vs/base/test/common/lifecycle.test.ts @@ -42,7 +42,8 @@ suite('Lifecycle', () => { assert(!disposable.isDisposed); assert(!disposable2.isDisposed); - dispose(disposable, disposable2); + dispose(disposable); + dispose(disposable2); assert(disposable.isDisposed); assert(disposable2.isDisposed); diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 4e9f685ecd4c..75e6e813a400 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -63,7 +63,7 @@ suite('URI', () => { assert.equal(URI.from({ scheme: 'http', authority: '', path: 'my/path' }).toString(), 'http:/my/path'); assert.equal(URI.from({ scheme: 'http', authority: '', path: '/my/path' }).toString(), 'http:/my/path'); //http://a-test-site.com/#test=true - assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: 'test=true' }).toString(), 'http://a-test-site.com/?test%3Dtrue'); + assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: 'test=true' }).toString(), 'http://a-test-site.com/?test=true'); assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: '', fragment: 'test=true' }).toString(), 'http://a-test-site.com/#test%3Dtrue'); }); @@ -102,11 +102,11 @@ suite('URI', () => { test('with, changes', () => { assert.equal(URI.parse('before:some/file/path').with({ scheme: 'after' }).toString(), 'after:some/file/path'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(), 'http:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'http:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'https', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'https:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTP', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTP:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTPS', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTPS:/api/files/test.me?t%3D1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(), 'http:/api/files/test.me?t=1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'http:/api/files/test.me?t=1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'https', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'https:/api/files/test.me?t=1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTP', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTP:/api/files/test.me?t=1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTPS', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTPS:/api/files/test.me?t=1234'); assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'boo', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'boo:/api/files/test.me?t%3D1234'); }); @@ -262,11 +262,11 @@ suite('URI', () => { value = URI.file('c:\\test with %25\\path'); assert.equal(value.path, '/c:/test with %25/path'); - assert.equal(value.toString(), 'file:///c%3A/test%20with%20%2525/path'); + assert.equal(value.toString(), 'file:///c%3A/test%20with%20%25/path'); value = URI.file('c:\\test with %25\\c#code'); assert.equal(value.path, '/c:/test with %25/c#code'); - assert.equal(value.toString(), 'file:///c%3A/test%20with%20%2525/c%23code'); + assert.equal(value.toString(), 'file:///c%3A/test%20with%20%25/c%23code'); value = URI.file('\\\\shares'); assert.equal(value.scheme, 'file'); @@ -376,7 +376,7 @@ suite('URI', () => { let uri = URI.parse('https://go.microsoft.com/fwlink/?LinkId=518008'); assert.equal(uri.query, 'LinkId=518008'); assert.equal(uri.toString(true), 'https://go.microsoft.com/fwlink/?LinkId=518008'); - assert.equal(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId%3D518008'); + assert.equal(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId=518008'); let uri2 = URI.parse(uri.toString()); assert.equal(uri2.query, 'LinkId=518008'); @@ -385,7 +385,7 @@ suite('URI', () => { uri = URI.parse('https://go.microsoft.com/fwlink/?LinkId=518008&foö&ké¥=üü'); assert.equal(uri.query, 'LinkId=518008&foö&ké¥=üü'); assert.equal(uri.toString(true), 'https://go.microsoft.com/fwlink/?LinkId=518008&foö&ké¥=üü'); - assert.equal(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId%3D518008%26fo%C3%B6%26k%C3%A9%C2%A5%3D%C3%BC%C3%BC'); + assert.equal(uri.toString(), 'https://go.microsoft.com/fwlink/?LinkId=518008&fo%C3%B6&k%C3%A9%C2%A5=%C3%BC%C3%BC'); uri2 = URI.parse(uri.toString()); assert.equal(uri2.query, 'LinkId=518008&foö&ké¥=üü'); @@ -426,6 +426,57 @@ suite('URI', () => { assert.equal(uri.toString(true), input); }); + test('Support URL specific encodings (query component) #25852', function () { + let input = 'http://example.com/over/there?name=ferret'; + assert.equal(input, URI.parse(input).toString()); + + input = 'http://example.com/over/there?name=ferret&foo=bar'; + assert.equal(input, URI.parse(input).toString()); + + input = 'attp://example.com/over/there?name=ferret'; + assert.equal('attp://example.com/over/there?name%3Dferret', URI.parse(input).toString()); + }); + + test('Uri#parse can break path-component #45515', function () { + let uri: URI; + uri = URI.from({ scheme: 's', authority: 'a', path: '/o%2f' }); + assert.equal(uri.toString(), 's://a/o%2f'); + uri = URI.from({ scheme: 's', authority: 'a', path: '/o%2fü' }); + assert.equal(uri.toString(), 's://a/o%2f%C3%BC'); + uri = URI.from({ scheme: 's', authority: 'a', path: '/o%2f%' }); + assert.equal(uri.toString(), 's://a/o%2f%25'); + + uri = URI.file('/test with %25/c#code'); + assert.equal(uri.path, '/test with %25/c#code'); + assert.equal(uri.toString(), 'file:///test%20with%20%25/c%23code'); + + uri = URI.from({ + scheme: 'http', + authority: 'a', + path: '/o/products%2FzVNZkudXJyq8bPGTXUxx%2FBetterave-Sesame.jpg' + }); + assert.equal(uri.path, '/o/products%2FzVNZkudXJyq8bPGTXUxx%2FBetterave-Sesame.jpg'); + assert.equal(uri.toString(), 'http://a/o/products%2FzVNZkudXJyq8bPGTXUxx%2FBetterave-Sesame.jpg'); + + assert.equal(URI.parse(uri.toString()).path, '/o/products%2FzVNZkudXJyq8bPGTXUxx%2FBetterave-Sesame.jpg'); + assert.equal(uri.toString(), URI.parse(uri.toString()).toString()); // identity + + uri = URI.parse('s://a/p%2ft%c3%bc'); + assert.equal(uri.path, '/p%2ftü'); + + uri = URI.parse('s://a/%c3%bcp%2f-REST'); + assert.equal(uri.path, '/üp%2f-REST'); + + uri = URI.parse('s://a/%c3%bcp%2fd%c3%b6wn'); + assert.equal(uri.path, '/üp%2fdöwn'); + + //https://github.com/microsoft/vscode/issues/25852 + uri = URI.parse('http://www.test.com/path/service?authId=CN%3DQ10'); + assert.equal(uri.query, 'authId=CN%3DQ10'); + assert.equal(uri.toString(), 'http://www.test.com/path/service?authId=CN%3DQ10'); + }); + + test('URI - (de)serialize', function () { const values = [ diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 92e4cba15f79..07ac3c653dfa 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -13,7 +13,12 @@ diff --git a/src/vs/code/browser/workbench/workbench.js b/src/vs/code/browser/workbench/workbench.js index f91b531007e7..7cb78f5c010b 100644 --- a/src/vs/code/browser/workbench/workbench.js +++ b/src/vs/code/browser/workbench/workbench.js @@ -21,7 +21,11 @@ // @ts-ignore require.config({ - baseUrl: `${window.location.origin}/out` + baseUrl: `${window.location.origin}/out`, + paths: { + 'vscode-textmate': `${window.location.origin}/node_modules/vscode-textmate/release/main`, + 'onigasm-umd': `${window.location.origin}/node_modules/onigasm-umd/release/main`, + } }); // @ts-ignore @@ -32,9 +36,8 @@ ], // @ts-ignore function () { - // @ts-ignore - require('vs/workbench/browser/web.main').main().then(undefined, console.error); + require('vs/workbench/browser/web.main').main(self['WINDOW_CONFIGURATION']).then(undefined, console.error); }); }); })(); \ No newline at end of file diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index b982aadc0b00..85e0d60dc332 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -33,7 +33,6 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel'; import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; -import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService'; import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc'; import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; @@ -41,6 +40,7 @@ import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporter import { Button } from 'vs/base/browser/ui/button/button'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { SpdLogService } from 'vs/platform/log/node/spdlogService'; const MAX_URL_LENGTH = 2045; @@ -300,7 +300,7 @@ export class IssueReporter extends Disposable { serviceCollection.set(IWindowsService, new WindowsService(mainProcessService)); this.environmentService = new EnvironmentService(configuration, configuration.execPath); - const logService = createBufferSpdLogService(`issuereporter${configuration.windowId}`, getLogLevel(this.environmentService), this.environmentService.logsPath); + const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService)); const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel')); this.logService = new FollowerLogService(logLevelClient, logService); diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts index f62e2f8b610b..5c59f89bf5cf 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts @@ -8,7 +8,7 @@ import * as pfs from 'vs/base/node/pfs'; import { IStringDictionary } from 'vs/base/common/collections'; import product from 'vs/platform/product/node/product'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -30,14 +30,13 @@ interface LanguagePackFile { [locale: string]: LanguagePackEntry; } -export class LanguagePackCachedDataCleaner { - - private _disposables: IDisposable[] = []; +export class LanguagePackCachedDataCleaner extends Disposable { constructor( @IEnvironmentService private readonly _environmentService: IEnvironmentService, @ILogService private readonly _logService: ILogService ) { + super(); // We have no Language pack support for dev version (run from source) // So only cleanup when we have a build version. if (this._environmentService.isBuilt) { @@ -45,10 +44,6 @@ export class LanguagePackCachedDataCleaner { } } - dispose(): void { - this._disposables = dispose(this._disposables); - } - private _manageCachedDataSoon(): void { let handle: any = setTimeout(async () => { handle = undefined; @@ -101,12 +96,10 @@ export class LanguagePackCachedDataCleaner { } }, 40 * 1000); - this._disposables.push({ - dispose() { - if (handle !== undefined) { - clearTimeout(handle); - } + this._register(toDisposable(() => { + if (handle !== undefined) { + clearTimeout(handle); } - }); + })); } } \ No newline at end of file diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 28b6572c9b9b..575362160c9e 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -30,7 +30,6 @@ import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppen import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common/windows'; import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; import { ipcRenderer } from 'electron'; -import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; @@ -48,6 +47,7 @@ import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contr import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { SpdLogService } from 'vs/platform/log/node/spdlogService'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -79,7 +79,7 @@ class MainProcessService implements IMainProcessService { } } -function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): void { +async function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): Promise { const services = new ServiceCollection(); const disposables: IDisposable[] = []; @@ -94,14 +94,17 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I const mainRouter = new StaticRouter(ctx => ctx === 'main'); const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', mainRouter)); - const logService = new FollowerLogService(logLevelClient, createBufferSpdLogService('sharedprocess', initData.logLevel, environmentService.logsPath)); + const logService = new FollowerLogService(logLevelClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel)); disposables.push(logService); - logService.info('main', JSON.stringify(configuration)); + const configurationService = new ConfigurationService(environmentService.settingsResource.path); + disposables.push(configurationService); + await configurationService.initialize(); + services.set(IEnvironmentService, environmentService); services.set(ILogService, logService); - services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath])); + services.set(IConfigurationService, configurationService); services.set(IRequestService, new SyncDescriptor(RequestService)); services.set(IDownloadService, new SyncDescriptor(DownloadService)); @@ -122,7 +125,7 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I const services = new ServiceCollection(); const environmentService = accessor.get(IEnvironmentService); const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; - const telemetryLogService = new FollowerLogService(logLevelClient, createBufferSpdLogService('telemetry', initData.logLevel, environmentService.logsPath)); + const telemetryLogService = new FollowerLogService(logLevelClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel)); telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.'); telemetryLogService.info('==========================================================='); @@ -165,12 +168,12 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I // update localizations cache (localizationsService as LocalizationsService).update(); // cache clean ups - disposables.push(combinedDisposable([ + disposables.push(combinedDisposable( instantiationService2.createInstance(NodeCachedDataCleaner), instantiationService2.createInstance(LanguagePackCachedDataCleaner), instantiationService2.createInstance(StorageDataCleaner), instantiationService2.createInstance(LogsDataCleaner) - ])); + )); disposables.push(extensionManagementService as ExtensionManagementService); }); }); @@ -218,6 +221,6 @@ async function handshake(configuration: ISharedProcessConfiguration): Promise - + diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 90ab9dd0a13b..1fef3b3b34a1 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -54,13 +54,7 @@ bootstrapWindow.load([ showPartsSplash(windowConfig); }, beforeLoaderConfig: function (windowConfig, loaderConfig) { - loaderConfig.recordStats = !!windowConfig['prof-modules']; - if (loaderConfig.nodeCachedData) { - const onNodeCachedData = window['MonacoEnvironment'].onNodeCachedData = []; - loaderConfig.nodeCachedData.onData = function () { - onNodeCachedData.push(arguments); - }; - } + loaderConfig.recordStats = true; }, beforeRequire: function () { perf.mark('willLoadWorkbenchMain'); @@ -89,7 +83,7 @@ function showPartsSplash(configuration) { } } - // high contrast mode has been turned on from the outside, e.g OS -> ignore stored colors and layouts + // high contrast mode has been turned on from the outside, e.g. OS -> ignore stored colors and layouts if (data && configuration.highContrast && data.baseTheme !== 'hc-black') { data = undefined; } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index eae9dc72f3e0..92a76cb94ce5 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -9,7 +9,7 @@ import { WindowsManager } from 'vs/code/electron-main/windows'; import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows'; import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc'; import { WindowsService } from 'vs/platform/windows/electron-main/windowsService'; -import { ILifecycleService, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { getShellEnvironment } from 'vs/code/node/shellEnv'; import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateChannel } from 'vs/platform/update/node/updateIpc'; @@ -36,12 +36,10 @@ import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; import product from 'vs/platform/product/node/product'; import pkg from 'vs/platform/product/node/package'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; -import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService } from 'vs/platform/history/common/history'; -import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types'; -import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard'; +import { withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc'; import { IWorkspacesMainService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; @@ -63,7 +61,6 @@ import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc'; import { hasArgs } from 'vs/platform/environment/node/argv'; import { RunOnceScheduler } from 'vs/base/common/async'; import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu'; -import { storeBackgroundColor } from 'vs/code/electron-main/theme'; import { homedir } from 'os'; import { join, sep } from 'vs/base/common/path'; import { localize } from 'vs/nls'; @@ -88,12 +85,7 @@ export class CodeApplication extends Disposable { private static readonly MACHINE_ID_KEY = 'telemetry.machineId'; - private windowsMainService: IWindowsMainService; - - private electronIpcServer: ElectronIPCServer; - - private sharedProcess: SharedProcess; - private sharedProcessClient: Promise; + private windowsMainService: IWindowsMainService | undefined; constructor( private readonly mainIpcServer: Server, @@ -102,14 +94,11 @@ export class CodeApplication extends Disposable { @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IConfigurationService private readonly configurationService: ConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IStateService private readonly stateService: IStateService ) { super(); - this._register(mainIpcServer); - this._register(configurationService); - this.registerListeners(); } @@ -120,12 +109,12 @@ export class CodeApplication extends Disposable { process.on('uncaughtException', err => this.onUnexpectedError(err)); process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); - // Contextmenu via IPC support - registerContextMenuListener(); - // Dispose on shutdown this.lifecycleService.onWillShutdown(() => this.dispose()); + // Contextmenu via IPC support + registerContextMenuListener(); + app.on('accessibility-support-changed', (event: Event, accessibilitySupportEnabled: boolean) => { if (this.windowsMainService) { this.windowsMainService.sendToAll('vscode:accessibilitySupportChanged', accessibilitySupportEnabled); @@ -198,7 +187,7 @@ export class CodeApplication extends Disposable { event.preventDefault(); // Keep in array because more might come! - macOpenFileURIs.push(getURIToOpenFromPathSync(path)); + macOpenFileURIs.push(this.getURIToOpenFromPathSync(path)); // Clear previous handler if any if (runningTimeout !== null) { @@ -215,6 +204,7 @@ export class CodeApplication extends Disposable { urisToOpen: macOpenFileURIs, preferNewWindow: true /* dropping on the dock or opening from finder prefers to open in a new window */ }); + macOpenFileURIs = []; runningTimeout = null; } @@ -222,7 +212,9 @@ export class CodeApplication extends Disposable { }); app.on('new-window-for-tab', () => { - this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button + if (this.windowsMainService) { + this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button + } }); ipc.on('vscode:exit', (event: Event, code: number) => { @@ -232,37 +224,26 @@ export class CodeApplication extends Disposable { this.lifecycleService.kill(code); }); - ipc.on('vscode:fetchShellEnv', (event: Event) => { + ipc.on('vscode:fetchShellEnv', async (event: Event) => { const webContents = event.sender; - getShellEnvironment(this.logService).then(shellEnv => { + + try { + const shellEnv = await getShellEnvironment(this.logService, this.environmentService); if (!webContents.isDestroyed()) { webContents.send('vscode:acceptShellEnv', shellEnv); } - }, err => { + } catch (error) { if (!webContents.isDestroyed()) { webContents.send('vscode:acceptShellEnv', {}); } - this.logService.error('Error fetching shell env', err); - }); - }); - - ipc.on('vscode:broadcast', (event: Event, windowId: number, broadcast: { channel: string; payload: object; }) => { - if (this.windowsMainService && broadcast.channel && !isUndefinedOrNull(broadcast.payload)) { - this.logService.trace('IPC#vscode:broadcast', broadcast.channel, broadcast.payload); - - // Handle specific events on main side - this.onBroadcast(broadcast.channel, broadcast.payload); - - // Send to all windows (except sender window) - this.windowsMainService.sendToAll('vscode:broadcast', broadcast, [windowId]); + this.logService.error('Error fetching shell env', error); } }); ipc.on('vscode:extensionHostDebug', (_: Event, windowId: number, broadcast: any) => { if (this.windowsMainService) { - // Send to all windows (except sender window) - this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]); + this.windowsMainService.sendToAll('vscode:extensionHostDebug', broadcast, [windowId]); // Send to all windows (except sender window) } }); @@ -271,11 +252,28 @@ export class CodeApplication extends Disposable { ipc.on('vscode:reloadWindow', (event: Event) => event.sender.reload()); - powerMonitor.on('resume', () => { // After waking up from sleep - if (this.windowsMainService) { - this.windowsMainService.sendToAll('vscode:osResume', undefined); - } - }); + // After waking up from sleep (after window opened) + (async () => { + await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen); + + powerMonitor.on('resume', () => { + if (this.windowsMainService) { + this.windowsMainService.sendToAll('vscode:osResume', undefined); + } + }); + })(); + + // Keyboard layout changes (after window opened) + (async () => { + await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen); + + const nativeKeymap = await import('native-keymap'); + nativeKeymap.onDidChangeKeyboardLayout(() => { + if (this.windowsMainService) { + this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false); + } + }); + })(); } private onUnexpectedError(err: Error): void { @@ -299,15 +297,7 @@ export class CodeApplication extends Disposable { } } - private onBroadcast(event: string, payload: object): void { - - // Theme changes - if (event === 'vscode:changeColorTheme' && typeof payload === 'string') { - storeBackgroundColor(this.stateService, JSON.parse(payload)); - } - } - - startup(): Promise { + async startup(): Promise { this.logService.debug('Starting VS Code'); this.logService.debug(`from: ${this.environmentService.appRoot}`); this.logService.debug('args:', this.environmentService.args); @@ -335,130 +325,108 @@ export class CodeApplication extends Disposable { } // Create Electron IPC Server - this.electronIpcServer = new ElectronIPCServer(); + const electronIpcServer = new ElectronIPCServer(); - const startupWithMachineId = (machineId: string) => { - this.logService.trace(`Resolved machine identifier: ${machineId}`); - - // Spawn shared process - this.sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); - this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); + // Resolve unique machine ID + this.logService.trace('Resolving machine identifier...'); + const machineId = await this.resolveMachineId(); + this.logService.trace(`Resolved machine identifier: ${machineId}`); + + // Spawn shared process after the first window has opened and 3s have passed + const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); + const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); + this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { + this._register(new RunOnceScheduler(async () => { + const userEnv = await getShellEnvironment(this.logService, this.environmentService); + + sharedProcess.spawn(userEnv); + }, 3000)).schedule(); + }); - // Services - return this.initServices(machineId).then(appInstantiationService => { + // Services + const appInstantiationService = await this.createServices(machineId, sharedProcess, sharedProcessClient); - // Create driver - if (this.environmentService.driverHandle) { - serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => { - this.logService.info('Driver started at:', this.environmentService.driverHandle); - this._register(server); - }); - } + // Create driver + if (this.environmentService.driverHandle) { + (async () => { + const server = await serveDriver(electronIpcServer, this.environmentService.driverHandle!, this.environmentService, appInstantiationService); - // Setup Auth Handler - const authHandler = appInstantiationService.createInstance(ProxyAuthHandler); - this._register(authHandler); + this.logService.info('Driver started at:', this.environmentService.driverHandle); + this._register(server); + })(); + } - // Open Windows - const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor)); + // Setup Auth Handler + const authHandler = appInstantiationService.createInstance(ProxyAuthHandler); + this._register(authHandler); - // Post Open Windows Tasks - appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor)); + // Open Windows + const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient)); - // Tracing: Stop tracing after windows are ready if enabled - if (this.environmentService.args.trace) { - this.stopTracingEventually(windows); - } - }); - }; + // Post Open Windows Tasks + this.afterWindowOpen(); - // Resolve unique machine ID - this.logService.trace('Resolving machine identifier...'); - const resolvedMachineId = this.resolveMachineId(); - if (typeof resolvedMachineId === 'string') { - return startupWithMachineId(resolvedMachineId); - } else { - return resolvedMachineId.then(machineId => startupWithMachineId(machineId)); + // Tracing: Stop tracing after windows are ready if enabled + if (this.environmentService.args.trace) { + this.stopTracingEventually(windows); } } - private resolveMachineId(): string | Promise { - const machineId = this.stateService.getItem(CodeApplication.MACHINE_ID_KEY); - if (machineId) { - return machineId; - } + private async resolveMachineId(): Promise { + + // We cache the machineId for faster lookups on startup + // and resolve it only once initially if not cached + let machineId = this.stateService.getItem(CodeApplication.MACHINE_ID_KEY); + if (!machineId) { + machineId = await getMachineId(); - return getMachineId().then(machineId => { this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId); + } - return machineId; - }); + return machineId; } - private stopTracingEventually(windows: ICodeWindow[]): void { - this.logService.info(`Tracing: waiting for windows to get ready...`); - - let recordingStopped = false; - const stopRecording = (timeout: boolean) => { - if (recordingStopped) { - return; - } + private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessClient: Promise>): Promise { + const services = new ServiceCollection(); - recordingStopped = true; // only once + switch (process.platform) { + case 'win32': + services.set(IUpdateService, new SyncDescriptor(Win32UpdateService)); + break; - contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => { - if (!timeout) { - this.windowsMainService.showMessageBox({ - type: 'info', - message: localize('trace.message', "Successfully created trace."), - detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize('trace.ok', "Ok")] - }, this.windowsMainService.getLastActiveWindow()); + case 'linux': + if (process.env.SNAP && process.env.SNAP_REVISION) { + services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION])); } else { - this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); + services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService)); } - }); - }; - - // Wait up to 30s before creating the trace anyways - const timeoutHandle = setTimeout(() => stopRecording(true), 30000); - - // Wait for all windows to get ready and stop tracing then - Promise.all(windows.map(window => window.ready())).then(() => { - clearTimeout(timeoutHandle); - stopRecording(false); - }); - } - - private initServices(machineId: string): Promise { - const services = new ServiceCollection(); + break; - if (process.platform === 'win32') { - services.set(IUpdateService, new SyncDescriptor(Win32UpdateService)); - } else if (process.platform === 'linux') { - if (process.env.SNAP && process.env.SNAP_REVISION) { - services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION])); - } else { - services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService)); - } - } else if (process.platform === 'darwin') { - services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService)); + case 'darwin': + services.set(IUpdateService, new SyncDescriptor(DarwinUpdateService)); + break; } - services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId])); - services.set(IWindowsService, new SyncDescriptor(WindowsService, [this.sharedProcess])); + services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv])); + services.set(IWindowsService, new SyncDescriptor(WindowsService, [sharedProcess])); services.set(ILaunchService, new SyncDescriptor(LaunchService)); services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv])); services.set(IMenubarService, new SyncDescriptor(MenubarService)); - services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); - services.set(IBackupMainService, new SyncDescriptor(BackupMainService)); + + const storageMainService = new StorageMainService(this.logService, this.environmentService); + services.set(IStorageMainService, storageMainService); + this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close())); + + const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService); + services.set(IBackupMainService, backupMainService); + services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService)); services.set(IURLService, new SyncDescriptor(URLService)); services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService)); // Telemetry if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { - const channel = getDelayedChannel(this.sharedProcessClient.then(c => c.getChannel('telemetryAppender'))); + const channel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('telemetryAppender'))); const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService)); const commonProperties = resolveCommonProperties(product.commit, pkg.version, machineId, this.environmentService.installSourcePath); const piiPaths = [this.environmentService.appRoot, this.environmentService.extensionsPath]; @@ -469,33 +437,50 @@ export class CodeApplication extends Disposable { services.set(ITelemetryService, NullTelemetryService); } - const appInstantiationService = this.instantiationService.createChild(services); - // Init services that require it - return appInstantiationService.invokeFunction(accessor => Promise.all([ - this.initStorageService(accessor), - this.initBackupService(accessor) - ])).then(() => appInstantiationService); + await backupMainService.initialize(); + + return this.instantiationService.createChild(services); } - private initStorageService(accessor: ServicesAccessor): Promise { - const storageMainService = accessor.get(IStorageMainService) as StorageMainService; + private stopTracingEventually(windows: ICodeWindow[]): void { + this.logService.info(`Tracing: waiting for windows to get ready...`); - // Ensure to close storage on shutdown - this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close())); + let recordingStopped = false; + const stopRecording = (timeout: boolean) => { + if (recordingStopped) { + return; + } - return Promise.resolve(); + recordingStopped = true; // only once - } + contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => { + if (!timeout) { + if (this.windowsMainService) { + this.windowsMainService.showMessageBox({ + type: 'info', + message: localize('trace.message', "Successfully created trace."), + detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), + buttons: [localize('trace.ok', "Ok")] + }, this.windowsMainService.getLastActiveWindow()); + } + } else { + this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); + } + }); + }; - private initBackupService(accessor: ServicesAccessor): Promise { - const backupMainService = accessor.get(IBackupMainService) as BackupMainService; + // Wait up to 30s before creating the trace anyways + const timeoutHandle = setTimeout(() => stopRecording(true), 30000); - return backupMainService.initialize(); + // Wait for all windows to get ready and stop tracing then + Promise.all(windows.map(window => window.ready())).then(() => { + clearTimeout(timeoutHandle); + stopRecording(false); + }); } - private openFirstWindow(accessor: ServicesAccessor): ICodeWindow[] { - const appInstantiationService = accessor.get(IInstantiationService); + private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise>): ICodeWindow[] { // Register more Main IPC services const launchService = accessor.get(ILaunchService); @@ -505,40 +490,40 @@ export class CodeApplication extends Disposable { // Register more Electron IPC services const updateService = accessor.get(IUpdateService); const updateChannel = new UpdateChannel(updateService); - this.electronIpcServer.registerChannel('update', updateChannel); + electronIpcServer.registerChannel('update', updateChannel); const issueService = accessor.get(IIssueService); const issueChannel = new IssueChannel(issueService); - this.electronIpcServer.registerChannel('issue', issueChannel); + electronIpcServer.registerChannel('issue', issueChannel); const workspacesService = accessor.get(IWorkspacesMainService); - const workspacesChannel = appInstantiationService.createInstance(WorkspacesChannel, workspacesService); - this.electronIpcServer.registerChannel('workspaces', workspacesChannel); + const workspacesChannel = new WorkspacesChannel(workspacesService); + electronIpcServer.registerChannel('workspaces', workspacesChannel); const windowsService = accessor.get(IWindowsService); const windowsChannel = new WindowsChannel(windowsService); - this.electronIpcServer.registerChannel('windows', windowsChannel); - this.sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel)); + electronIpcServer.registerChannel('windows', windowsChannel); + sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel)); const menubarService = accessor.get(IMenubarService); const menubarChannel = new MenubarChannel(menubarService); - this.electronIpcServer.registerChannel('menubar', menubarChannel); + electronIpcServer.registerChannel('menubar', menubarChannel); const urlService = accessor.get(IURLService); const urlChannel = new URLServiceChannel(urlService); - this.electronIpcServer.registerChannel('url', urlChannel); + electronIpcServer.registerChannel('url', urlChannel); const storageMainService = accessor.get(IStorageMainService); const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService as StorageMainService)); - this.electronIpcServer.registerChannel('storage', storageChannel); + electronIpcServer.registerChannel('storage', storageChannel); // Log level management const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService)); - this.electronIpcServer.registerChannel('loglevel', logLevelChannel); - this.sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel)); + electronIpcServer.registerChannel('loglevel', logLevelChannel); + sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel)); - // Lifecycle - (this.lifecycleService as LifecycleService).ready(); + // Signal phase: ready (services set) + this.lifecycleService.phase = LifecycleMainPhase.Ready; // Propagate to clients const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this @@ -546,7 +531,7 @@ export class CodeApplication extends Disposable { // Create a URL handler which forwards to the last active window const activeWindowManager = new ActiveWindowManager(windowsService); const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); - const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', activeWindowRouter); + const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', activeWindowRouter); const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel); // On Mac, Code can be running without any open windows, so we must create a window to handle urls, @@ -555,15 +540,17 @@ export class CodeApplication extends Disposable { const environmentService = accessor.get(IEnvironmentService); urlService.registerHandler({ - handleURL(uri: URI): Promise { + async handleURL(uri: URI): Promise { if (windowsMainService.getWindowCount() === 0) { const cli = { ...environmentService.args, goto: true }; const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true }); - return window.ready().then(() => urlService.open(uri)); + await window.ready(); + + return urlService.open(uri); } - return Promise.resolve(false); + return false; } }); } @@ -574,11 +561,9 @@ export class CodeApplication extends Disposable { // Watch Electron URLs and forward them to the UrlService const args = this.environmentService.args; const urls = args['open-url'] ? args._urls : []; - const urlListener = new ElectronURLListener(urls || [], urlService, this.windowsMainService); + const urlListener = new ElectronURLListener(urls || [], urlService, windowsMainService); this._register(urlListener); - this.windowsMainService.ready(this.userEnv); - // Open our first window const macOpenFiles: string[] = (global).macOpenFiles; const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP; @@ -588,9 +573,9 @@ export class CodeApplication extends Disposable { const noRecentEntry = args['skip-add-to-recently-opened'] === true; const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined; + // new window if "-n" was used without paths if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { - // new window if "-n" was used without paths - return this.windowsMainService.open({ + return windowsMainService.open({ context, cli: args, forceNewWindow: true, @@ -601,12 +586,12 @@ export class CodeApplication extends Disposable { }); } + // mac: open-file event received on startup if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { - // mac: open-file event received on startup - return this.windowsMainService.open({ + return windowsMainService.open({ context: OpenContext.DOCK, cli: args, - urisToOpen: macOpenFiles.map(getURIToOpenFromPathSync), + urisToOpen: macOpenFiles.map(file => this.getURIToOpenFromPathSync(file)), noRecentEntry, waitMarkerFileURI, initialStartup: true @@ -614,7 +599,7 @@ export class CodeApplication extends Disposable { } // default: read paths from cli - return this.windowsMainService.open({ + return windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']), @@ -625,61 +610,30 @@ export class CodeApplication extends Disposable { }); } - private afterWindowOpen(accessor: ServicesAccessor): void { - const windowsMainService = accessor.get(IWindowsMainService); - const historyMainService = accessor.get(IHistoryMainService); - - if (isWindows) { - - // Setup Windows mutex - try { - const Mutex = (require.__$__nodeRequire('windows-mutex') as any).Mutex; - const windowsMutex = new Mutex(product.win32MutexName); - this._register(toDisposable(() => windowsMutex.release())); - } catch (e) { - if (!this.environmentService.isBuilt) { - windowsMainService.showMessageBox({ - title: product.nameLong, - type: 'warning', - message: 'Failed to load windows-mutex!', - detail: e.toString(), - noLink: true - }); - } + private getURIToOpenFromPathSync(path: string): IURIToOpen { + try { + const fileStat = statSync(path); + if (fileStat.isDirectory()) { + return { folderUri: URI.file(path) }; } - // Ensure Windows foreground love module - try { - // tslint:disable-next-line:no-unused-expression - require.__$__nodeRequire('windows-foreground-love'); - } catch (e) { - if (!this.environmentService.isBuilt) { - windowsMainService.showMessageBox({ - title: product.nameLong, - type: 'warning', - message: 'Failed to load windows-foreground-love!', - detail: e.toString(), - noLink: true - }); - } + if (hasWorkspaceFileExtension(path)) { + return { workspaceUri: URI.file(path) }; } + } catch (error) { + // ignore errors } - // Remote Authorities - this.handleRemoteAuthorities(); + return { fileUri: URI.file(path) }; + } - // Keyboard layout changes - KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => { - this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false); - }); + private afterWindowOpen(): void { - // Jump List - historyMainService.updateWindowsJumpList(); - historyMainService.onRecentlyOpenedChange(() => historyMainService.updateWindowsJumpList()); + // Signal phase: after window open + this.lifecycleService.phase = LifecycleMainPhase.AfterWindowOpen; - // Start shared process after a while - const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment(this.logService).then(userEnv => this.sharedProcess.spawn(userEnv)), 3000)); - sharedProcessSpawn.schedule(); + // Remote Authorities + this.handleRemoteAuthorities(); } private handleRemoteAuthorities(): void { @@ -694,8 +648,9 @@ export class CodeApplication extends Disposable { constructor(authority: string, host: string, port: number) { this._authority = authority; + const options: IConnectionOptions = { - isBuilt: isBuilt, + isBuilt, commit: product.commit, webSocketFactory: nodeWebSocketFactory, addressProvider: { @@ -704,6 +659,7 @@ export class CodeApplication extends Disposable { } } }; + this._connection = connectRemoteAgentManagement(options, authority, `main`); this._disposeRunner = new RunOnceScheduler(() => this.dispose(), 5000); } @@ -711,14 +667,13 @@ export class CodeApplication extends Disposable { dispose(): void { this._disposeRunner.dispose(); connectionPool.delete(this._authority); - this._connection.then((connection) => { - connection.dispose(); - }); + this._connection.then(connection => connection.dispose()); } async getClient(): Promise> { this._disposeRunner.schedule(); const connection = await this._connection; + return connection.client; } } @@ -726,7 +681,9 @@ export class CodeApplication extends Disposable { const resolvedAuthorities = new Map(); ipc.on('vscode:remoteAuthorityResolved', (event: Electron.Event, data: ResolvedAuthority) => { this.logService.info('Received resolved authority', data.authority); + resolvedAuthorities.set(data.authority, data); + // Make sure to close and remove any existing connections if (connectionPool.has(data.authority)) { connectionPool.get(data.authority)!.dispose(); @@ -735,15 +692,19 @@ export class CodeApplication extends Disposable { const resolveAuthority = (authority: string): ResolvedAuthority | null => { this.logService.info('Resolving authority', authority); + if (authority.indexOf('+') >= 0) { if (resolvedAuthorities.has(authority)) { return withUndefinedAsNull(resolvedAuthorities.get(authority)); } + this.logService.info('Didnot find resolved authority for', authority); + return null; } else { const [host, strPort] = authority.split(':'); const port = parseInt(strPort, 10); + return { authority, host, port }; } }; @@ -752,6 +713,7 @@ export class CodeApplication extends Disposable { if (request.method !== 'GET') { return callback(undefined); } + const uri = URI.parse(request.url); let activeConnection: ActiveConnection | undefined; @@ -763,9 +725,11 @@ export class CodeApplication extends Disposable { callback(undefined); return; } + activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port); connectionPool.set(uri.authority, activeConnection); } + try { const rawClient = await activeConnection!.getClient(); if (connectionPool.has(uri.authority)) { // not disposed in the meantime @@ -784,16 +748,3 @@ export class CodeApplication extends Disposable { }); } } - -function getURIToOpenFromPathSync(path: string): IURIToOpen { - try { - const fileStat = statSync(path); - if (fileStat.isDirectory()) { - return { folderUri: URI.file(path) }; - } else if (hasWorkspaceFileExtension(path)) { - return { workspaceUri: URI.file(path) }; - } - } catch (error) { - } - return { fileUri: URI.file(path) }; -} diff --git a/src/vs/code/electron-main/keyboard.ts b/src/vs/code/electron-main/keyboard.ts deleted file mode 100644 index aad1b04a88e9..000000000000 --- a/src/vs/code/electron-main/keyboard.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nativeKeymap from 'native-keymap'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { Emitter } from 'vs/base/common/event'; - -export class KeyboardLayoutMonitor { - - public static readonly INSTANCE = new KeyboardLayoutMonitor(); - - private readonly _emitter: Emitter; - private _registered: boolean; - - private constructor() { - this._emitter = new Emitter(); - this._registered = false; - } - - public onDidChangeKeyboardLayout(callback: () => void): IDisposable { - if (!this._registered) { - this._registered = true; - - nativeKeymap.onDidChangeKeyboardLayout(() => { - this._emitter.fire(); - }); - } - return this._emitter.event(callback); - } -} \ No newline at end of file diff --git a/src/vs/code/electron-main/logUploader.ts b/src/vs/code/electron-main/logUploader.ts index 811718d4ab93..5676a395c514 100644 --- a/src/vs/code/electron-main/logUploader.ts +++ b/src/vs/code/electron-main/logUploader.ts @@ -22,10 +22,10 @@ interface PostResult { class Endpoint { private constructor( - public readonly url: string + readonly url: string ) { } - public static getFromProduct(): Endpoint | undefined { + static getFromProduct(): Endpoint | undefined { const logUploaderUrl = product.logUploaderUrl; return logUploaderUrl ? new Endpoint(logUploaderUrl) : undefined; } diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 111fc412cdd0..2e86a42d1a72 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/code/code.main'; +import 'vs/platform/update/node/update.config.contribution'; import { app, dialog } from 'electron'; import { assign } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; @@ -32,76 +32,184 @@ import * as fs from 'fs'; import { CodeApplication } from 'vs/code/electron-main/app'; import { localize } from 'vs/nls'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; +import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { IDiagnosticsService, DiagnosticsService } from 'vs/platform/diagnostics/electron-main/diagnosticsService'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; import { uploadLogs } from 'vs/code/electron-main/logUploader'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; +import { Client } from 'vs/base/parts/ipc/common/ipc.net'; +import { once } from 'vs/base/common/functional'; class ExpectedError extends Error { readonly isExpected = true; } -function setupIPC(accessor: ServicesAccessor): Promise { - const logService = accessor.get(ILogService); - const environmentService = accessor.get(IEnvironmentService); - const instantiationService = accessor.get(IInstantiationService); +class CodeMain { - function allowSetForegroundWindow(service: LaunchChannelClient): Promise { - let promise: Promise = Promise.resolve(); - if (platform.isWindows) { - promise = service.getMainProcessId() - .then(processId => { - logService.trace('Sending some foreground love to the running instance:', processId); - - try { - const { allowSetForegroundWindow } = require.__$__nodeRequire('windows-foreground-love'); - allowSetForegroundWindow(processId); - } catch (e) { - // noop - } - }); + main(): void { + + // Set the error handler early enough so that we are not getting the + // default electron error dialog popping up + setUnexpectedErrorHandler(err => console.error(err)); + + // Parse arguments + let args: ParsedArgs; + try { + args = parseMainProcessArgv(process.argv); + args = validatePaths(args); + } catch (err) { + console.error(err.message); + app.exit(1); + + return; + } + + // If we are started with --wait create a random temporary file + // and pass it over to the starting instance. We can use this file + // to wait for it to be deleted to monitor that the edited file + // is closed and then exit the waiting process. + // + // Note: we are not doing this if the wait marker has been already + // added as argument. This can happen if Code was started from CLI. + if (args.wait && !args.waitMarkerFilePath) { + const waitMarkerFilePath = createWaitMarkerFile(args.verbose); + if (waitMarkerFilePath) { + addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath); + args.waitMarkerFilePath = waitMarkerFilePath; + } } - return promise; + // Launch + this.startup(args); } - function setup(retry: boolean): Promise { - return serve(environmentService.mainIPCHandle).then(server => { + private async startup(args: ParsedArgs): Promise { - // Print --status usage info - if (environmentService.args.status) { - logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.'); - throw new ExpectedError('Terminating...'); - } + // We need to buffer the spdlog logs until we are sure + // we are the only instance running, otherwise we'll have concurrent + // log file access on Windows (https://github.com/Microsoft/vscode/issues/41218) + const bufferLogService = new BufferLogService(); - // Log uploader usage info - if (typeof environmentService.args['upload-logs'] !== 'undefined') { - logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.'); - throw new ExpectedError('Terminating...'); - } + const [instantiationService, instanceEnvironment] = this.createServices(args, bufferLogService); + try { - // dock might be hidden at this case due to a retry - if (platform.isMacintosh) { - app.dock.show(); - } + // Init services + await instantiationService.invokeFunction(async accessor => { + const environmentService = accessor.get(IEnvironmentService); + const configurationService = accessor.get(IConfigurationService); + const stateService = accessor.get(IStateService); + + try { + await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService); + } catch (error) { + + // Show a dialog for errors that can be resolved by the user + this.handleStartupDataDirError(environmentService, error); + + throw error; + } + }); + + // Startup + await instantiationService.invokeFunction(async accessor => { + const environmentService = accessor.get(IEnvironmentService); + const logService = accessor.get(ILogService); + const lifecycleService = accessor.get(ILifecycleService); + const configurationService = accessor.get(IConfigurationService); + + const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleService, instantiationService, true); + + bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel()); + once(lifecycleService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose()); + + return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup(); + }); + } catch (error) { + instantiationService.invokeFunction(this.quit, error); + } + } + + private createServices(args: ParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, typeof process.env] { + const services = new ServiceCollection(); + + const environmentService = new EnvironmentService(args, process.execPath); + const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment + services.set(IEnvironmentService, environmentService); + + const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]); + process.once('exit', () => logService.dispose()); + services.set(ILogService, logService); + + services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource.path)); + services.set(ILifecycleService, new SyncDescriptor(LifecycleService)); + services.set(IStateService, new SyncDescriptor(StateService)); + services.set(IRequestService, new SyncDescriptor(RequestService)); + services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); + services.set(IThemeMainService, new SyncDescriptor(ThemeMainService)); + + return [new InstantiationService(services, true), instanceEnvironment]; + } - // Set the VSCODE_PID variable here when we are sure we are the first - // instance to startup. Otherwise we would wrongly overwrite the PID - process.env['VSCODE_PID'] = String(process.pid); + private initServices(environmentService: IEnvironmentService, configurationService: ConfigurationService, stateService: StateService): Promise { + + // Environment service (paths) + const environmentServiceInitialization = Promise.all([ + environmentService.extensionsPath, + environmentService.nodeCachedDataDir, + environmentService.logsPath, + environmentService.globalStorageHome, + environmentService.workspaceStorageHome, + environmentService.backupHome + ].map((path): undefined | Promise => path ? mkdirp(path) : undefined)); + + // Configuration service + const configurationServiceInitialization = configurationService.initialize(); + + // State service + const stateServiceInitialization = stateService.init(); + + return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]); + } + + private patchEnvironment(environmentService: IEnvironmentService): typeof process.env { + const instanceEnvironment: typeof process.env = { + VSCODE_IPC_HOOK: environmentService.mainIPCHandle, + VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'], + VSCODE_LOGS: process.env['VSCODE_LOGS'], + // {{SQL CARBON EDIT}} We keep VSCODE_LOGS to not break functionality for merged code + ADS_LOGS: process.env['ADS_LOGS'] + }; + + if (process.env['VSCODE_PORTABLE']) { + instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE']; + } - return server; - }, err => { + assign(process.env, instanceEnvironment); + + return instanceEnvironment; + } + + private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleService: ILifecycleService, instantiationService: IInstantiationService, retry: boolean): Promise { + + // Try to setup a server for running. If that succeeds it means + // we are the first instance to startup. Otherwise it is likely + // that another instance is already running. + let server: Server; + try { + server = await serve(environmentService.mainIPCHandle); + once(lifecycleService.onWillShutdown)(() => server.dispose()); + } catch (error) { // Handle unexpected errors (the only expected error is EADDRINUSE that // indicates a second instance of Code is running) - if (err.code !== 'EADDRINUSE') { + if (error.code !== 'EADDRINUSE') { // Show a dialog for errors that can be resolved by the user - handleStartupDataDirError(environmentService, err); + this.handleStartupDataDirError(environmentService, error); // Any other runtime error is just printed to the console - return Promise.reject(err); + throw error; } // Since we are the second instance, we do not want to show the dock @@ -110,263 +218,188 @@ function setupIPC(accessor: ServicesAccessor): Promise { } // there's a running instance, let's connect to it - return connect(environmentService.mainIPCHandle, 'main').then( - client => { - - // Tests from CLI require to be the only instance currently - if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) { - const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.'; - logService.error(msg); - client.dispose(); - - return Promise.reject(new Error(msg)); - } - - // Show a warning dialog after some timeout if it takes long to talk to the other instance - // Skip this if we are running with --wait where it is expected that we wait for a while. - // Also skip when gathering diagnostics (--status) which can take a longer time. - let startupWarningDialogHandle: NodeJS.Timeout; - if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) { - startupWarningDialogHandle = setTimeout(() => { - showStartupWarningDialog( - localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort), - localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.") - ); - }, 10000); - } - - const channel = client.getChannel('launch'); - const service = new LaunchChannelClient(channel); - - // Process Info - if (environmentService.args.status) { - return instantiationService.invokeFunction(accessor => { - return accessor.get(IDiagnosticsService).getDiagnostics(service).then(diagnostics => { - console.log(diagnostics); - return Promise.reject(new ExpectedError()); - }); - }); - } - - // Log uploader - if (typeof environmentService.args['upload-logs'] !== 'undefined') { - return instantiationService.invokeFunction(accessor => { - return uploadLogs(service, accessor.get(IRequestService), environmentService) - .then(() => Promise.reject(new ExpectedError())); - }); - } - - logService.trace('Sending env to running instance...'); - - return allowSetForegroundWindow(service) - .then(() => service.start(environmentService.args, process.env as platform.IProcessEnvironment)) - .then(() => client.dispose()) - .then(() => { - - // Now that we started, make sure the warning dialog is prevented - if (startupWarningDialogHandle) { - clearTimeout(startupWarningDialogHandle); - } - - return Promise.reject(new ExpectedError('Sent env to running instance. Terminating...')); - }); - }, - err => { - if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') { - if (err.code === 'EPERM') { - showStartupWarningDialog( - localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort), - localize('secondInstanceAdminDetail', "Please close the other instance and try again.") - ); - } - - return Promise.reject(err); - } - - // it happens on Linux and OS X that the pipe is left behind - // let's delete it, since we can't connect to it - // and then retry the whole thing - try { - fs.unlinkSync(environmentService.mainIPCHandle); - } catch (e) { - logService.warn('Could not delete obsolete instance handle', e); - return Promise.reject(e); + let client: Client; + try { + client = await connect(environmentService.mainIPCHandle, 'main'); + } catch (error) { + + // Handle unexpected connection errors by showing a dialog to the user + if (!retry || platform.isWindows || error.code !== 'ECONNREFUSED') { + if (error.code === 'EPERM') { + this.showStartupWarningDialog( + localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", product.nameShort), + localize('secondInstanceAdminDetail', "Please close the other instance and try again.") + ); } - return setup(false); + throw error; } - ); - }); - } - - return setup(true); -} -function showStartupWarningDialog(message: string, detail: string): void { - dialog.showMessageBox({ - title: product.nameLong, - type: 'warning', - buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], - message, - detail, - noLink: true - }); -} + // it happens on Linux and OS X that the pipe is left behind + // let's delete it, since we can't connect to it and then + // retry the whole thing + try { + fs.unlinkSync(environmentService.mainIPCHandle); + } catch (error) { + logService.warn('Could not delete obsolete instance handle', error); -function handleStartupDataDirError(environmentService: IEnvironmentService, error: NodeJS.ErrnoException): void { - if (error.code === 'EACCES' || error.code === 'EPERM') { - showStartupWarningDialog( - localize('startupDataDirError', "Unable to write program user data."), - localize('startupDataDirErrorDetail', "Please make sure the directories {0} and {1} are writeable.", environmentService.userDataPath, environmentService.extensionsPath) - ); - } -} + throw error; + } -function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void { - const logService = accessor.get(ILogService); - const lifecycleService = accessor.get(ILifecycleService); + return this.doStartup(logService, environmentService, lifecycleService, instantiationService, false); + } - let exitCode = 0; + // Tests from CLI require to be the only instance currently + if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) { + const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.'; + logService.error(msg); + client.dispose(); - if (reason) { - if ((reason as ExpectedError).isExpected) { - if (reason.message) { - logService.trace(reason.message); + throw new Error(msg); } - } else { - exitCode = 1; // signal error to the outside - if (reason.stack) { - logService.error(reason.stack); - } else { - logService.error(`Startup error: ${reason.toString()}`); + // Show a warning dialog after some timeout if it takes long to talk to the other instance + // Skip this if we are running with --wait where it is expected that we wait for a while. + // Also skip when gathering diagnostics (--status) which can take a longer time. + let startupWarningDialogHandle: NodeJS.Timeout | undefined = undefined; + if (!environmentService.wait && !environmentService.status && !environmentService.args['upload-logs']) { + startupWarningDialogHandle = setTimeout(() => { + this.showStartupWarningDialog( + localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", product.nameShort), + localize('secondInstanceNoResponseDetail', "Please close all other instances and try again.") + ); + }, 10000); } - } - } - lifecycleService.kill(exitCode); -} + const channel = client.getChannel('launch'); + const launchClient = new LaunchChannelClient(channel); -function patchEnvironment(environmentService: IEnvironmentService): typeof process.env { - const instanceEnvironment: typeof process.env = { - VSCODE_IPC_HOOK: environmentService.mainIPCHandle, - VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'], - VSCODE_LOGS: process.env['VSCODE_LOGS'], - // {{SQL CARBON EDIT}} We keep VSCODE_LOGS to not break functionality for merged code - ADS_LOGS: process.env['ADS_LOGS'] - }; - - if (process.env['VSCODE_PORTABLE']) { - instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE']; - } + // Process Info + if (environmentService.args.status) { + return instantiationService.invokeFunction(async accessor => { + const diagnostics = await accessor.get(IDiagnosticsService).getDiagnostics(launchClient); + console.log(diagnostics); - assign(process.env, instanceEnvironment); + throw new ExpectedError(); + }); + } - return instanceEnvironment; -} + // Log uploader + if (typeof environmentService.args['upload-logs'] !== 'undefined') { + return instantiationService.invokeFunction(async accessor => { + await uploadLogs(launchClient, accessor.get(IRequestService), environmentService); -function startup(args: ParsedArgs): void { + throw new ExpectedError(); + }); + } - // We need to buffer the spdlog logs until we are sure - // we are the only instance running, otherwise we'll have concurrent - // log file access on Windows (https://github.com/Microsoft/vscode/issues/41218) - const bufferLogService = new BufferLogService(); - const instantiationService = createServices(args, bufferLogService); - instantiationService.invokeFunction(accessor => { - const environmentService = accessor.get(IEnvironmentService); - const stateService = accessor.get(IStateService); + // Windows: allow to set foreground + if (platform.isWindows) { + await this.windowsAllowSetForegroundWindow(launchClient, logService); + } - // Patch `process.env` with the instance's environment - const instanceEnvironment = patchEnvironment(environmentService); + // Send environment over... + logService.trace('Sending env to running instance...'); + await launchClient.start(environmentService.args, process.env as platform.IProcessEnvironment); - // Startup - return initServices(environmentService, stateService as StateService) - .then(() => instantiationService.invokeFunction(setupIPC), error => { + // Cleanup + await client.dispose(); - // Show a dialog for errors that can be resolved by the user - handleStartupDataDirError(environmentService, error); + // Now that we started, make sure the warning dialog is prevented + if (startupWarningDialogHandle) { + clearTimeout(startupWarningDialogHandle); + } - return Promise.reject(error); - }) - .then(mainIpcServer => { - createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath).then(logger => bufferLogService.logger = logger); + throw new ExpectedError('Sent env to running instance. Terminating...'); + } - return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup(); - }); - }).then(null, err => instantiationService.invokeFunction(quit, err)); -} + // Print --status usage info + if (environmentService.args.status) { + logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.'); -function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService { - const services = new ServiceCollection(); + throw new ExpectedError('Terminating...'); + } - const environmentService = new EnvironmentService(args, process.execPath); + // Log uploader usage info + if (typeof environmentService.args['upload-logs'] !== 'undefined') { + logService.warn('Warning: The --upload-logs argument can only be used if Code is already running. Please run it again after Code has started.'); - const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]); - process.once('exit', () => logService.dispose()); + throw new ExpectedError('Terminating...'); + } - services.set(IEnvironmentService, environmentService); - services.set(ILogService, logService); - services.set(ILifecycleService, new SyncDescriptor(LifecycleService)); - services.set(IStateService, new SyncDescriptor(StateService)); - services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath])); - services.set(IRequestService, new SyncDescriptor(RequestService)); - services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); + // dock might be hidden at this case due to a retry + if (platform.isMacintosh) { + app.dock.show(); + } - return new InstantiationService(services, true); -} + // Set the VSCODE_PID variable here when we are sure we are the first + // instance to startup. Otherwise we would wrongly overwrite the PID + process.env['VSCODE_PID'] = String(process.pid); -function initServices(environmentService: IEnvironmentService, stateService: StateService): Promise { + return server; + } - // Ensure paths for environment service exist - const environmentServiceInitialization = Promise.all([ - environmentService.extensionsPath, - environmentService.nodeCachedDataDir, - environmentService.logsPath, - environmentService.globalStorageHome, - environmentService.workspaceStorageHome, - environmentService.backupHome - ].map((path): undefined | Promise => path ? mkdirp(path) : undefined)); + private handleStartupDataDirError(environmentService: IEnvironmentService, error: NodeJS.ErrnoException): void { + if (error.code === 'EACCES' || error.code === 'EPERM') { + this.showStartupWarningDialog( + localize('startupDataDirError', "Unable to write program user data."), + localize('startupDataDirErrorDetail', "Please make sure the directories {0} and {1} are writeable.", environmentService.userDataPath, environmentService.extensionsPath) + ); + } + } - // State service - const stateServiceInitialization = stateService.init(); + private showStartupWarningDialog(message: string, detail: string): void { + dialog.showMessageBox({ + title: product.nameLong, + type: 'warning', + buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], + message, + detail, + noLink: true + }); + } - return Promise.all([environmentServiceInitialization, stateServiceInitialization]); -} + private async windowsAllowSetForegroundWindow(client: LaunchChannelClient, logService: ILogService): Promise { + if (platform.isWindows) { + const processId = await client.getMainProcessId(); -function main(): void { + logService.trace('Sending some foreground love to the running instance:', processId); - // Set the error handler early enough so that we are not getting the - // default electron error dialog popping up - setUnexpectedErrorHandler(err => console.error(err)); + try { + (await import('windows-foreground-love')).allowSetForegroundWindow(processId); + } catch (error) { + logService.error(error); + } + } + } - // Parse arguments - let args: ParsedArgs; - try { - args = parseMainProcessArgv(process.argv); - args = validatePaths(args); - } catch (err) { - console.error(err.message); - app.exit(1); + private quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void { + const logService = accessor.get(ILogService); + const lifecycleService = accessor.get(ILifecycleService); - return undefined; - } + let exitCode = 0; - // If we are started with --wait create a random temporary file - // and pass it over to the starting instance. We can use this file - // to wait for it to be deleted to monitor that the edited file - // is closed and then exit the waiting process. - // - // Note: we are not doing this if the wait marker has been already - // added as argument. This can happen if Code was started from CLI. - if (args.wait && !args.waitMarkerFilePath) { - const waitMarkerFilePath = createWaitMarkerFile(args.verbose); - if (waitMarkerFilePath) { - addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath); - args.waitMarkerFilePath = waitMarkerFilePath; + if (reason) { + if ((reason as ExpectedError).isExpected) { + if (reason.message) { + logService.trace(reason.message); + } + } else { + exitCode = 1; // signal error to the outside + + if (reason.stack) { + logService.error(reason.stack); + } else { + logService.error(`Startup error: ${reason.toString()}`); + } + } } + + lifecycleService.kill(exitCode); } - startup(args); } -main(); +// Main Startup +const code = new CodeMain(); +code.main(); \ No newline at end of file diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index af04e4b51914..7f340f2f2fbc 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -11,8 +11,7 @@ import { ISharedProcess } from 'vs/platform/windows/electron-main/windows'; import { Barrier } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; -import { IStateService } from 'vs/platform/state/common/state'; -import { getBackgroundColor } from 'vs/code/electron-main/theme'; +import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { dispose, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; export class SharedProcess implements ISharedProcess { @@ -26,15 +25,15 @@ export class SharedProcess implements ISharedProcess { private userEnv: NodeJS.ProcessEnv, @IEnvironmentService private readonly environmentService: IEnvironmentService, @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IStateService private readonly stateService: IStateService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IThemeMainService private readonly themeMainService: IThemeMainService ) { } @memoize private get _whenReady(): Promise { this.window = new BrowserWindow({ show: false, - backgroundColor: getBackgroundColor(this.stateService), + backgroundColor: this.themeMainService.getBackgroundColor(), webPreferences: { images: false, webaudio: false, diff --git a/src/vs/code/electron-main/theme.ts b/src/vs/code/electron-main/theme.ts deleted file mode 100644 index 4710c45392b3..000000000000 --- a/src/vs/code/electron-main/theme.ts +++ /dev/null @@ -1,44 +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 { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { systemPreferences } from 'electron'; -import { IStateService } from 'vs/platform/state/common/state'; - -const DEFAULT_BG_LIGHT = '#FFFFFF'; -const DEFAULT_BG_DARK = '#1E1E1E'; -const DEFAULT_BG_HC_BLACK = '#000000'; - -const THEME_STORAGE_KEY = 'theme'; -const THEME_BG_STORAGE_KEY = 'themeBackground'; - -export function storeBackgroundColor(stateService: IStateService, data: { baseTheme: string, background: string }): void { - stateService.setItem(THEME_STORAGE_KEY, data.baseTheme); - stateService.setItem(THEME_BG_STORAGE_KEY, data.background); -} - -export function getBackgroundColor(stateService: IStateService): string { - if (isWindows && systemPreferences.isInvertedColorScheme()) { - return DEFAULT_BG_HC_BLACK; - } - - let background = stateService.getItem(THEME_BG_STORAGE_KEY, null); - if (!background) { - let baseTheme: string; - if (isWindows && systemPreferences.isInvertedColorScheme()) { - baseTheme = 'hc-black'; - } else { - baseTheme = stateService.getItem(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0]; - } - - background = (baseTheme === 'hc-black') ? DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? DEFAULT_BG_LIGHT : DEFAULT_BG_DARK); - } - - if (isMacintosh && background.toUpperCase() === DEFAULT_BG_DARK) { - background = '#171717'; // https://github.com/electron/electron/issues/5150 - } - - return background; -} \ No newline at end of file diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 99d30e973cc0..12e78be10ef3 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -7,7 +7,6 @@ import * as path from 'vs/base/common/path'; import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { IStateService } from 'vs/platform/state/common/state'; import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display } from 'electron'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; @@ -23,9 +22,9 @@ import { IBackupMainService } from 'vs/platform/backup/common/backup'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import * as perf from 'vs/base/common/performance'; import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService'; -import { getBackgroundColor } from 'vs/code/electron-main/theme'; -import { RunOnceScheduler } from 'vs/base/common/async'; +import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { endsWith } from 'vs/base/common/strings'; +import { RunOnceScheduler } from 'vs/base/common/async'; export interface IWindowCreationOptions { state: IWindowState; @@ -84,7 +83,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IStateService private readonly stateService: IStateService, + @IThemeMainService private readonly themeMainService: IThemeMainService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, ) { @@ -124,7 +123,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { height: this.windowState.height, x: this.windowState.x, y: this.windowState.y, - backgroundColor: getBackgroundColor(this.stateService), + backgroundColor: this.themeMainService.getBackgroundColor(), minWidth: CodeWindow.MIN_WIDTH, minHeight: CodeWindow.MIN_HEIGHT, show: !isFullscreenOrMaximized, @@ -204,12 +203,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { return !!this.config.extensionTestsPath; } - /* - get extensionDevelopmentPaths(): string | string[] | undefined { - return this.config.extensionDevelopmentPath; - } - */ - get config(): IWindowConfiguration { return this.currentConfig; } diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 9159e6e935a0..15cb7748cf5f 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -15,7 +15,7 @@ import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; import { hasArgs, asArray } from 'vs/platform/environment/node/argv'; import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter } from 'electron'; import { parseLineAndColumnAware } from 'vs/code/node/paths'; -import { ILifecycleService, UnloadReason, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleService, UnloadReason, LifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, isFileToOpen, isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows'; @@ -38,6 +38,8 @@ import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; import { getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { once } from 'vs/base/common/functional'; +import { Disposable } from 'vs/base/common/lifecycle'; const enum WindowError { UNRESPONSIVE = 1, @@ -153,15 +155,13 @@ interface IWorkspacePathToOpen { label?: string; } -export class WindowsManager implements IWindowsMainService { +export class WindowsManager extends Disposable implements IWindowsMainService { _serviceBrand: any; private static readonly windowsStateStorageKey = 'windowsState'; - private static WINDOWS: ICodeWindow[] = []; - - private initialUserEnv: IProcessEnvironment; + private static readonly WINDOWS: ICodeWindow[] = []; private readonly windowsState: IWindowsState; private lastClosedWindowState?: IWindowState; @@ -169,20 +169,21 @@ export class WindowsManager implements IWindowsMainService { private readonly dialogs: Dialogs; private readonly workspacesManager: WorkspacesManager; - private _onWindowReady = new Emitter(); - onWindowReady: CommonEvent = this._onWindowReady.event; + private readonly _onWindowReady = this._register(new Emitter()); + readonly onWindowReady: CommonEvent = this._onWindowReady.event; - private _onWindowClose = new Emitter(); - onWindowClose: CommonEvent = this._onWindowClose.event; + private readonly _onWindowClose = this._register(new Emitter()); + readonly onWindowClose: CommonEvent = this._onWindowClose.event; - private _onWindowLoad = new Emitter(); - onWindowLoad: CommonEvent = this._onWindowLoad.event; + private readonly _onWindowLoad = this._register(new Emitter()); + readonly onWindowLoad: CommonEvent = this._onWindowLoad.event; - private _onWindowsCountChanged = new Emitter(); - onWindowsCountChanged: CommonEvent = this._onWindowsCountChanged.event; + private readonly _onWindowsCountChanged = this._register(new Emitter()); + readonly onWindowsCountChanged: CommonEvent = this._onWindowsCountChanged.event; constructor( private readonly machineId: string, + private readonly initialUserEnv: IProcessEnvironment, @ILogService private readonly logService: ILogService, @IStateService private readonly stateService: IStateService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @@ -194,6 +195,7 @@ export class WindowsManager implements IWindowsMainService { @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { + super(); const windowsStateStoreData = this.stateService.getItem(WindowsManager.windowsStateStorageKey); this.windowsState = restoreWindowsState(windowsStateStoreData); @@ -203,12 +205,50 @@ export class WindowsManager implements IWindowsMainService { this.dialogs = new Dialogs(stateService, this); this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this); + + this.lifecycleService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); + this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.setupNativeHelpers()); } - ready(initialUserEnv: IProcessEnvironment): void { - this.initialUserEnv = initialUserEnv; + private setupNativeHelpers(): void { + if (isWindows) { - this.registerListeners(); + // Setup Windows mutex + try { + const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex; + const mutex = new WindowsMutex(product.win32MutexName); + once(this.lifecycleService.onWillShutdown)(() => mutex.release()); + } catch (e) { + this.logService.error(e); + + if (!this.environmentService.isBuilt) { + this.showMessageBox({ + title: product.nameLong, + type: 'warning', + message: 'Failed to load windows-mutex!', + detail: e.toString(), + noLink: true + }); + } + } + + // Dev only: Ensure Windows foreground love module is present + if (!this.environmentService.isBuilt) { + try { + require.__$__nodeRequire('windows-foreground-love'); + } catch (e) { + this.logService.error(e); + + this.showMessageBox({ + title: product.nameLong, + type: 'warning', + message: 'Failed to load windows-foreground-love!', + detail: e.toString(), + noLink: true + }); + } + } + } } private registerListeners(): void { @@ -543,6 +583,7 @@ export class WindowsManager implements IWindowsMainService { // Find suitable window or folder path to open files in const fileToCheck = fileInputs.filesToOpenOrCreate[0] || fileInputs.filesToDiff[0]; + // only look at the windows with correct authority const windows = WindowsManager.WINDOWS.filter(w => w.remoteAuthority === fileInputs!.remoteAuthority); @@ -639,7 +680,6 @@ export class WindowsManager implements IWindowsMainService { // Handle folders to open (instructed and to restore) const allFoldersToOpen = arrays.distinct(foldersToOpen, folder => getComparisonKey(folder.folderUri)); // prevent duplicates - if (allFoldersToOpen.length > 0) { // Check for existing instances @@ -713,7 +753,9 @@ export class WindowsManager implements IWindowsMainService { if (fileInputs && !emptyToOpen) { emptyToOpen++; } + const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined); + for (let i = 0; i < emptyToOpen; i++) { usedWindows.push(this.openInBrowserWindow({ userEnv: openConfig.userEnv, @@ -1536,14 +1578,13 @@ export class WindowsManager implements IWindowsMainService { return state; } - reload(win: ICodeWindow, cli?: ParsedArgs): void { + async reload(win: ICodeWindow, cli?: ParsedArgs): Promise { // Only reload when the window has not vetoed this - this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => { - if (!veto) { - win.reload(undefined, cli); - } - }); + const veto = await this.lifecycleService.unload(win, UnloadReason.RELOAD); + if (!veto) { + win.reload(undefined, cli); + } } closeWorkspace(win: ICodeWindow): void { @@ -1554,8 +1595,10 @@ export class WindowsManager implements IWindowsMainService { }); } - enterWorkspace(win: ICodeWindow, path: URI): Promise { - return this.workspacesManager.enterWorkspace(win, path).then(result => result ? this.doEnterWorkspace(win, result) : undefined); + async enterWorkspace(win: ICodeWindow, path: URI): Promise { + const result = await this.workspacesManager.enterWorkspace(win, path); + + return result ? this.doEnterWorkspace(win, result) : undefined; } private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult { @@ -1751,8 +1794,10 @@ export class WindowsManager implements IWindowsMainService { const paths = await this.dialogs.pick({ ...options, pickFolders: true, pickFiles: true, title }); if (paths) { this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFileFolder', options.telemetryExtraData); - const urisToOpen = await Promise.all(paths.map(path => { - return dirExists(path).then(isDir => isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) }); + const urisToOpen = await Promise.all(paths.map(async path => { + const isDir = await dirExists(path); + + return isDir ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) }; })); this.open({ context: OpenContext.DIALOG, @@ -1880,7 +1925,7 @@ class Dialogs { this.noWindowDialogQueue = new Queue(); } - pick(options: IInternalNativeOpenDialogOptions): Promise { + async pick(options: IInternalNativeOpenDialogOptions): Promise { // Ensure dialog options const dialogOptions: Electron.OpenDialogOptions = { @@ -1913,16 +1958,16 @@ class Dialogs { // Show Dialog const focusedWindow = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow(); - return this.showOpenDialog(dialogOptions, focusedWindow).then(paths => { - if (paths && paths.length > 0) { + const paths = await this.showOpenDialog(dialogOptions, focusedWindow); + if (paths && paths.length > 0) { - // Remember path in storage for next time - this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0])); - return paths; - } + // Remember path in storage for next time + this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0])); - return undefined; - }); + return paths; + } + + return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check } private getDialogQueue(window?: ICodeWindow): Queue { @@ -2028,28 +2073,26 @@ class WorkspacesManager { private readonly windowsMainService: IWindowsMainService, ) { } - enterWorkspace(window: ICodeWindow, path: URI): Promise { + async enterWorkspace(window: ICodeWindow, path: URI): Promise { if (!window || !window.win || !window.isReady) { - return Promise.resolve(null); // return early if the window is not ready or disposed + return null; // return early if the window is not ready or disposed } - return this.isValidTargetWorkspacePath(window, path).then(isValid => { - if (!isValid) { - return null; // return early if the workspace is not valid - } - const workspaceIdentifier = getWorkspaceIdentifier(path); - return this.doOpenWorkspace(window, workspaceIdentifier); - }); + const isValid = await this.isValidTargetWorkspacePath(window, path); + if (!isValid) { + return null; // return early if the workspace is not valid + } + return this.doOpenWorkspace(window, getWorkspaceIdentifier(path)); } - private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise { + private async isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise { if (!path) { - return Promise.resolve(true); + return true; } if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) { - return Promise.resolve(false); // window is already opened on a workspace with that path + return false; // window is already opened on a workspace with that path } // Prevent overwriting a workspace that is currently opened in another window @@ -2063,10 +2106,12 @@ class WorkspacesManager { noLink: true }; - return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false); + await this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()); + + return false; } - return Promise.resolve(true); // OK + return true; // OK } private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult { @@ -2090,14 +2135,16 @@ class WorkspacesManager { return { workspace, backupPath }; } - } -function resourceFromURIToOpen(u: IURIToOpen) { +function resourceFromURIToOpen(u: IURIToOpen): URI { if (isWorkspaceToOpen(u)) { return u.workspaceUri; - } else if (isFolderToOpen(u)) { + } + + if (isFolderToOpen(u)) { return u.folderUri; } + return u.fileUri; } \ No newline at end of file diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index bf2c6afc3254..805e3f3ce60c 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -57,6 +57,7 @@ export async function main(argv: string[]): Promise { else if (shouldSpawnCliProcess(args)) { const cli = await new Promise((c, e) => require(['vs/code/node/cliProcessMain'], c, e)); await cli.main(args); + return; } @@ -257,7 +258,7 @@ export async function main(argv: string[]): Promise { addArg(argv, `--prof-startup-prefix`, filenamePrefix); addArg(argv, `--no-cached-data`); - fs.writeFileSync(filenamePrefix, argv.slice(-6).join('|')); + writeFileSync(filenamePrefix, argv.slice(-6).join('|')); processCallbacks.push(async _child => { @@ -329,7 +330,7 @@ export async function main(argv: string[]): Promise { await extHost.stop(); // re-create the marker file to signal that profiling is done - fs.writeFileSync(filenamePrefix, ''); + writeFileSync(filenamePrefix, ''); } catch (e) { console.error('Failed to profile startup. Make sure to quit Code first.'); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 04c6304e0e3b..d4ee61170841 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -31,7 +31,6 @@ import { mkdirp, writeFile } from 'vs/base/node/pfs'; import { getBaseLabel } from 'vs/base/common/labels'; import { IStateService } from 'vs/platform/state/common/state'; import { StateService } from 'vs/platform/state/node/stateService'; -import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -41,10 +40,11 @@ import { IExtensionManifest, ExtensionType, isLanguagePackExtension } from 'vs/p import { CancellationToken } from 'vs/base/common/cancellation'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { Schemas } from 'vs/base/common/network'; +import { SpdLogService } from 'vs/platform/log/node/spdlogService'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id); -const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, eg: {0}", 'ms-vscode.csharp'); +const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-vscode.csharp'); function getId(manifest: IExtensionManifest, withVersion?: boolean): string { if (withVersion) { @@ -275,17 +275,22 @@ export class Main { const eventPrefix = 'monacoworkbench'; -export function main(argv: ParsedArgs): Promise { +export async function main(argv: ParsedArgs): Promise { const services = new ServiceCollection(); const environmentService = new EnvironmentService(argv, process.execPath); - const logService = createBufferSpdLogService('cli', getLogLevel(environmentService), environmentService.logsPath); + const logService: ILogService = new SpdLogService('cli', environmentService.logsPath, getLogLevel(environmentService)); process.once('exit', () => logService.dispose()); - logService.info('main', argv); + await Promise.all([environmentService.appSettingsHome, environmentService.extensionsPath].map(p => mkdirp(p))); + + const configurationService = new ConfigurationService(environmentService.settingsResource.path); + await configurationService.initialize(); + services.set(IEnvironmentService, environmentService); services.set(ILogService, logService); + services.set(IConfigurationService, configurationService); services.set(IStateService, new SyncDescriptor(StateService)); const instantiationService: IInstantiationService = new InstantiationService(services); @@ -294,40 +299,37 @@ export function main(argv: ParsedArgs): Promise { const envService = accessor.get(IEnvironmentService); const stateService = accessor.get(IStateService); - return Promise.all([envService.appSettingsHome, envService.extensionsPath].map(p => mkdirp(p))).then(() => { - const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService; + const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = envService; - const services = new ServiceCollection(); - services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.appSettingsPath])); - services.set(IRequestService, new SyncDescriptor(RequestService)); - services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); - services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); + const services = new ServiceCollection(); + services.set(IRequestService, new SyncDescriptor(RequestService)); + services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); + services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); - const appenders: AppInsightsAppender[] = []; - if (isBuilt && !extensionDevelopmentLocationURI && !envService.args['disable-telemetry'] && product.enableTelemetry) { + const appenders: AppInsightsAppender[] = []; + if (isBuilt && !extensionDevelopmentLocationURI && !envService.args['disable-telemetry'] && product.enableTelemetry) { - if (product.aiConfig && product.aiConfig.asimovKey) { - appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, logService)); - } + if (product.aiConfig && product.aiConfig.asimovKey) { + appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey, logService)); + } - const config: ITelemetryServiceConfig = { - appender: combinedAppender(...appenders), - commonProperties: resolveCommonProperties(product.commit, pkg.version, stateService.getItem('telemetry.machineId'), installSourcePath), - piiPaths: [appRoot, extensionsPath] - }; + const config: ITelemetryServiceConfig = { + appender: combinedAppender(...appenders), + commonProperties: resolveCommonProperties(product.commit, pkg.version, stateService.getItem('telemetry.machineId'), installSourcePath), + piiPaths: [appRoot, extensionsPath] + }; - services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); - } else { - services.set(ITelemetryService, NullTelemetryService); - } + services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); + } else { + services.set(ITelemetryService, NullTelemetryService); + } - const instantiationService2 = instantiationService.createChild(services); - const main = instantiationService2.createInstance(Main); + const instantiationService2 = instantiationService.createChild(services); + const main = instantiationService2.createInstance(Main); - return main.run(argv).then(() => { - // Dispose the AI adapter so that remaining data gets flushed. - return combinedAppender(...appenders).dispose(); - }); + return main.run(argv).then(() => { + // Dispose the AI adapter so that remaining data gets flushed. + return combinedAppender(...appenders).dispose(); }); }); } \ No newline at end of file diff --git a/src/vs/code/node/shellEnv.ts b/src/vs/code/node/shellEnv.ts index 80ab30eb961a..c8eec0c3b4fb 100644 --- a/src/vs/code/node/shellEnv.ts +++ b/src/vs/code/node/shellEnv.ts @@ -8,6 +8,7 @@ import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; import { isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; function getUnixShellEnvironment(logService: ILogService): Promise { const promise = new Promise((resolve, reject) => { @@ -89,9 +90,12 @@ let _shellEnv: Promise; * This should only be done when Code itself is not launched * from within a shell. */ -export function getShellEnvironment(logService: ILogService): Promise { +export function getShellEnvironment(logService: ILogService, environmentService: IEnvironmentService): Promise { if (_shellEnv === undefined) { - if (isWindows) { + if (environmentService.args['disable-user-env-probe']) { + logService.trace('getShellEnvironment: disable-user-env-probe set, skipping'); + _shellEnv = Promise.resolve({}); + } else if (isWindows) { logService.trace('getShellEnvironment: runing on windows, skipping'); _shellEnv = Promise.resolve({}); } else if (process.env['VSCODE_CLI'] === '1') { diff --git a/src/vs/editor/browser/core/editorState.ts b/src/vs/editor/browser/core/editorState.ts index 923566afae1f..f15229fb1eaf 100644 --- a/src/vs/editor/browser/core/editorState.ts +++ b/src/vs/editor/browser/core/editorState.ts @@ -79,7 +79,7 @@ export class EditorState { * A cancellation token source that cancels when the editor changes as expressed * by the provided flags */ -export class EditorStateCancellationTokenSource extends EditorKeybindingCancellationTokenSource { +export class EditorStateCancellationTokenSource extends EditorKeybindingCancellationTokenSource implements IDisposable { private readonly _listener: IDisposable[] = []; @@ -110,7 +110,7 @@ export class EditorStateCancellationTokenSource extends EditorKeybindingCancella /** * A cancellation token source that cancels when the provided model changes */ -export class TextModelCancellationTokenSource extends CancellationTokenSource { +export class TextModelCancellationTokenSource extends CancellationTokenSource implements IDisposable { private _listener: IDisposable; diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 32bc7cff951c..f223361745e5 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -132,7 +132,7 @@ export class View extends ViewEventHandler { this._setLayout(); // Pointer handler - this.pointerHandler = new PointerHandler(this._context, viewController, this.createPointerHandlerHelper()); + this.pointerHandler = this._register(new PointerHandler(this._context, viewController, this.createPointerHandlerHelper())); this._register(model.addEventListener((events: viewEvents.ViewEvent[]) => { this.eventDispatcher.emitMany(events); @@ -342,8 +342,6 @@ export class View extends ViewEventHandler { this.eventDispatcher.removeEventHandler(this); this.outgoingEvents.dispose(); - this.pointerHandler.dispose(); - this.viewLines.dispose(); // Destroy view parts diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 98158ce394d3..5b1070b1207f 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -257,7 +257,12 @@ class MinimapLayout { const computedSliderRatio = (maxMinimapSliderTop) / (scrollHeight - viewportHeight); const sliderTop = (scrollTop * computedSliderRatio); - if (minimapLinesFitting >= lineCount) { + let extraLinesAtTheBottom = 0; + if (options.scrollBeyondLastLine) { + const expectedViewportLineCount = viewportHeight / lineHeight; + extraLinesAtTheBottom = expectedViewportLineCount; + } + if (minimapLinesFitting >= lineCount + extraLinesAtTheBottom) { // All lines fit in the minimap const startLineNumber = 1; const endLineNumber = lineCount; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index f00b97775d66..3e5f73e5110b 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -38,7 +38,8 @@ import { ClassName } from 'vs/editor/common/model/intervalTree'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import * as modes from 'vs/editor/common/modes'; -import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorUnnecessaryCodeBorder, editorUnnecessaryCodeOpacity, editorWarningBorder, editorWarningForeground } from 'vs/editor/common/view/editorColorRegistry'; +import { editorUnnecessaryCodeBorder, editorUnnecessaryCodeOpacity } from 'vs/editor/common/view/editorColorRegistry'; +import { editorErrorBorder, editorErrorForeground, editorHintBorder, editorHintForeground, editorInfoBorder, editorInfoForeground, editorWarningBorder, editorWarningForeground } from 'vs/platform/theme/common/colorRegistry'; import { VerticalRevealType } from 'vs/editor/common/view/viewEvents'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; diff --git a/src/vs/editor/browser/widget/diffNavigator.ts b/src/vs/editor/browser/widget/diffNavigator.ts index b08089de4c8b..5e956dba0f3c 100644 --- a/src/vs/editor/browser/widget/diffNavigator.ts +++ b/src/vs/editor/browser/widget/diffNavigator.ts @@ -5,7 +5,7 @@ import * as assert from 'vs/base/common/assert'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; @@ -33,12 +33,11 @@ const defaultOptions: Options = { /** * Create a new diff navigator for the provided diff editor. */ -export class DiffNavigator { +export class DiffNavigator extends Disposable { private readonly _editor: IDiffEditor; private readonly _options: Options; - private readonly _disposables: IDisposable[]; - private readonly _onDidUpdate = new Emitter(); + private readonly _onDidUpdate = this._register(new Emitter()); readonly onDidUpdate: Event = this._onDidUpdate.event; @@ -49,11 +48,11 @@ export class DiffNavigator { private ignoreSelectionChange: boolean; constructor(editor: IDiffEditor, options: Options = {}) { + super(); this._editor = editor; this._options = objects.mixin(options, defaultOptions, false); this.disposed = false; - this._disposables = []; this.nextIdx = -1; this.ranges = []; @@ -61,11 +60,11 @@ export class DiffNavigator { this.revealFirst = Boolean(this._options.alwaysRevealFirst); // hook up to diff editor for diff, disposal, and caret move - this._disposables.push(this._editor.onDidDispose(() => this.dispose())); - this._disposables.push(this._editor.onDidUpdateDiff(() => this._onDiffUpdated())); + this._register(this._editor.onDidDispose(() => this.dispose())); + this._register(this._editor.onDidUpdateDiff(() => this._onDiffUpdated())); if (this._options.followsCaret) { - this._disposables.push(this._editor.getModifiedEditor().onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { + this._register(this._editor.getModifiedEditor().onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { if (this.ignoreSelectionChange) { return; } @@ -73,7 +72,7 @@ export class DiffNavigator { })); } if (this._options.alwaysRevealFirst) { - this._disposables.push(this._editor.getModifiedEditor().onDidChangeModel((e) => { + this._register(this._editor.getModifiedEditor().onDidChangeModel((e) => { this.revealFirst = true; })); } @@ -216,9 +215,7 @@ export class DiffNavigator { } dispose(): void { - dispose(this._disposables); - this._disposables.length = 0; - this._onDidUpdate.dispose(); + super.dispose(); this.ranges = []; this.disposed = true; } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 664f83d28134..8a561edb9379 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -839,7 +839,7 @@ const editorConfiguration: IConfigurationNode = { enumDescriptions: [ nls.localize('editor.gotoLocation.multiple.peek', 'Show peek view of the results (default)'), nls.localize('editor.gotoLocation.multiple.gotoAndPeek', 'Go to the primary result and show a peek view'), - nls.localize('editor.gotoLocation.multiple.goto', 'Go to the primary result and ignore others') + nls.localize('editor.gotoLocation.multiple.goto', 'Go to the primary result and enable peek-less navigation to others') ] }, 'editor.selectionHighlight': { diff --git a/src/vs/editor/common/core/uint.ts b/src/vs/editor/common/core/uint.ts index a91e40a647eb..4532f4a22f80 100644 --- a/src/vs/editor/common/core/uint.ts +++ b/src/vs/editor/common/core/uint.ts @@ -10,7 +10,7 @@ export class Uint8Matrix { public readonly cols: number; constructor(rows: number, cols: number, defaultValue: number) { - let data = new Uint8Array(rows * cols); + const data = new Uint8Array(rows * cols); for (let i = 0, len = rows * cols; i < len; i++) { data[i] = defaultValue; } @@ -85,8 +85,8 @@ export function toUint32(v: number): number { } export function toUint32Array(arr: number[]): Uint32Array { - let len = arr.length; - let r = new Uint32Array(len); + const len = arr.length; + const r = new Uint32Array(len); for (let i = 0; i < len; i++) { r[i] = toUint32(arr[i]); } diff --git a/src/vs/editor/common/model/indentationGuesser.ts b/src/vs/editor/common/model/indentationGuesser.ts index e41a7d0bd2a0..884d657d700c 100644 --- a/src/vs/editor/common/model/indentationGuesser.ts +++ b/src/vs/editor/common/model/indentationGuesser.ts @@ -177,22 +177,26 @@ export function guessIndentation(source: ITextBuffer, defaultTabSize: number, de } let tabSize = defaultTabSize; - let tabSizeScore = (insertSpaces ? 0 : 0.1 * linesCount); - // console.log("score threshold: " + tabSizeScore); + // Guess tabSize only if inserting spaces... + if (insertSpaces) { + let tabSizeScore = (insertSpaces ? 0 : 0.1 * linesCount); - ALLOWED_TAB_SIZE_GUESSES.forEach((possibleTabSize) => { - let possibleTabSizeScore = spacesDiffCount[possibleTabSize]; - if (possibleTabSizeScore > tabSizeScore) { - tabSizeScore = possibleTabSizeScore; - tabSize = possibleTabSize; - } - }); + // console.log("score threshold: " + tabSizeScore); + + ALLOWED_TAB_SIZE_GUESSES.forEach((possibleTabSize) => { + let possibleTabSizeScore = spacesDiffCount[possibleTabSize]; + if (possibleTabSizeScore > tabSizeScore) { + tabSizeScore = possibleTabSizeScore; + tabSize = possibleTabSize; + } + }); - // Let a tabSize of 2 win even if it is not the maximum - // (only in case 4 was guessed) - if (tabSize === 4 && spacesDiffCount[4] > 0 && spacesDiffCount[2] > 0 && spacesDiffCount[2] >= spacesDiffCount[4] / 2) { - tabSize = 2; + // Let a tabSize of 2 win even if it is not the maximum + // (only in case 4 was guessed) + if (tabSize === 4 && spacesDiffCount[4] > 0 && spacesDiffCount[2] > 0 && spacesDiffCount[2] >= spacesDiffCount[4] / 2) { + tabSize = 2; + } } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index ca1acb961fdf..f42c5fd92056 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -550,6 +550,10 @@ export interface CodeActionContext { trigger: CodeActionTrigger; } +export interface CodeActionList extends IDisposable { + readonly actions: ReadonlyArray; +} + /** * The code action interface defines the contract between extensions and * the [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature. @@ -559,7 +563,7 @@ export interface CodeActionProvider { /** * Provide commands for the given document and range. */ - provideCodeActions(model: model.ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult; + provideCodeActions(model: model.ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult; /** * Optional list of CodeActionKinds that this provider returns. @@ -1000,6 +1004,7 @@ export interface IInplaceReplaceSupportResult { export interface ILink { range: IRange; url?: URI | string; + tooltip?: string; } export interface ILinksList { @@ -1093,7 +1098,6 @@ export interface DocumentColorProvider { } export interface SelectionRange { - kind: string; range: IRange; } @@ -1461,15 +1465,21 @@ export interface IWebviewPanelOptions { readonly retainContextWhenHidden?: boolean; } -export interface ICodeLensSymbol { +export interface CodeLens { range: IRange; id?: string; command?: Command; } + +export interface CodeLensList { + lenses: CodeLens[]; + dispose(): void; +} + export interface CodeLensProvider { onDidChange?: Event; - provideCodeLenses(model: model.ITextModel, token: CancellationToken): ProviderResult; - resolveCodeLens?(model: model.ITextModel, codeLens: ICodeLensSymbol, token: CancellationToken): ProviderResult; + provideCodeLenses(model: model.ITextModel, token: CancellationToken): ProviderResult; + resolveCodeLens?(model: model.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } // --- feature registries ------ diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index f80df149dde5..9fe4195b3939 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Color, RGBA } from 'vs/base/common/color'; -import { activeContrastBorder, editorBackground, editorForeground, registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { activeContrastBorder, editorBackground, editorForeground, registerColor, editorWarningForeground, editorInfoForeground, editorWarningBorder, editorInfoBorder } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; /** @@ -37,26 +37,14 @@ export const editorOverviewRulerBorder = registerColor('editorOverviewRuler.bord export const editorGutter = registerColor('editorGutter.background', { dark: editorBackground, light: editorBackground, hc: editorBackground }, nls.localize('editorGutter', 'Background color of the editor gutter. The gutter contains the glyph margins and the line numbers.')); -export const editorErrorForeground = registerColor('editorError.foreground', { dark: '#ea4646', light: '#d60a0a', hc: null }, nls.localize('errorForeground', 'Foreground color of error squigglies in the editor.')); -export const editorErrorBorder = registerColor('editorError.border', { dark: null, light: null, hc: Color.fromHex('#E47777').transparent(0.8) }, nls.localize('errorBorder', 'Border color of error squigglies in the editor.')); - -export const editorWarningForeground = registerColor('editorWarning.foreground', { dark: '#4d9e4d', light: '#117711', hc: null }, nls.localize('warningForeground', 'Foreground color of warning squigglies in the editor.')); -export const editorWarningBorder = registerColor('editorWarning.border', { dark: null, light: null, hc: Color.fromHex('#71B771').transparent(0.8) }, nls.localize('warningBorder', 'Border color of warning squigglies in the editor.')); - -export const editorInfoForeground = registerColor('editorInfo.foreground', { dark: '#008000', light: '#008000', hc: null }, nls.localize('infoForeground', 'Foreground color of info squigglies in the editor.')); -export const editorInfoBorder = registerColor('editorInfo.border', { dark: null, light: null, hc: Color.fromHex('#71B771').transparent(0.8) }, nls.localize('infoBorder', 'Border color of info squigglies in the editor.')); - -export const editorHintForeground = registerColor('editorHint.foreground', { dark: Color.fromHex('#eeeeee').transparent(0.7), light: '#6c6c6c', hc: null }, nls.localize('hintForeground', 'Foreground color of hint squigglies in the editor.')); -export const editorHintBorder = registerColor('editorHint.border', { dark: null, light: null, hc: Color.fromHex('#eeeeee').transparent(0.8) }, nls.localize('hintBorder', 'Border color of hint squigglies in the editor.')); - export const editorUnnecessaryCodeBorder = registerColor('editorUnnecessaryCode.border', { dark: null, light: null, hc: Color.fromHex('#fff').transparent(0.8) }, nls.localize('unnecessaryCodeBorder', 'Border color of unnecessary (unused) source code in the editor.')); export const editorUnnecessaryCodeOpacity = registerColor('editorUnnecessaryCode.opacity', { dark: Color.fromHex('#000a'), light: Color.fromHex('#0007'), hc: null }, nls.localize('unnecessaryCodeOpacity', 'Opacity of unnecessary (unused) source code in the editor. For example, "#000000c0" will render the code with 75% opacity. For high contrast themes, use the \'editorUnnecessaryCode.border\' theme color to underline unnecessary code instead of fading it out.')); const rulerRangeDefault = new Color(new RGBA(0, 122, 204, 0.6)); export const overviewRulerRangeHighlight = registerColor('editorOverviewRuler.rangeHighlightForeground', { dark: rulerRangeDefault, light: rulerRangeDefault, hc: rulerRangeDefault }, nls.localize('overviewRulerRangeHighlight', 'Overview ruler marker color for range highlights. The color must not be opaque so as not to hide underlying decorations.'), true); export const overviewRulerError = registerColor('editorOverviewRuler.errorForeground', { dark: new Color(new RGBA(255, 18, 18, 0.7)), light: new Color(new RGBA(255, 18, 18, 0.7)), hc: new Color(new RGBA(255, 50, 50, 1)) }, nls.localize('overviewRuleError', 'Overview ruler marker color for errors.')); -export const overviewRulerWarning = registerColor('editorOverviewRuler.warningForeground', { dark: new Color(new RGBA(18, 136, 18, 0.7)), light: new Color(new RGBA(18, 136, 18, 0.7)), hc: new Color(new RGBA(50, 255, 50, 1)) }, nls.localize('overviewRuleWarning', 'Overview ruler marker color for warnings.')); -export const overviewRulerInfo = registerColor('editorOverviewRuler.infoForeground', { dark: new Color(new RGBA(18, 18, 136, 0.7)), light: new Color(new RGBA(18, 18, 136, 0.7)), hc: new Color(new RGBA(50, 50, 255, 1)) }, nls.localize('overviewRuleInfo', 'Overview ruler marker color for infos.')); +export const overviewRulerWarning = registerColor('editorOverviewRuler.warningForeground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningBorder }, nls.localize('overviewRuleWarning', 'Overview ruler marker color for warnings.')); +export const overviewRulerInfo = registerColor('editorOverviewRuler.infoForeground', { dark: editorInfoForeground, light: editorInfoForeground, hc: editorInfoBorder }, nls.localize('overviewRuleInfo', 'Overview ruler marker color for infos.')); // contains all color rules that used to defined in editor/browser/widget/editor.css registerThemingParticipant((theme, collector) => { diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index faf1bd239bf5..942a74829b27 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -13,6 +13,7 @@ import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { PrefixSumComputerWithCache } from 'vs/editor/common/viewModel/prefixSumComputer'; import { ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { ITheme } from 'vs/platform/theme/common/themeService'; +import { IDisposable } from 'vs/base/common/lifecycle'; export class OutputPosition { _outputPositionBrand: void; @@ -62,11 +63,9 @@ export interface ISplitLine { getViewLineNumberOfModelPosition(deltaLineNumber: number, inputColumn: number): number; } -export interface IViewModelLinesCollection { +export interface IViewModelLinesCollection extends IDisposable { createCoordinatesConverter(): ICoordinatesConverter; - dispose(): void; - setWrappingSettings(wrappingIndent: WrappingIndent, wrappingColumn: number, columnsForFullWidthChar: number): boolean; setTabSize(newTabSize: number): boolean; getHiddenAreas(): Range[]; diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 367df28c4498..f9f14c303acc 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -15,8 +15,14 @@ import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTr import { IModelService } from 'vs/editor/common/services/modelService'; import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './codeActionTrigger'; import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -export class CodeActionSet { +export interface CodeActionSet extends IDisposable { + readonly actions: readonly CodeAction[]; + readonly hasAutoFix: boolean; +} + +class ManagedCodeActionSet extends Disposable implements CodeActionSet { private static codeActionsComparator(a: CodeAction, b: CodeAction): number { if (isNonEmptyArray(a.diagnostics)) { @@ -34,8 +40,10 @@ export class CodeActionSet { public readonly actions: readonly CodeAction[]; - public constructor(actions: readonly CodeAction[]) { - this.actions = mergeSort([...actions], CodeActionSet.codeActionsComparator); + public constructor(actions: readonly CodeAction[], disposables: DisposableStore) { + super(); + this._register(disposables); + this.actions = mergeSort([...actions], ManagedCodeActionSet.codeActionsComparator); } public get hasAutoFix() { @@ -59,12 +67,14 @@ export function getCodeActions( const cts = new TextModelCancellationTokenSource(model, token); const providers = getCodeActionProviders(model, filter); + const disposables = new DisposableStore(); const promises = providers.map(provider => { return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token)).then(providedCodeActions => { - if (cts.token.isCancellationRequested || !Array.isArray(providedCodeActions)) { + if (cts.token.isCancellationRequested || !providedCodeActions) { return []; } - return providedCodeActions.filter(action => action && filtersAction(filter, action)); + disposables.add(providedCodeActions); + return providedCodeActions.actions.filter(action => action && filtersAction(filter, action)); }, (err): CodeAction[] => { if (isPromiseCanceledError(err)) { throw err; @@ -84,7 +94,7 @@ export function getCodeActions( return Promise.all(promises) .then(flatten) - .then(actions => new CodeActionSet(actions)) + .then(actions => new ManagedCodeActionSet(actions, disposables)) .finally(() => { listener.dispose(); cts.dispose(); @@ -106,7 +116,7 @@ function getCodeActionProviders( }); } -registerLanguageCommand('_executeCodeActionProvider', function (accessor, args): Promise> { +registerLanguageCommand('_executeCodeActionProvider', async function (accessor, args): Promise> { const { resource, range, kind } = args; if (!(resource instanceof URI) || !Range.isIRange(range)) { throw illegalArgument(); @@ -117,9 +127,11 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args): throw illegalArgument(); } - return getCodeActions( + const codeActionSet = await getCodeActions( model, model.validateRange(range), { type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, - CancellationToken.None).then(actions => actions.actions); + CancellationToken.None); + codeActionSet.dispose(); + return codeActionSet.actions; }); diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 697f8979d289..c26c5315dd61 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -3,9 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancelablePromise } from 'vs/base/common/async'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose } from 'vs/base/common/lifecycle'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; @@ -20,10 +19,10 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { CodeActionModel, SUPPORTED_CODE_ACTIONS, CodeActionsState } from './codeActionModel'; -import { CodeActionAutoApply, CodeActionFilter, CodeActionKind } from './codeActionTrigger'; -import { CodeActionContextMenu } from './codeActionWidget'; +import { CodeActionAutoApply, CodeActionFilter, CodeActionKind, CodeActionTrigger } from './codeActionTrigger'; +import { CodeActionWidget } from './codeActionWidget'; import { LightBulbWidget } from './lightBulbWidget'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -45,16 +44,15 @@ export class QuickFixController extends Disposable implements IEditorContributio private readonly _editor: ICodeEditor; private readonly _model: CodeActionModel; - private readonly _codeActionContextMenu: CodeActionContextMenu; + private readonly _codeActionWidget: CodeActionWidget; private readonly _lightBulbWidget: LightBulbWidget; - - private _activeRequest: CancelablePromise | undefined; + private _currentCodeActions: CodeActionSet | undefined; constructor( editor: ICodeEditor, @IMarkerService markerService: IMarkerService, @IContextKeyService contextKeyService: IContextKeyService, - @IProgressService progressService: IProgressService, + @ILocalProgressService progressService: ILocalProgressService, @IContextMenuService contextMenuService: IContextMenuService, @ICommandService private readonly _commandService: ICommandService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @@ -63,58 +61,70 @@ export class QuickFixController extends Disposable implements IEditorContributio super(); this._editor = editor; - this._model = new CodeActionModel(this._editor, markerService, contextKeyService, progressService); - this._codeActionContextMenu = new CodeActionContextMenu(editor, contextMenuService, action => this._onApplyCodeAction(action)); + this._model = this._register(new CodeActionModel(this._editor, markerService, contextKeyService, progressService)); + this._codeActionWidget = new CodeActionWidget(editor, contextMenuService, { + onSelectCodeAction: async (action) => { + try { + await this._applyCodeAction(action); + } finally { + // Retrigger + this._trigger({ type: 'auto', filter: {} }); + } + } + }); this._lightBulbWidget = this._register(new LightBulbWidget(editor)); this._updateLightBulbTitle(); - this._register(this._codeActionContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto', filter: {} }))); this._register(this._lightBulbWidget.onClick(this._handleLightBulbSelect, this)); - this._register(this._model.onDidChangeState(e => this._onDidChangeCodeActionsState(e))); + this._register(this._model.onDidChangeState((newState) => this._onDidChangeCodeActionsState(newState))); this._register(this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this)); } - public dispose(): void { + dipose() { super.dispose(); - this._model.dispose(); + dispose(this._currentCodeActions); } private _onDidChangeCodeActionsState(newState: CodeActionsState.State): void { - if (this._activeRequest) { - this._activeRequest.cancel(); - this._activeRequest = undefined; - } - if (newState.type === CodeActionsState.Type.Triggered) { - this._activeRequest = newState.actions; + newState.actions.then(actions => { + dispose(this._currentCodeActions); + this._currentCodeActions = actions; + + if (!actions.actions.length && newState.trigger.context) { + MessageController.get(this._editor).showMessage(newState.trigger.context.notAvailableMessage, newState.trigger.context.position); + } + }); if (newState.trigger.filter && newState.trigger.filter.kind) { // Triggered for specific scope - newState.actions.then(fixes => { - if (fixes.actions.length > 0) { + newState.actions.then(codeActions => { + if (codeActions.actions.length > 0) { // Apply if we only have one action or requested autoApply - if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.actions.length === 1)) { - this._onApplyCodeAction(fixes.actions[0]); + if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && codeActions.actions.length === 1)) { + this._applyCodeAction(codeActions.actions[0]); return; } } - this._codeActionContextMenu.show(newState.actions, newState.position); + this._codeActionWidget.show(newState.actions, newState.position); }).catch(onUnexpectedError); } else if (newState.trigger.type === 'manual') { - this._codeActionContextMenu.show(newState.actions, newState.position); + this._codeActionWidget.show(newState.actions, newState.position); } else { // auto magically triggered // * update an existing list of code actions // * manage light bulb - if (this._codeActionContextMenu.isVisible) { - this._codeActionContextMenu.show(newState.actions, newState.position); + if (this._codeActionWidget.isVisible) { + this._codeActionWidget.show(newState.actions, newState.position); } else { this._lightBulbWidget.tryShow(newState); } } } else { + dispose(this._currentCodeActions); + this._currentCodeActions = undefined; this._lightBulbWidget.hide(); } } @@ -123,12 +133,26 @@ export class QuickFixController extends Disposable implements IEditorContributio return QuickFixController.ID; } - private _handleLightBulbSelect(e: { x: number, y: number, state: CodeActionsState.Triggered }): void { - this._codeActionContextMenu.show(e.state.actions, e); + public manualTriggerAtCurrentPosition( + notAvailableMessage: string, + filter?: CodeActionFilter, + autoApply?: CodeActionAutoApply + ): void { + if (!this._editor.hasModel()) { + return; + } + + MessageController.get(this._editor).closeMessage(); + const triggerPosition = this._editor.getPosition(); + this._trigger({ type: 'manual', filter, autoApply, context: { notAvailableMessage, position: triggerPosition } }); } - public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Promise { - return this._model.trigger({ type: 'manual', filter, autoApply }); + private _trigger(trigger: CodeActionTrigger) { + return this._model.trigger(trigger); + } + + private _handleLightBulbSelect(e: { x: number, y: number, state: CodeActionsState.Triggered }): void { + this._codeActionWidget.show(e.state.actions, e); } private _updateLightBulbTitle(): void { @@ -142,7 +166,7 @@ export class QuickFixController extends Disposable implements IEditorContributio this._lightBulbWidget.title = title; } - private _onApplyCodeAction(action: CodeAction): Promise { + private _applyCodeAction(action: CodeAction): Promise { return applyCodeAction(action, this._bulkEditService, this._commandService, this._editor); } } @@ -161,28 +185,18 @@ export async function applyCodeAction( } } -function showCodeActionsForEditorSelection( +function triggerCodeActionsForEditorSelection( editor: ICodeEditor, notAvailableMessage: string, - filter?: CodeActionFilter, - autoApply?: CodeActionAutoApply -) { - if (!editor.hasModel()) { - return; - } - - const controller = QuickFixController.get(editor); - if (!controller) { - return; - } - - MessageController.get(editor).closeMessage(); - const pos = editor.getPosition(); - controller.triggerFromEditorSelection(filter, autoApply).then(codeActions => { - if (!codeActions || !codeActions.actions.length) { - MessageController.get(editor).showMessage(notAvailableMessage, pos); + filter: CodeActionFilter | undefined, + autoApply: CodeActionAutoApply | undefined +): void { + if (editor.hasModel()) { + const controller = QuickFixController.get(editor); + if (controller) { + controller.manualTriggerAtCurrentPosition(notAvailableMessage, filter, autoApply); } - }); + } } export class QuickFixAction extends EditorAction { @@ -204,7 +218,7 @@ export class QuickFixAction extends EditorAction { } public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available")); + return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), undefined, undefined); } } @@ -284,7 +298,7 @@ export class CodeActionCommand extends EditorCommand { kind: CodeActionKind.Empty, apply: CodeActionAutoApply.IfSingle, }); - return showCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), + return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.quickFix.noneMessage', "No code actions available"), { kind: args.kind, includeSourceActions: true, @@ -347,7 +361,7 @@ export class RefactorAction extends EditorAction { kind: CodeActionKind.Refactor, apply: CodeActionAutoApply.Never }); - return showCodeActionsForEditorSelection(editor, + return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.refactor.noneMessage', "No refactorings available"), { kind: CodeActionKind.Refactor.contains(args.kind) ? args.kind : CodeActionKind.Empty, @@ -402,7 +416,7 @@ export class SourceAction extends EditorAction { kind: CodeActionKind.Source, apply: CodeActionAutoApply.Never }); - return showCodeActionsForEditorSelection(editor, + return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.source.noneMessage', "No source actions available"), { kind: CodeActionKind.Source.contains(args.kind) ? args.kind : CodeActionKind.Empty, @@ -434,7 +448,7 @@ export class OrganizeImportsAction extends EditorAction { } public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - return showCodeActionsForEditorSelection(editor, + return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.organize.noneMessage', "No organize imports action available"), { kind: CodeActionKind.SourceOrganizeImports, includeSourceActions: true }, CodeActionAutoApply.IfSingle); @@ -457,7 +471,7 @@ export class FixAllAction extends EditorAction { } public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - return showCodeActionsForEditorSelection(editor, + return triggerCodeActionsForEditorSelection(editor, nls.localize('fixAll.noneMessage', "No fix all action available"), { kind: CodeActionKind.SourceFixAll, includeSourceActions: true }, CodeActionAutoApply.IfSingle); @@ -488,7 +502,7 @@ export class AutoFixAction extends EditorAction { } public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - return showCodeActionsForEditorSelection(editor, + return triggerCodeActionsForEditorSelection(editor, nls.localize('editor.action.autoFix.noneMessage', "No auto fixes available"), { kind: CodeActionKind.QuickFix, diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index fff21ce6b815..e4d714d58920 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { CancelablePromise, createCancelablePromise, TimeoutTimer } from 'vs/base/common/async'; -import { Emitter, Event } from 'vs/base/common/event'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Emitter } from 'vs/base/common/event'; +import { dispose, Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; @@ -14,36 +14,34 @@ import { Selection } from 'vs/editor/common/core/selection'; import { CodeActionProviderRegistry } from 'vs/editor/common/modes'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; import { CodeActionTrigger } from './codeActionTrigger'; export const SUPPORTED_CODE_ACTIONS = new RawContextKey('supportedCodeAction', ''); -export class CodeActionOracle { +export type TriggeredCodeAction = undefined | { + readonly selection: Selection; + readonly trigger: CodeActionTrigger; + readonly position: Position; +}; - private _disposables: IDisposable[] = []; - private readonly _autoTriggerTimer = new TimeoutTimer(); +class CodeActionOracle extends Disposable { + + private readonly _autoTriggerTimer = this._register(new TimeoutTimer()); constructor( private readonly _editor: ICodeEditor, private readonly _markerService: IMarkerService, - private readonly _signalChange: (newState: CodeActionsState.State) => void, + private readonly _signalChange: (triggered: TriggeredCodeAction) => void, private readonly _delay: number = 250, - private readonly _progressService?: IProgressService, ) { - this._disposables.push( - this._markerService.onMarkerChanged(e => this._onMarkerChanges(e)), - this._editor.onDidChangeCursorPosition(() => this._onCursorChange()), - ); - } - - dispose(): void { - this._disposables = dispose(this._disposables); - this._autoTriggerTimer.cancel(); + super(); + this._register(this._markerService.onMarkerChanged(e => this._onMarkerChanges(e))); + this._register(this._editor.onDidChangeCursorPosition(() => this._onCursorChange())); } - trigger(trigger: CodeActionTrigger) { + public trigger(trigger: CodeActionTrigger): TriggeredCodeAction { const selection = this._getRangeOfSelectionUnlessWhitespaceEnclosed(trigger); return this._createEventAndSignalChange(trigger, selection); } @@ -112,35 +110,24 @@ export class CodeActionOracle { return selection ? selection : undefined; } - private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Promise { - if (!selection) { + private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): TriggeredCodeAction { + const model = this._editor.getModel(); + if (!selection || !model) { // cancel - this._signalChange(CodeActionsState.Empty); - return Promise.resolve(undefined); - } else { - const model = this._editor.getModel(); - if (!model) { - // cancel - this._signalChange(CodeActionsState.Empty); - return Promise.resolve(undefined); - } - - const markerRange = this._getRangeOfMarker(selection); - const position = markerRange ? markerRange.getStartPosition() : selection.getStartPosition(); - const actions = createCancelablePromise(token => getCodeActions(model, selection, trigger, token)); + this._signalChange(undefined); + return undefined; + } - if (this._progressService && trigger.type === 'manual') { - this._progressService.showWhile(actions, 250); - } + const markerRange = this._getRangeOfMarker(selection); + const position = markerRange ? markerRange.getStartPosition() : selection.getStartPosition(); - this._signalChange(new CodeActionsState.Triggered( - trigger, - selection, - position, - actions - )); - return actions; - } + const e: TriggeredCodeAction = { + trigger, + selection, + position + }; + this._signalChange(e); + return e; } } @@ -167,36 +154,35 @@ export namespace CodeActionsState { export type State = typeof Empty | Triggered; } -export class CodeActionModel { +export class CodeActionModel extends Disposable { private _codeActionOracle?: CodeActionOracle; private _state: CodeActionsState.State = CodeActionsState.Empty; - private _onDidChangeState = new Emitter(); - private _disposables: IDisposable[] = []; private readonly _supportedCodeActions: IContextKey; + private readonly _onDidChangeState = this._register(new Emitter()); + public readonly onDidChangeState = this._onDidChangeState.event; + constructor( private readonly _editor: ICodeEditor, private readonly _markerService: IMarkerService, contextKeyService: IContextKeyService, - private readonly _progressService: IProgressService + private readonly _progressService?: ILocalProgressService ) { + super(); this._supportedCodeActions = SUPPORTED_CODE_ACTIONS.bindTo(contextKeyService); - this._disposables.push(this._editor.onDidChangeModel(() => this._update())); - this._disposables.push(this._editor.onDidChangeModelLanguage(() => this._update())); - this._disposables.push(CodeActionProviderRegistry.onDidChange(() => this._update())); + this._register(this._editor.onDidChangeModel(() => this._update())); + this._register(this._editor.onDidChangeModelLanguage(() => this._update())); + this._register(CodeActionProviderRegistry.onDidChange(() => this._update())); this._update(); } dispose(): void { - this._disposables = dispose(this._disposables); + super.dispose(); dispose(this._codeActionOracle); - } - - get onDidChangeState(): Event { - return this._onDidChangeState.event; + this.setState(CodeActionsState.Empty, true); } private _update(): void { @@ -205,9 +191,6 @@ export class CodeActionModel { this._codeActionOracle = undefined; } - if (this._state.type === CodeActionsState.Type.Triggered) { - this._state.actions.cancel(); - } this.setState(CodeActionsState.Empty); const model = this._editor.getModel(); @@ -224,25 +207,46 @@ export class CodeActionModel { this._supportedCodeActions.set(supportedActions.join(' ')); - this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, newState => this.setState(newState), undefined, this._progressService); + this._codeActionOracle = new CodeActionOracle(this._editor, this._markerService, trigger => { + if (!trigger) { + this.setState(CodeActionsState.Empty); + return; + } + + const actions = createCancelablePromise(token => getCodeActions(model, trigger.selection, trigger.trigger, token)); + if (this._progressService && trigger.trigger.type === 'manual') { + this._progressService.showWhile(actions, 250); + } + + this.setState(new CodeActionsState.Triggered(trigger.trigger, trigger.selection, trigger.position, actions)); + + }, undefined); this._codeActionOracle.trigger({ type: 'auto' }); } else { this._supportedCodeActions.reset(); } } - public trigger(trigger: CodeActionTrigger): Promise { + public trigger(trigger: CodeActionTrigger) { if (this._codeActionOracle) { - return this._codeActionOracle.trigger(trigger); + this._codeActionOracle.trigger(trigger); } - return Promise.resolve(undefined); } - private setState(newState: CodeActionsState.State) { + private setState(newState: CodeActionsState.State, skipNotify?: boolean) { if (newState === this._state) { return; } + + // Cancel old request + if (this._state.type === CodeActionsState.Type.Triggered) { + this._state.actions.cancel(); + } + this._state = newState; - this._onDidChangeState.fire(newState); + + if (!skipNotify) { + this._onDidChangeState.fire(newState); + } } } diff --git a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts b/src/vs/editor/contrib/codeAction/codeActionTrigger.ts index c641c9ce130c..382a4bea1ab3 100644 --- a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts +++ b/src/vs/editor/contrib/codeAction/codeActionTrigger.ts @@ -5,6 +5,7 @@ import { startsWith } from 'vs/base/common/strings'; import { CodeAction } from 'vs/editor/common/modes'; +import { Position } from 'vs/editor/common/core/position'; export class CodeActionKind { private static readonly sep = '.'; @@ -90,4 +91,8 @@ export interface CodeActionTrigger { readonly type: 'auto' | 'manual'; readonly filter?: CodeActionFilter; readonly autoApply?: CodeActionAutoApply; + readonly context?: { + readonly notAvailableMessage: string; + readonly position: Position; + }; } \ No newline at end of file diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts index 8a7c0ea9eb5e..709a16cdb448 100644 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ b/src/vs/editor/contrib/codeAction/codeActionWidget.ts @@ -6,7 +6,6 @@ import { getDomNodePagePosition } from 'vs/base/browser/dom'; import { Action } from 'vs/base/common/actions'; import { canceled } from 'vs/base/common/errors'; -import { Emitter, Event } from 'vs/base/common/event'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { ScrollType } from 'vs/editor/common/editorCommon'; @@ -14,25 +13,32 @@ import { CodeAction } from 'vs/editor/common/modes'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; -export class CodeActionContextMenu { +interface CodeActionWidgetDelegate { + onSelectCodeAction: (action: CodeAction) => Promise; +} - private _visible: boolean; +export class CodeActionWidget { - private readonly _onDidExecuteCodeAction = new Emitter(); - public readonly onDidExecuteCodeAction: Event = this._onDidExecuteCodeAction.event; + private _visible: boolean; constructor( private readonly _editor: ICodeEditor, private readonly _contextMenuService: IContextMenuService, - private readonly _onApplyCodeAction: (action: CodeAction) => Promise + private readonly _delegate: CodeActionWidgetDelegate ) { } - async show(actionsToShow: Promise, at?: { x: number; y: number } | Position): Promise { + public async show(actionsToShow: Promise, at?: { x: number; y: number } | Position): Promise { const codeActions = await actionsToShow; + if (!codeActions.actions.length) { + this._visible = false; + return; + } if (!this._editor.getDomNode()) { // cancel when editor went off-dom + this._visible = false; return Promise.reject(canceled()); } + this._visible = true; const actions = codeActions.actions.map(action => this.codeActionToAction(action)); this._contextMenuService.showContextMenu({ @@ -54,9 +60,7 @@ export class CodeActionContextMenu { private codeActionToAction(action: CodeAction): Action { const id = action.command ? action.command.id : action.title; const title = action.title; - return new Action(id, title, undefined, true, () => - this._onApplyCodeAction(action) - .finally(() => this._onDidExecuteCodeAction.fire(undefined))); + return new Action(id, title, undefined, true, () => this._delegate.onSelectCodeAction(action)); } get isVisible(): boolean { diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index 41d45f8bb1f8..8954d192003c 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -45,13 +45,14 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this._futureFixes.cancel(); } })); - this._register(dom.addStandardDisposableListener(this._domNode, 'click', e => { + this._register(dom.addStandardDisposableListener(this._domNode, 'mousedown', e => { if (this._state.type !== CodeActionsState.Type.Triggered) { return; } // Make sure that focus / cursor location is not lost when clicking widget icon this._editor.focus(); + dom.EventHelper.stop(e, true); // a bit of extra work to make sure the menu // doesn't cover the line-text const { top, height } = dom.getDomNodePagePosition(this._domNode); @@ -106,9 +107,8 @@ export class LightBulbWidget extends Disposable implements IContentWidget { return this._position; } - tryShow(newState: CodeActionsState.State) { - - if (newState.type !== CodeActionsState.Type.Triggered || this._position && (!newState.position || this._position.position && this._position.position.lineNumber !== newState.position.lineNumber)) { + tryShow(newState: CodeActionsState.Triggered) { + if (this._position && (!newState.position || this._position.position && this._position.position.lineNumber !== newState.position.lineNumber)) { // hide when getting a 'hide'-request or when currently // showing on another line this.hide(); @@ -121,10 +121,6 @@ export class LightBulbWidget extends Disposable implements IContentWidget { const { token } = this._futureFixes; this._state = newState; - if (this._state.type === CodeActionsState.Empty.type) { - return; - } - const selection = this._state.rangeOrSelection; this._state.actions.then(fixes => { if (!token.isCancellationRequested && fixes.actions.length > 0 && selection) { diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index aaaca4974030..3ed337306fe7 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -3,22 +3,34 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { CodeAction, CodeActionContext, CodeActionProvider, CodeActionProviderRegistry, Command, LanguageIdentifier, ResourceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes'; +import * as modes from 'vs/editor/common/modes'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { CancellationToken } from 'vs/base/common/cancellation'; +function staticCodeActionProvider(...actions: modes.CodeAction[]): modes.CodeActionProvider { + return new class implements modes.CodeActionProvider { + provideCodeActions(): modes.CodeActionList { + return { + actions: actions, + dispose: () => { } + }; + } + }; +} + + suite('CodeAction', () => { - let langId = new LanguageIdentifier('fooLang', 17); + let langId = new modes.LanguageIdentifier('fooLang', 17); let uri = URI.parse('untitled:path'); let model: TextModel; - let disposables: IDisposable[] = []; + const disposables = new DisposableStore(); let testData = { diagnostics: { abc: { @@ -46,7 +58,7 @@ suite('CodeAction', () => { }, command: { abc: { - command: new class implements Command { + command: new class implements modes.Command { id: '1'; title: 'abc'; }, @@ -56,8 +68,8 @@ suite('CodeAction', () => { spelling: { bcd: { diagnostics: [], - edit: new class implements WorkspaceEdit { - edits: ResourceTextEdit[]; + edit: new class implements modes.WorkspaceEdit { + edits: modes.ResourceTextEdit[]; }, title: 'abc' } @@ -79,30 +91,27 @@ suite('CodeAction', () => { }; setup(function () { + disposables.clear(); model = TextModel.createFromString('test1\ntest2\ntest3', undefined, langId, uri); - disposables = [model]; + disposables.add(model); }); teardown(function () { - dispose(disposables); + disposables.clear(); }); test('CodeActions are sorted by type, #38623', async function () { - const provider = new class implements CodeActionProvider { - provideCodeActions() { - return [ - testData.command.abc, - testData.diagnostics.bcd, - testData.spelling.bcd, - testData.tsLint.bcd, - testData.tsLint.abc, - testData.diagnostics.abc - ]; - } - }; + const provider = staticCodeActionProvider( + testData.command.abc, + testData.diagnostics.bcd, + testData.spelling.bcd, + testData.tsLint.bcd, + testData.tsLint.abc, + testData.diagnostics.abc + ); - disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); + disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); const expected = [ // CodeActions with a diagnostics array are shown first ordered by diagnostics.message @@ -122,17 +131,13 @@ suite('CodeAction', () => { }); test('getCodeActions should filter by scope', async function () { - const provider = new class implements CodeActionProvider { - provideCodeActions(): CodeAction[] { - return [ - { title: 'a', kind: 'a' }, - { title: 'b', kind: 'b' }, - { title: 'a.b', kind: 'a.b' } - ]; - } - }; + const provider = staticCodeActionProvider( + { title: 'a', kind: 'a' }, + { title: 'b', kind: 'b' }, + { title: 'a.b', kind: 'a.b' } + ); - disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); + disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); @@ -154,15 +159,18 @@ suite('CodeAction', () => { }); test('getCodeActions should forward requested scope to providers', async function () { - const provider = new class implements CodeActionProvider { - provideCodeActions(_model: any, _range: Range, context: CodeActionContext, _token: any): CodeAction[] { - return [ - { title: context.only || '', kind: context.only } - ]; + const provider = new class implements modes.CodeActionProvider { + provideCodeActions(_model: any, _range: Range, context: modes.CodeActionContext, _token: any): modes.CodeActionList { + return { + actions: [ + { title: context.only || '', kind: context.only } + ], + dispose: () => { } + }; } }; - disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); + disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 1); @@ -170,16 +178,12 @@ suite('CodeAction', () => { }); test('getCodeActions should not return source code action by default', async function () { - const provider = new class implements CodeActionProvider { - provideCodeActions(): CodeAction[] { - return [ - { title: 'a', kind: CodeActionKind.Source.value }, - { title: 'b', kind: 'b' } - ]; - } - }; + const provider = staticCodeActionProvider( + { title: 'a', kind: CodeActionKind.Source.value }, + { title: 'b', kind: 'b' } + ); - disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); + disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None); @@ -196,16 +200,16 @@ suite('CodeAction', () => { test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () { let wasInvoked = false; - const provider = new class implements CodeActionProvider { - provideCodeActions() { + const provider = new class implements modes.CodeActionProvider { + provideCodeActions(): modes.CodeActionList { wasInvoked = true; - return []; + return { actions: [], dispose: () => { } }; } providedCodeActionKinds = [CodeActionKind.Refactor.value]; }; - disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); + disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index a5bef22904a9..59b98899d9ce 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -4,32 +4,38 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Selection } from 'vs/editor/common/core/selection'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { CodeActionProviderRegistry, LanguageIdentifier } from 'vs/editor/common/modes'; -import { CodeActionOracle, CodeActionsState } from 'vs/editor/contrib/codeAction/codeActionModel'; +import * as modes from 'vs/editor/common/modes'; +import { CodeActionModel, CodeActionsState } from 'vs/editor/contrib/codeAction/codeActionModel'; import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { MarkerService } from 'vs/platform/markers/common/markerService'; const testProvider = { - provideCodeActions() { - return [{ id: 'test-command', title: 'test', arguments: [] }]; + provideCodeActions(): modes.CodeActionList { + return { + actions: [ + { title: 'test', command: { id: 'test-command', title: 'test', arguments: [] } } + ], + dispose() { /* noop*/ } + }; } }; -suite('CodeAction', () => { +suite('CodeActionModel', () => { - const languageIdentifier = new LanguageIdentifier('foo-lang', 3); + const languageIdentifier = new modes.LanguageIdentifier('foo-lang', 3); let uri = URI.parse('untitled:path'); let model: TextModel; let markerService: MarkerService; let editor: ICodeEditor; - let disposables: IDisposable[]; + const disposables = new DisposableStore(); setup(() => { - disposables = []; + disposables.clear(); markerService = new MarkerService(); model = TextModel.createFromString('foobar foo bar\nfarboo far boo', undefined, languageIdentifier, uri); editor = createTestCodeEditor({ model: model }); @@ -37,26 +43,28 @@ suite('CodeAction', () => { }); teardown(() => { - dispose(disposables); + disposables.clear(); editor.dispose(); model.dispose(); markerService.dispose(); }); test('Orcale -> marker added', done => { - const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider); - disposables.push(reg); + const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, testProvider); + disposables.add(reg); - const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => { + const contextKeys = new MockContextKeyService(); + const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); + disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { assert.equal(e.trigger.type, 'auto'); assert.ok(e.actions); e.actions.then(fixes => { - oracle.dispose(); + model.dispose(); assert.equal(fixes.actions.length, 1); done(); }, done); - }); + })); // start here markerService.changeOne('fake', uri, [{ @@ -70,8 +78,8 @@ suite('CodeAction', () => { }); test('Orcale -> position changed', () => { - const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider); - disposables.push(reg); + const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, testProvider); + disposables.add(reg); markerService.changeOne('fake', uri, [{ startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6, @@ -84,28 +92,29 @@ suite('CodeAction', () => { editor.setPosition({ lineNumber: 2, column: 1 }); return new Promise((resolve, reject) => { - - const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => { + const contextKeys = new MockContextKeyService(); + const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); + disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { assert.equal(e.trigger.type, 'auto'); assert.ok(e.actions); e.actions.then(fixes => { - oracle.dispose(); + model.dispose(); assert.equal(fixes.actions.length, 1); resolve(undefined); }, reject); - }); + })); // start here editor.setPosition({ lineNumber: 1, column: 1 }); }); }); test('Lightbulb is in the wrong place, #29933', async function () { - const reg = CodeActionProviderRegistry.register(languageIdentifier.language, { - provideCodeActions(_doc, _range) { - return []; + const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, { + provideCodeActions(_doc, _range): modes.CodeActionList { + return { actions: [], dispose() { /* noop*/ } }; } }); - disposables.push(reg); + disposables.add(reg); editor.getModel()!.setValue('// @ts-check\n2\ncon\n'); @@ -119,8 +128,9 @@ suite('CodeAction', () => { // case 1 - drag selection over multiple lines -> range of enclosed marker, position or marker await new Promise(resolve => { - - let oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => { + const contextKeys = new MockContextKeyService(); + const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); + disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { assert.equal(e.trigger.type, 'auto'); const selection = e.rangeOrSelection; assert.deepEqual(selection.selectionStartLineNumber, 1); @@ -128,31 +138,32 @@ suite('CodeAction', () => { assert.deepEqual(selection.endLineNumber, 4); assert.deepEqual(selection.endColumn, 1); assert.deepEqual(e.position, { lineNumber: 3, column: 1 }); - - oracle.dispose(); + model.dispose(); resolve(undefined); - }, 5); + }, 5)); editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 }); }); }); test('Orcale -> should only auto trigger once for cursor and marker update right after each other', done => { - const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider); - disposables.push(reg); + const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, testProvider); + disposables.add(reg); let triggerCount = 0; - const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => { + const contextKeys = new MockContextKeyService(); + const model = disposables.add(new CodeActionModel(editor, markerService, contextKeys, undefined)); + disposables.add(model.onDidChangeState((e: CodeActionsState.Triggered) => { assert.equal(e.trigger.type, 'auto'); ++triggerCount; // give time for second trigger before completing test setTimeout(() => { - oracle.dispose(); + model.dispose(); assert.strictEqual(triggerCount, 1); done(); }, 50); - }, 5 /*delay*/); + }, 5 /*delay*/)); markerService.changeOne('fake', uri, [{ startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6, diff --git a/src/vs/editor/contrib/codelens/codeLensCache.ts b/src/vs/editor/contrib/codelens/codeLensCache.ts index b23eb35037a7..ed11e7d70e65 100644 --- a/src/vs/editor/contrib/codelens/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/codeLensCache.ts @@ -6,18 +6,19 @@ import { ITextModel } from 'vs/editor/common/model'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ICodeLensData } from 'vs/editor/contrib/codelens/codelens'; +import { CodeLensModel } from 'vs/editor/contrib/codelens/codelens'; import { LRUCache, values } from 'vs/base/common/map'; -import { ICodeLensSymbol, CodeLensProvider } from 'vs/editor/common/modes'; +import { CodeLensProvider, CodeLensList, CodeLens } from 'vs/editor/common/modes'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Range } from 'vs/editor/common/core/range'; +import { runWhenIdle } from 'vs/base/common/async'; export const ICodeLensCache = createDecorator('ICodeLensCache'); export interface ICodeLensCache { _serviceBrand: any; - put(model: ITextModel, data: ICodeLensData[]): void; - get(model: ITextModel): ICodeLensData[] | undefined; + put(model: ITextModel, data: CodeLensModel): void; + get(model: ITextModel): CodeLensModel | undefined; delete(model: ITextModel): void; } @@ -30,7 +31,7 @@ class CacheItem { constructor( readonly lineCount: number, - readonly data: ICodeLensData[] + readonly data: CodeLensModel ) { } } @@ -39,7 +40,7 @@ export class CodeLensCache implements ICodeLensCache { _serviceBrand: any; private readonly _fakeProvider = new class implements CodeLensProvider { - provideCodeLenses(): ICodeLensSymbol[] { + provideCodeLenses(): CodeLensList { throw new Error('not supported'); } }; @@ -48,9 +49,12 @@ export class CodeLensCache implements ICodeLensCache { constructor(@IStorageService storageService: IStorageService) { - const key = 'codelens/cache'; + // remove old data + const oldkey = 'codelens/cache'; + runWhenIdle(() => storageService.remove(oldkey, StorageScope.WORKSPACE)); // restore lens data on start + const key = 'codelens/cache2'; const raw = storageService.get(key, StorageScope.WORKSPACE, '{}'); this._deserialize(raw); @@ -61,13 +65,12 @@ export class CodeLensCache implements ICodeLensCache { }); } - put(model: ITextModel, data: ICodeLensData[]): void { - const item = new CacheItem(model.getLineCount(), data.map(item => { - return { - symbol: item.symbol, - provider: this._fakeProvider - }; - })); + put(model: ITextModel, data: CodeLensModel): void { + + const lensModel = new CodeLensModel(); + lensModel.add({ lenses: data.lenses.map(v => v.symbol), dispose() { } }, this._fakeProvider); + + const item = new CacheItem(model.getLineCount(), lensModel); this._cache.set(model.uri.toString(), item); } @@ -86,7 +89,7 @@ export class CodeLensCache implements ICodeLensCache { const data: Record = Object.create(null); this._cache.forEach((value, key) => { const lines = new Set(); - for (const d of value.data) { + for (const d of value.data.lenses) { lines.add(d.symbol.range.startLineNumber); } data[key] = { @@ -102,14 +105,14 @@ export class CodeLensCache implements ICodeLensCache { const data: Record = JSON.parse(raw); for (const key in data) { const element = data[key]; - const symbols: ICodeLensData[] = []; + const lenses: CodeLens[] = []; for (const line of element.lines) { - symbols.push({ - provider: this._fakeProvider, - symbol: { range: new Range(line, 1, line, 11) } - }); + lenses.push({ range: new Range(line, 1, line, 11) }); } - this._cache.set(key, new CacheItem(element.lineCount, symbols)); + + const model = new CodeLensModel(); + model.add({ lenses, dispose() { } }, this._fakeProvider); + this._cache.set(key, new CacheItem(element.lineCount, model)); } } catch { // ignore... diff --git a/src/vs/editor/contrib/codelens/codelens.ts b/src/vs/editor/contrib/codelens/codelens.ts index 2d3fd1ec6e16..bc10d804d761 100644 --- a/src/vs/editor/contrib/codelens/codelens.ts +++ b/src/vs/editor/contrib/codelens/codelens.ts @@ -9,38 +9,59 @@ import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/error import { URI } from 'vs/base/common/uri'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { ITextModel } from 'vs/editor/common/model'; -import { CodeLensProvider, CodeLensProviderRegistry, ICodeLensSymbol } from 'vs/editor/common/modes'; +import { CodeLensProvider, CodeLensProviderRegistry, CodeLens, CodeLensList } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { DisposableStore } from 'vs/base/common/lifecycle'; -export interface ICodeLensData { - symbol: ICodeLensSymbol; +export interface CodeLensItem { + symbol: CodeLens; provider: CodeLensProvider; } -export function getCodeLensData(model: ITextModel, token: CancellationToken): Promise { +export class CodeLensModel { - const symbols: ICodeLensData[] = []; - const provider = CodeLensProviderRegistry.ordered(model); + lenses: CodeLensItem[] = []; - const promises = provider.map(provider => Promise.resolve(provider.provideCodeLenses(model, token)).then(result => { - if (Array.isArray(result)) { - for (let symbol of result) { - symbols.push({ symbol, provider }); - } + private readonly _dispoables = new DisposableStore(); + + dispose(): void { + this._dispoables.dispose(); + } + + add(list: CodeLensList, provider: CodeLensProvider): void { + this._dispoables.add(list); + for (const symbol of list.lenses) { + this.lenses.push({ symbol, provider }); } - }).catch(onUnexpectedExternalError)); + } +} + +export function getCodeLensData(model: ITextModel, token: CancellationToken): Promise { + + const provider = CodeLensProviderRegistry.ordered(model); + const providerRanks = new Map(); + const result = new CodeLensModel(); + + const promises = provider.map((provider, i) => { + + providerRanks.set(provider, i); + + return Promise.resolve(provider.provideCodeLenses(model, token)) + .then(list => list && result.add(list, provider)) + .catch(onUnexpectedExternalError); + }); return Promise.all(promises).then(() => { - return mergeSort(symbols, (a, b) => { + result.lenses = mergeSort(result.lenses, (a, b) => { // sort by lineNumber, provider-rank, and column if (a.symbol.range.startLineNumber < b.symbol.range.startLineNumber) { return -1; } else if (a.symbol.range.startLineNumber > b.symbol.range.startLineNumber) { return 1; - } else if (provider.indexOf(a.provider) < provider.indexOf(b.provider)) { + } else if (providerRanks.get(a.provider)! < providerRanks.get(b.provider)!) { return -1; - } else if (provider.indexOf(a.provider) > provider.indexOf(b.provider)) { + } else if (providerRanks.get(a.provider)! > providerRanks.get(b.provider)!) { return 1; } else if (a.symbol.range.startColumn < b.symbol.range.startColumn) { return -1; @@ -50,6 +71,8 @@ export function getCodeLensData(model: ITextModel, token: CancellationToken): Pr return 0; } }); + + return result; }); } @@ -65,12 +88,12 @@ registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) { throw illegalArgument(); } - const result: ICodeLensSymbol[] = []; + const result: CodeLens[] = []; return getCodeLensData(model, CancellationToken.None).then(value => { let resolve: Promise[] = []; - for (const item of value) { + for (const item of value.lenses) { if (typeof itemResolveCount === 'undefined' || Boolean(item.symbol.command)) { result.push(item.symbol); } else if (itemResolveCount-- > 0 && item.provider.resolveCodeLens) { @@ -78,7 +101,7 @@ registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) { } } - return Promise.all(resolve); + return Promise.all(resolve).finally(() => value.dispose()); }).then(() => { return result; diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index b2b61a2f4039..2b7960b62700 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -5,16 +5,16 @@ import { CancelablePromise, RunOnceScheduler, createCancelablePromise, disposableTimeout } from 'vs/base/common/async'; import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; -import { CodeLensProviderRegistry, ICodeLensSymbol } from 'vs/editor/common/modes'; -import { ICodeLensData, getCodeLensData } from 'vs/editor/contrib/codelens/codelens'; -import { CodeLens, CodeLensHelper } from 'vs/editor/contrib/codelens/codelensWidget'; +import { CodeLensProviderRegistry, CodeLens } from 'vs/editor/common/modes'; +import { CodeLensModel, getCodeLensData, CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; +import { CodeLensWidget, CodeLensHelper } from 'vs/editor/contrib/codelens/codelensWidget'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache'; @@ -25,12 +25,13 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { private _isEnabled: boolean; - private _globalToDispose: IDisposable[]; - private _localToDispose: IDisposable[]; - private _lenses: CodeLens[]; - private _currentFindCodeLensSymbolsPromise: CancelablePromise | null; - private _modelChangeCounter: number; - private _currentResolveCodeLensSymbolsPromise: CancelablePromise | null; + private readonly _globalToDispose = new DisposableStore(); + private readonly _localToDispose = new DisposableStore(); + private _lenses: CodeLensWidget[] = []; + private _currentFindCodeLensSymbolsPromise: CancelablePromise | undefined; + private _currentCodeLensModel: CodeLensModel | undefined; + private _modelChangeCounter: number = 0; + private _currentResolveCodeLensSymbolsPromise: CancelablePromise | undefined; private _detectVisibleLenses: RunOnceScheduler; constructor( @@ -41,41 +42,36 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { ) { this._isEnabled = this._editor.getConfiguration().contribInfo.codeLens; - this._globalToDispose = []; - this._localToDispose = []; - this._lenses = []; - this._currentFindCodeLensSymbolsPromise = null; - this._modelChangeCounter = 0; - - this._globalToDispose.push(this._editor.onDidChangeModel(() => this._onModelChange())); - this._globalToDispose.push(this._editor.onDidChangeModelLanguage(() => this._onModelChange())); - this._globalToDispose.push(this._editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { + this._globalToDispose.add(this._editor.onDidChangeModel(() => this._onModelChange())); + this._globalToDispose.add(this._editor.onDidChangeModelLanguage(() => this._onModelChange())); + this._globalToDispose.add(this._editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { let prevIsEnabled = this._isEnabled; this._isEnabled = this._editor.getConfiguration().contribInfo.codeLens; if (prevIsEnabled !== this._isEnabled) { this._onModelChange(); } })); - this._globalToDispose.push(CodeLensProviderRegistry.onDidChange(this._onModelChange, this)); + this._globalToDispose.add(CodeLensProviderRegistry.onDidChange(this._onModelChange, this)); this._onModelChange(); } dispose(): void { this._localDispose(); - this._globalToDispose = dispose(this._globalToDispose); + this._globalToDispose.dispose(); } private _localDispose(): void { if (this._currentFindCodeLensSymbolsPromise) { this._currentFindCodeLensSymbolsPromise.cancel(); - this._currentFindCodeLensSymbolsPromise = null; + this._currentFindCodeLensSymbolsPromise = undefined; this._modelChangeCounter++; } if (this._currentResolveCodeLensSymbolsPromise) { this._currentResolveCodeLensSymbolsPromise.cancel(); - this._currentResolveCodeLensSymbolsPromise = null; + this._currentResolveCodeLensSymbolsPromise = undefined; } - this._localToDispose = dispose(this._localToDispose); + this._localToDispose.clear(); + dispose(this._currentCodeLensModel); } getId(): string { @@ -104,7 +100,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { // no provider -> return but check with // cached lenses. they expire after 30 seconds if (cachedLenses) { - this._localToDispose.push(disposableTimeout(() => { + this._localToDispose.add(disposableTimeout(() => { const cachedLensesNow = this._codeLensCache.get(model); if (cachedLenses === cachedLensesNow) { this._codeLensCache.delete(model); @@ -118,7 +114,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { for (const provider of CodeLensProviderRegistry.all(model)) { if (typeof provider.onDidChange === 'function') { let registration = provider.onDidChange(() => scheduler.schedule()); - this._localToDispose.push(registration); + this._localToDispose.add(registration); } } @@ -136,18 +132,25 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._currentFindCodeLensSymbolsPromise.then(result => { if (counterValue === this._modelChangeCounter) { // only the last one wins + // lifecycle -> dispose old model + dispose(this._currentCodeLensModel); + this._currentCodeLensModel = result; + + // cache model to reduce flicker this._codeLensCache.put(model, result); + + // render lenses this._renderCodeLensSymbols(result); this._detectVisibleLenses.schedule(); } }, onUnexpectedError); }, 250); - this._localToDispose.push(scheduler); - this._localToDispose.push(this._detectVisibleLenses); - this._localToDispose.push(this._editor.onDidChangeModelContent((e) => { + this._localToDispose.add(scheduler); + this._localToDispose.add(this._detectVisibleLenses); + this._localToDispose.add(this._editor.onDidChangeModelContent((e) => { this._editor.changeDecorations((changeAccessor) => { this._editor.changeViewZones((viewAccessor) => { - let toDispose: CodeLens[] = []; + let toDispose: CodeLensWidget[] = []; let lastLensLineNumber: number = -1; this._lenses.forEach((lens) => { @@ -176,15 +179,15 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { // Ask for all references again scheduler.schedule(); })); - this._localToDispose.push(this._editor.onDidScrollChange(e => { + this._localToDispose.add(this._editor.onDidScrollChange(e => { if (e.scrollTopChanged && this._lenses.length > 0) { this._detectVisibleLenses.schedule(); } })); - this._localToDispose.push(this._editor.onDidLayoutChange(e => { + this._localToDispose.add(this._editor.onDidLayoutChange(e => { this._detectVisibleLenses.schedule(); })); - this._localToDispose.push(toDisposable(() => { + this._localToDispose.add(toDisposable(() => { if (this._editor.getModel()) { const scrollState = StableEditorScrollState.capture(this._editor); this._editor.changeDecorations((changeAccessor) => { @@ -198,14 +201,14 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._disposeAllLenses(undefined, undefined); } })); - this._localToDispose.push(this._editor.onDidChangeConfiguration(e => { + this._localToDispose.add(this._editor.onDidChangeConfiguration(e => { if (e.fontInfo) { for (const lens of this._lenses) { lens.updateHeight(); } } })); - this._localToDispose.push(this._editor.onMouseUp(e => { + this._localToDispose.add(this._editor.onMouseUp(e => { if (e.target.type === editorBrowser.MouseTargetType.CONTENT_WIDGET && e.target.element && e.target.element.tagName === 'A') { for (const lens of this._lenses) { let command = lens.getCommand(e.target.element as HTMLLinkElement); @@ -228,16 +231,16 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._lenses = []; } - private _renderCodeLensSymbols(symbols: ICodeLensData[]): void { + private _renderCodeLensSymbols(symbols: CodeLensModel): void { if (!this._editor.hasModel()) { return; } let maxLineNumber = this._editor.getModel().getLineCount(); - let groups: ICodeLensData[][] = []; - let lastGroup: ICodeLensData[] | undefined; + let groups: CodeLensItem[][] = []; + let lastGroup: CodeLensItem[] | undefined; - for (let symbol of symbols) { + for (let symbol of symbols.lenses) { let line = symbol.symbol.range.startLineNumber; if (line < 1 || line > maxLineNumber) { // invalid code lens @@ -272,7 +275,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { groupsIndex++; codeLensIndex++; } else { - this._lenses.splice(codeLensIndex, 0, new CodeLens(groups[groupsIndex], this._editor, helper, accessor, () => this._detectVisibleLenses.schedule())); + this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, helper, accessor, () => this._detectVisibleLenses.schedule())); codeLensIndex++; groupsIndex++; } @@ -286,7 +289,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { // Create extra symbols while (groupsIndex < groups.length) { - this._lenses.push(new CodeLens(groups[groupsIndex], this._editor, helper, accessor, () => this._detectVisibleLenses.schedule())); + this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, helper, accessor, () => this._detectVisibleLenses.schedule())); groupsIndex++; } @@ -300,7 +303,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { private _onViewportChanged(): void { if (this._currentResolveCodeLensSymbolsPromise) { this._currentResolveCodeLensSymbolsPromise.cancel(); - this._currentResolveCodeLensSymbolsPromise = null; + this._currentResolveCodeLensSymbolsPromise = undefined; } const model = this._editor.getModel(); @@ -308,8 +311,8 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { return; } - const toResolve: ICodeLensData[][] = []; - const lenses: CodeLens[] = []; + const toResolve: CodeLensItem[][] = []; + const lenses: CodeLensWidget[] = []; this._lenses.forEach((lens) => { const request = lens.computeIfNecessary(model); if (request) { @@ -326,7 +329,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { const promises = toResolve.map((request, i) => { - const resolvedSymbols = new Array(request.length); + const resolvedSymbols = new Array(request.length); const promises = request.map((request, i) => { if (!request.symbol.command && typeof request.provider.resolveCodeLens === 'function') { return Promise.resolve(request.provider.resolveCodeLens(model, request.symbol, token)).then(symbol => { @@ -346,11 +349,10 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { return Promise.all(promises); }); - this._currentResolveCodeLensSymbolsPromise.then(() => { - this._currentResolveCodeLensSymbolsPromise = null; - }).catch(err => { - this._currentResolveCodeLensSymbolsPromise = null; + this._currentResolveCodeLensSymbolsPromise.catch(err => { onUnexpectedError(err); + }).finally(() => { + this._currentResolveCodeLensSymbolsPromise = undefined; }); } } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 9701a319f43a..2f85d841a0f3 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -11,9 +11,9 @@ import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { Command, ICodeLensSymbol } from 'vs/editor/common/modes'; +import { Command, CodeLens } from 'vs/editor/common/modes'; import { editorCodeLensForeground } from 'vs/editor/common/view/editorColorRegistry'; -import { ICodeLensData } from 'vs/editor/contrib/codelens/codelens'; +import { CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -65,7 +65,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { constructor( editor: editorBrowser.ICodeEditor, symbolRange: Range, - data: ICodeLensData[] + data: CodeLensItem[] ) { this._id = 'codeLensWidget' + (++CodeLensContentWidget._idPool); this._editor = editor; @@ -88,7 +88,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { this._domNode.innerHTML = ' '; } - withCommands(inSymbols: Array, animate: boolean): void { + withCommands(inSymbols: Array, animate: boolean): void { this._commands.clear(); const symbols = coalesce(inSymbols); @@ -189,17 +189,17 @@ export class CodeLensHelper { } } -export class CodeLens { +export class CodeLensWidget { private readonly _editor: editorBrowser.ICodeEditor; private readonly _viewZone: CodeLensViewZone; private readonly _viewZoneId: number; private readonly _contentWidget: CodeLensContentWidget; private _decorationIds: string[]; - private _data: ICodeLensData[]; + private _data: CodeLensItem[]; constructor( - data: ICodeLensData[], + data: CodeLensItem[], editor: editorBrowser.ICodeEditor, helper: CodeLensHelper, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor, @@ -256,7 +256,7 @@ export class CodeLens { }); } - updateCodeLensSymbols(data: ICodeLensData[], helper: CodeLensHelper): void { + updateCodeLensSymbols(data: CodeLensItem[], helper: CodeLensHelper): void { while (this._decorationIds.length) { helper.removeDecoration(this._decorationIds.pop()!); } @@ -270,7 +270,7 @@ export class CodeLens { }); } - computeIfNecessary(model: ITextModel): ICodeLensData[] | null { + computeIfNecessary(model: ITextModel): CodeLensItem[] | null { if (!this._contentWidget.isVisible()) { return null; } @@ -285,7 +285,7 @@ export class CodeLens { return this._data; } - updateCommands(symbols: Array): void { + updateCommands(symbols: Array): void { this._contentWidget.withCommands(symbols, true); for (let i = 0; i < this._data.length; i++) { const resolved = symbols[i]; diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index 40f51d379c3a..bd94aa5b587f 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -7,7 +7,7 @@ import { CancelablePromise, TimeoutTimer, createCancelablePromise } from 'vs/bas import { RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { hash } from 'vs/base/common/hash'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -22,14 +22,13 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur const MAX_DECORATORS = 500; -export class ColorDetector implements IEditorContribution { +export class ColorDetector extends Disposable implements IEditorContribution { private static readonly ID: string = 'editor.contrib.colorDetector'; static RECOMPUTE_TIME = 1000; // ms - private _globalToDispose: IDisposable[] = []; - private _localToDispose: IDisposable[] = []; + private readonly _localToDispose = this._register(new DisposableStore()); private _computePromise: CancelablePromise | null; private _timeoutTimer: TimeoutTimer | null; @@ -45,13 +44,14 @@ export class ColorDetector implements IEditorContribution { @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { - this._globalToDispose.push(_editor.onDidChangeModel((e) => { + super(); + this._register(_editor.onDidChangeModel((e) => { this._isEnabled = this.isEnabled(); this.onModelChanged(); })); - this._globalToDispose.push(_editor.onDidChangeModelLanguage((e) => this.onModelChanged())); - this._globalToDispose.push(ColorProviderRegistry.onDidChange((e) => this.onModelChanged())); - this._globalToDispose.push(_editor.onDidChangeConfiguration((e) => { + this._register(_editor.onDidChangeModelLanguage((e) => this.onModelChanged())); + this._register(ColorProviderRegistry.onDidChange((e) => this.onModelChanged())); + this._register(_editor.onDidChangeConfiguration((e) => { let prevIsEnabled = this._isEnabled; this._isEnabled = this.isEnabled(); if (prevIsEnabled !== this._isEnabled) { @@ -98,7 +98,7 @@ export class ColorDetector implements IEditorContribution { dispose(): void { this.stop(); this.removeAllDecorations(); - this._globalToDispose = dispose(this._globalToDispose); + super.dispose(); } private onModelChanged(): void { @@ -113,7 +113,7 @@ export class ColorDetector implements IEditorContribution { return; } - this._localToDispose.push(this._editor.onDidChangeModelContent((e) => { + this._localToDispose.add(this._editor.onDidChangeModelContent((e) => { if (!this._timeoutTimer) { this._timeoutTimer = new TimeoutTimer(); this._timeoutTimer.cancelAndSet(() => { @@ -149,7 +149,7 @@ export class ColorDetector implements IEditorContribution { this._computePromise.cancel(); this._computePromise = null; } - this._localToDispose = dispose(this._localToDispose); + this._localToDispose.clear(); } private updateDecorations(colorDatas: IColorData[]): void { diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index a77302fc2e87..34ec21d86203 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -125,7 +125,7 @@ export class ContextMenuController implements IEditorContribution { } } - private _getMenuActions(model: ITextModel): IAction[] { + private _getMenuActions(model: ITextModel): ReadonlyArray { const result: IAction[] = []; let contextMenu = this._menuService.createMenu(MenuId.EditorContext, this._contextKeyService); @@ -141,7 +141,7 @@ export class ContextMenuController implements IEditorContribution { return result; } - private _doShowContextMenu(actions: IAction[], anchor: IAnchor | null = null): void { + private _doShowContextMenu(actions: ReadonlyArray, anchor: IAnchor | null = null): void { if (!this._editor.hasModel()) { return; } diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index e2957bab5819..bb50bac3b853 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -5,7 +5,7 @@ import 'vs/css!./dnd'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ICodeEditor, IEditorMouseEvent, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser'; @@ -28,12 +28,11 @@ function hasTriggerModifier(e: IKeyboardEvent | IMouseEvent): boolean { } } -export class DragAndDropController implements editorCommon.IEditorContribution { +export class DragAndDropController extends Disposable implements editorCommon.IEditorContribution { private static readonly ID = 'editor.contrib.dragAndDrop'; private readonly _editor: ICodeEditor; - private _toUnhook: IDisposable[]; private _dragSelection: Selection | null; private _dndDecorationIds: string[]; private _mouseDown: boolean; @@ -45,15 +44,15 @@ export class DragAndDropController implements editorCommon.IEditorContribution { } constructor(editor: ICodeEditor) { + super(); this._editor = editor; - this._toUnhook = []; - this._toUnhook.push(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); - this._toUnhook.push(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); - this._toUnhook.push(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e))); - this._toUnhook.push(this._editor.onMouseDrop((e: IEditorMouseEvent) => this._onEditorMouseDrop(e))); - this._toUnhook.push(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e))); - this._toUnhook.push(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e))); - this._toUnhook.push(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur())); + this._register(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); + this._register(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); + this._register(this._editor.onMouseDrag((e: IEditorMouseEvent) => this._onEditorMouseDrag(e))); + this._register(this._editor.onMouseDrop((e: IEditorMouseEvent) => this._onEditorMouseDrop(e))); + this._register(this._editor.onKeyDown((e: IKeyboardEvent) => this.onEditorKeyDown(e))); + this._register(this._editor.onKeyUp((e: IKeyboardEvent) => this.onEditorKeyUp(e))); + this._register(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur())); this._dndDecorationIds = []; this._mouseDown = false; this._modifierPressed = false; @@ -228,7 +227,7 @@ export class DragAndDropController implements editorCommon.IEditorContribution { this._dragSelection = null; this._mouseDown = false; this._modifierPressed = false; - this._toUnhook = dispose(this._toUnhook); + super.dispose(); } } diff --git a/src/vs/editor/contrib/find/simpleFindWidget.ts b/src/vs/editor/contrib/find/simpleFindWidget.ts index 61cbcdc127e6..76be0e50ad11 100644 --- a/src/vs/editor/contrib/find/simpleFindWidget.ts +++ b/src/vs/editor/contrib/find/simpleFindWidget.ts @@ -99,29 +99,29 @@ export abstract class SimpleFindWidget extends Widget { } })); - const prevBtn = new SimpleButton({ + const prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL, className: 'previous', onTrigger: () => { this.find(true); } - }); + })); - const nextBtn = new SimpleButton({ + const nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL, className: 'next', onTrigger: () => { this.find(false); } - }); + })); - const closeBtn = new SimpleButton({ + const closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL, className: 'close-fw', onTrigger: () => { this.hide(); } - }); + })); this._innerDomNode = document.createElement('div'); this._innerDomNode.classList.add('simple-find-part'); diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 9ec22649c4c9..ef20ff755237 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -9,7 +9,7 @@ import * as types from 'vs/base/common/types'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { RunOnceScheduler, Delayer, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ScrollType, IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, registerInstantiatedEditorAction } from 'vs/editor/browser/editorExtensions'; @@ -46,7 +46,7 @@ interface FoldingStateMemento { provider?: string; } -export class FoldingController implements IEditorContribution { +export class FoldingController extends Disposable implements IEditorContribution { static MAX_FOLDING_REGIONS = 5000; @@ -73,27 +73,25 @@ export class FoldingController implements IEditorContribution { private foldingModelPromise: Promise | null; private updateScheduler: Delayer | null; - private globalToDispose: IDisposable[]; private cursorChangedScheduler: RunOnceScheduler | null; - private localToDispose: IDisposable[]; + private readonly localToDispose = this._register(new DisposableStore()); constructor(editor: ICodeEditor) { + super(); this.editor = editor; this._isEnabled = this.editor.getConfiguration().contribInfo.folding; this._autoHideFoldingControls = this.editor.getConfiguration().contribInfo.showFoldingControls === 'mouseover'; this._useFoldingProviders = this.editor.getConfiguration().contribInfo.foldingStrategy !== 'indentation'; - this.globalToDispose = []; - this.localToDispose = []; this.foldingDecorationProvider = new FoldingDecorationProvider(editor); this.foldingDecorationProvider.autoHideFoldingControls = this._autoHideFoldingControls; - this.globalToDispose.push(this.editor.onDidChangeModel(() => this.onModelChanged())); + this._register(this.editor.onDidChangeModel(() => this.onModelChanged())); - this.globalToDispose.push(this.editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { + this._register(this.editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { if (e.contribInfo) { let oldIsEnabled = this._isEnabled; this._isEnabled = this.editor.getConfiguration().contribInfo.folding; @@ -113,7 +111,6 @@ export class FoldingController implements IEditorContribution { } } })); - this.globalToDispose.push({ dispose: () => dispose(this.localToDispose) }); this.onModelChanged(); } @@ -121,10 +118,6 @@ export class FoldingController implements IEditorContribution { return ID; } - public dispose(): void { - this.globalToDispose = dispose(this.globalToDispose); - } - /** * Store view state. */ @@ -173,7 +166,7 @@ export class FoldingController implements IEditorContribution { } private onModelChanged(): void { - this.localToDispose = dispose(this.localToDispose); + this.localToDispose.clear(); let model = this.editor.getModel(); if (!this._isEnabled || !model || model.isTooLargeForTokenization()) { @@ -182,23 +175,23 @@ export class FoldingController implements IEditorContribution { } this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider); - this.localToDispose.push(this.foldingModel); + this.localToDispose.add(this.foldingModel); this.hiddenRangeModel = new HiddenRangeModel(this.foldingModel); - this.localToDispose.push(this.hiddenRangeModel); - this.localToDispose.push(this.hiddenRangeModel.onDidChange(hr => this.onHiddenRangesChanges(hr))); + this.localToDispose.add(this.hiddenRangeModel); + this.localToDispose.add(this.hiddenRangeModel.onDidChange(hr => this.onHiddenRangesChanges(hr))); this.updateScheduler = new Delayer(200); this.cursorChangedScheduler = new RunOnceScheduler(() => this.revealCursor(), 200); - this.localToDispose.push(this.cursorChangedScheduler); - this.localToDispose.push(FoldingRangeProviderRegistry.onDidChange(() => this.onFoldingStrategyChanged())); - this.localToDispose.push(this.editor.onDidChangeModelLanguageConfiguration(() => this.onFoldingStrategyChanged())); // covers model language changes as well - this.localToDispose.push(this.editor.onDidChangeModelContent(() => this.onModelContentChanged())); - this.localToDispose.push(this.editor.onDidChangeCursorPosition(() => this.onCursorPositionChanged())); - this.localToDispose.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); - this.localToDispose.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); - this.localToDispose.push({ + this.localToDispose.add(this.cursorChangedScheduler); + this.localToDispose.add(FoldingRangeProviderRegistry.onDidChange(() => this.onFoldingStrategyChanged())); + this.localToDispose.add(this.editor.onDidChangeModelLanguageConfiguration(() => this.onFoldingStrategyChanged())); // covers model language changes as well + this.localToDispose.add(this.editor.onDidChangeModelContent(() => this.onModelContentChanged())); + this.localToDispose.add(this.editor.onDidChangeCursorPosition(() => this.onCursorPositionChanged())); + this.localToDispose.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); + this.localToDispose.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); + this.localToDispose.add({ dispose: () => { if (this.foldingRegionPromise) { this.foldingRegionPromise.cancel(); diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index b363cba00fb5..ecf25033875b 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -6,7 +6,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, registerEditorAction, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -30,18 +30,18 @@ class FormatOnType implements editorCommon.IEditorContribution { private static readonly ID = 'editor.contrib.autoFormat'; private readonly _editor: ICodeEditor; - private _callOnDispose: IDisposable[] = []; - private _callOnModel: IDisposable[] = []; + private readonly _callOnDispose = new DisposableStore(); + private readonly _callOnModel = new DisposableStore(); constructor( editor: ICodeEditor, @IEditorWorkerService private readonly _workerService: IEditorWorkerService ) { this._editor = editor; - this._callOnDispose.push(editor.onDidChangeConfiguration(() => this._update())); - this._callOnDispose.push(editor.onDidChangeModel(() => this._update())); - this._callOnDispose.push(editor.onDidChangeModelLanguage(() => this._update())); - this._callOnDispose.push(OnTypeFormattingEditProviderRegistry.onDidChange(this._update, this)); + this._callOnDispose.add(editor.onDidChangeConfiguration(() => this._update())); + this._callOnDispose.add(editor.onDidChangeModel(() => this._update())); + this._callOnDispose.add(editor.onDidChangeModelLanguage(() => this._update())); + this._callOnDispose.add(OnTypeFormattingEditProviderRegistry.onDidChange(this._update, this)); } getId(): string { @@ -49,14 +49,14 @@ class FormatOnType implements editorCommon.IEditorContribution { } dispose(): void { - this._callOnDispose = dispose(this._callOnDispose); - this._callOnModel = dispose(this._callOnModel); + this._callOnDispose.dispose(); + this._callOnModel.dispose(); } private _update(): void { // clean up - this._callOnModel = dispose(this._callOnModel); + this._callOnModel.clear(); // we are disabled if (!this._editor.getConfiguration().contribInfo.formatOnType) { @@ -81,7 +81,7 @@ class FormatOnType implements editorCommon.IEditorContribution { for (let ch of support.autoFormatTriggerCharacters) { triggerChars.add(ch.charCodeAt(0)); } - this._callOnModel.push(this._editor.onDidType((text: string) => { + this._callOnModel.add(this._editor.onDidType((text: string) => { let lastCharCode = text.charCodeAt(text.length - 1); if (triggerChars.has(lastCharCode)) { this._trigger(String.fromCharCode(lastCharCode)); @@ -156,20 +156,17 @@ class FormatOnPaste implements editorCommon.IEditorContribution { private static readonly ID = 'editor.contrib.formatOnPaste'; - private _callOnDispose: IDisposable[]; - private _callOnModel: IDisposable[]; + private readonly _callOnDispose = new DisposableStore(); + private readonly _callOnModel = new DisposableStore(); constructor( private readonly editor: ICodeEditor, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { - this._callOnDispose = []; - this._callOnModel = []; - - this._callOnDispose.push(editor.onDidChangeConfiguration(() => this._update())); - this._callOnDispose.push(editor.onDidChangeModel(() => this._update())); - this._callOnDispose.push(editor.onDidChangeModelLanguage(() => this._update())); - this._callOnDispose.push(DocumentRangeFormattingEditProviderRegistry.onDidChange(this._update, this)); + this._callOnDispose.add(editor.onDidChangeConfiguration(() => this._update())); + this._callOnDispose.add(editor.onDidChangeModel(() => this._update())); + this._callOnDispose.add(editor.onDidChangeModelLanguage(() => this._update())); + this._callOnDispose.add(DocumentRangeFormattingEditProviderRegistry.onDidChange(this._update, this)); } getId(): string { @@ -177,14 +174,14 @@ class FormatOnPaste implements editorCommon.IEditorContribution { } dispose(): void { - this._callOnDispose = dispose(this._callOnDispose); - this._callOnModel = dispose(this._callOnModel); + this._callOnDispose.dispose(); + this._callOnModel.dispose(); } private _update(): void { // clean up - this._callOnModel = dispose(this._callOnModel); + this._callOnModel.dispose(); // we are disabled if (!this.editor.getConfiguration().contribInfo.formatOnPaste) { @@ -201,7 +198,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution { return; } - this._callOnModel.push(this.editor.onDidPaste(range => this._trigger(range))); + this._callOnModel.add(this.editor.onDidPaste(range => this._trigger(range))); } private _trigger(range: Range): void { diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts index 68f2358f4fa6..4497e20fa7b7 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts @@ -25,7 +25,7 @@ import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition, getDeclarationsAtPosition } from './goToDefinition'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { EditorStateCancellationTokenSource, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; @@ -58,7 +58,7 @@ export class DefinitionAction extends EditorAction { } const notificationService = accessor.get(INotificationService); const editorService = accessor.get(ICodeEditorService); - const progressService = accessor.get(IProgressService); + const progressService = accessor.get(ILocalProgressService); const symbolNavService = accessor.get(ISymbolNavigationService); const model = editor.getModel(); @@ -176,7 +176,6 @@ export class DefinitionAction extends EditorAction { resource: reference.uri, options: { selection: Range.collapseToStart(range), - revealIfOpened: true, revealInCenterIfOutsideViewport: true } }, editor, sideBySide); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts index b3d90178f032..ef5057bd5102 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionResultsNavigation.ts @@ -13,11 +13,11 @@ import { registerEditorCommand, EditorCommand } from 'vs/editor/browser/editorEx import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Range } from 'vs/editor/common/core/range'; -import { Disposable, dispose, combinedDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export const ctxHasSymbols = new RawContextKey('hasSymbols', false); @@ -38,14 +38,14 @@ class SymbolNavigationService implements ISymbolNavigationService { private _currentModel?: ReferencesModel = undefined; private _currentIdx: number = -1; - private _currentDisposables: IDisposable[] = []; - private _currentMessage?: IDisposable = undefined; + private _currentState?: IDisposable; + private _currentMessage?: IDisposable; private _ignoreEditorChange: boolean = false; constructor( @IContextKeyService contextKeyService: IContextKeyService, @ICodeEditorService private readonly _editorService: ICodeEditorService, - @IStatusbarService private readonly _statusbarService: IStatusbarService, + @INotificationService private readonly _notificationService: INotificationService, @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { this._ctxHasSymbols = ctxHasSymbols.bindTo(contextKeyService); @@ -53,7 +53,7 @@ class SymbolNavigationService implements ISymbolNavigationService { reset(): void { this._ctxHasSymbols.reset(); - dispose(this._currentDisposables); + dispose(this._currentState); dispose(this._currentMessage); this._currentModel = undefined; this._currentIdx = -1; @@ -72,8 +72,8 @@ class SymbolNavigationService implements ISymbolNavigationService { this._ctxHasSymbols.set(true); this._showMessage(); - const editorStatus = new EditorStatus(this._editorService); - const listener = editorStatus.onDidChange(_ => { + const editorState = new EditorState(this._editorService); + const listener = editorState.onDidChange(_ => { if (this._ignoreEditorChange) { return; @@ -104,7 +104,7 @@ class SymbolNavigationService implements ISymbolNavigationService { } }); - this._currentDisposables = [editorStatus, listener]; + this._currentState = combinedDisposable(editorState, listener); } revealNext(source: ICodeEditor): Promise { @@ -126,8 +126,7 @@ class SymbolNavigationService implements ISymbolNavigationService { resource: reference.uri, options: { selection: Range.collapseToStart(reference.range), - revealInCenterIfOutsideViewport: true, - revealIfOpened: true + revealInCenterIfOutsideViewport: true } }, source).finally(() => { this._ignoreEditorChange = false; @@ -141,10 +140,10 @@ class SymbolNavigationService implements ISymbolNavigationService { const kb = this._keybindingService.lookupKeybinding('editor.gotoNextSymbolFromResult'); const message = kb - ? localize('location.kb', "Symbol {0} of {1}, press {2} to reveal next", this._currentIdx + 1, this._currentModel!.references.length, kb.getLabel()) + ? localize('location.kb', "Symbol {0} of {1}, {2} for next", this._currentIdx + 1, this._currentModel!.references.length, kb.getLabel()) : localize('location', "Symbol {0} of {1}", this._currentIdx + 1, this._currentModel!.references.length); - this._currentMessage = this._statusbarService.setStatusMessage(message); + this._currentMessage = this._notificationService.status(message); } } @@ -183,26 +182,31 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // -class EditorStatus extends Disposable { +class EditorState { private readonly _listener = new Map(); + private readonly _disposables = new DisposableStore(); private readonly _onDidChange = new Emitter<{ editor: ICodeEditor }>(); readonly onDidChange: Event<{ editor: ICodeEditor }> = this._onDidChange.event; constructor(@ICodeEditorService editorService: ICodeEditorService) { - super(); - this._register(this._onDidChange); - this._register(editorService.onCodeEditorRemove(this._onDidRemoveEditor, this)); - this._register(editorService.onCodeEditorAdd(this._onDidAddEditor, this)); + this._disposables.add(editorService.onCodeEditorRemove(this._onDidRemoveEditor, this)); + this._disposables.add(editorService.onCodeEditorAdd(this._onDidAddEditor, this)); editorService.listCodeEditors().forEach(this._onDidAddEditor, this); } + dispose(): void { + this._disposables.dispose(); + this._onDidChange.dispose(); + this._listener.forEach(dispose); + } + private _onDidAddEditor(editor: ICodeEditor): void { - this._listener.set(editor, combinedDisposable([ + this._listener.set(editor, combinedDisposable( editor.onDidChangeCursorPosition(_ => this._onDidChange.fire({ editor })), editor.onDidChangeModelContent(_ => this._onDidChange.fire({ editor })), - ])); + )); } private _onDidRemoveEditor(editor: ICodeEditor): void { diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 9ed7491b5065..d67b01c0b711 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -6,15 +6,14 @@ import 'vs/css!./media/gotoErrorWidget'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { registerColor, oneOf, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { registerColor, oneOf, textLinkForeground, editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoForeground, editorInfoBorder } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; -import { editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoForeground, editorInfoBorder } from 'vs/editor/common/view/editorColorRegistry'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ScrollType } from 'vs/editor/common/editorCommon'; @@ -27,6 +26,7 @@ import { IAction } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; class MessageWidget { @@ -168,8 +168,9 @@ export class MarkerNavigationWidget extends PeekViewWidget { private _parentContainer: HTMLElement; private _container: HTMLElement; + private _icon: HTMLElement; private _message: MessageWidget; - private _callOnDispose: IDisposable[] = []; + private readonly _callOnDispose = new DisposableStore(); private _severity: MarkerSeverity; private _backgroundColor?: Color; private _onDidSelectRelatedInformation = new Emitter(); @@ -179,7 +180,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { constructor( editor: ICodeEditor, - private readonly actions: IAction[], + private readonly actions: ReadonlyArray, private readonly _themeService: IThemeService ) { super(editor, { showArrow: true, showFrame: true, isAccessible: true }); @@ -187,7 +188,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._backgroundColor = Color.white; this._applyTheme(_themeService.getTheme()); - this._callOnDispose.push(_themeService.onThemeChange(this._applyTheme.bind(this))); + this._callOnDispose.add(_themeService.onThemeChange(this._applyTheme.bind(this))); this.create(); } @@ -218,7 +219,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { } dispose(): void { - this._callOnDispose = dispose(this._callOnDispose); + this._callOnDispose.dispose(); super.dispose(); } @@ -231,6 +232,10 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._actionbarWidget.push(this.actions, { label: false, icon: true }); } + protected _fillTitleIcon(container: HTMLElement): void { + this._icon = dom.append(container, dom.$('')); + } + protected _getActionBarOptions(): IActionBarOptions { return { orientation: ActionsOrientation.HORIZONTAL_REVERSE @@ -247,7 +252,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { container.appendChild(this._container); this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related)); - this._disposables.push(this._message); + this._disposables.add(this._message); } show(where: Position, heightInLines: number): void { @@ -278,13 +283,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { : nls.localize('change', "{0} of {1} problem", markerIdx, markerCount); this.setTitle(basename(model.uri), detail); } - let headingIconClassName = 'error'; - if (this._severity === MarkerSeverity.Warning) { - headingIconClassName = 'warning'; - } else if (this._severity === MarkerSeverity.Info) { - headingIconClassName = 'info'; - } - this.setTitleIcon(headingIconClassName); + this._icon.className = SeverityIcon.className(MarkerSeverity.toSeverity(this._severity)); this.editor.revealPositionInCenter(position, ScrollType.Smooth); diff --git a/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css index d24b98149ff6..1a0be0be1aac 100644 --- a/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css +++ b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css @@ -5,28 +5,10 @@ /* marker zone */ -.monaco-editor .peekview-widget .head .peekview-title .icon.warning { - background: url('status-warning.svg') center center no-repeat; -} - -.monaco-editor .peekview-widget .head .peekview-title .icon.error { - background: url('status-error.svg') center center no-repeat; -} - -.monaco-editor .peekview-widget .head .peekview-title .icon.info { - background: url('status-info.svg') center center no-repeat; -} - -.vs-dark .monaco-editor .peekview-widget .head .peekview-title .icon.warning { - background: url('status-warning-inverse.svg') center center no-repeat; -} - -.vs-dark .monaco-editor .peekview-widget .head .peekview-title .icon.error { - background: url('status-error-inverse.svg') center center no-repeat; -} - -.vs-dark .monaco-editor .peekview-widget .head .peekview-title .icon.info { - background: url('status-info-inverse.svg') center center no-repeat; +.monaco-editor .peekview-widget .head .peekview-title .severity-icon { + display: inline-block; + vertical-align: text-top; + margin-right: 4px; } .monaco-editor .marker-widget { diff --git a/src/vs/editor/contrib/gotoError/media/status-error-inverse.svg b/src/vs/editor/contrib/gotoError/media/status-error-inverse.svg deleted file mode 100644 index 3c852a7ffde9..000000000000 --- a/src/vs/editor/contrib/gotoError/media/status-error-inverse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/editor/contrib/gotoError/media/status-error.svg b/src/vs/editor/contrib/gotoError/media/status-error.svg deleted file mode 100644 index a1ddb39fed6a..000000000000 --- a/src/vs/editor/contrib/gotoError/media/status-error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/editor/contrib/gotoError/media/status-info-inverse.svg b/src/vs/editor/contrib/gotoError/media/status-info-inverse.svg deleted file mode 100644 index d38c363e0e4c..000000000000 --- a/src/vs/editor/contrib/gotoError/media/status-info-inverse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/editor/contrib/gotoError/media/status-info.svg b/src/vs/editor/contrib/gotoError/media/status-info.svg deleted file mode 100644 index 6e2e22f67bc9..000000000000 --- a/src/vs/editor/contrib/gotoError/media/status-info.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/editor/contrib/gotoError/media/status-warning-inverse.svg b/src/vs/editor/contrib/gotoError/media/status-warning-inverse.svg deleted file mode 100644 index df44e61b3265..000000000000 --- a/src/vs/editor/contrib/gotoError/media/status-warning-inverse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/editor/contrib/gotoError/media/status-warning.svg b/src/vs/editor/contrib/gotoError/media/status-warning.svg deleted file mode 100644 index f4e2a84b0af4..000000000000 --- a/src/vs/editor/contrib/gotoError/media/status-warning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index bca65f0fa71c..abdfcce355ca 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -7,7 +7,7 @@ import 'vs/css!./hover'; import * as nls from 'vs/nls'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEmptyContentData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -34,7 +34,7 @@ export class ModesHoverController implements IEditorContribution { private static readonly ID = 'editor.contrib.hover'; - private _toUnhook: IDisposable[]; + private readonly _toUnhook = new DisposableStore(); private readonly _didChangeConfigurationHandler: IDisposable; private _contentWidget: ModesContentHoverWidget; @@ -73,8 +73,6 @@ export class ModesHoverController implements IEditorContribution { @ICommandService private readonly _commandService: ICommandService, @IThemeService private readonly _themeService: IThemeService ) { - this._toUnhook = []; - this._isMouseDown = false; this._hoverClicked = false; @@ -96,22 +94,22 @@ export class ModesHoverController implements IEditorContribution { this._isHoverEnabled = hoverOpts.enabled; this._isHoverSticky = hoverOpts.sticky; if (this._isHoverEnabled) { - this._toUnhook.push(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); - this._toUnhook.push(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); - this._toUnhook.push(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); - this._toUnhook.push(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); - this._toUnhook.push(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); + this._toUnhook.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e))); + this._toUnhook.add(this._editor.onMouseUp((e: IEditorMouseEvent) => this._onEditorMouseUp(e))); + this._toUnhook.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e))); + this._toUnhook.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e))); + this._toUnhook.add(this._editor.onDidChangeModelDecorations(() => this._onModelDecorationsChanged())); } else { - this._toUnhook.push(this._editor.onMouseMove(hideWidgetsEventHandler)); + this._toUnhook.add(this._editor.onMouseMove(hideWidgetsEventHandler)); } - this._toUnhook.push(this._editor.onMouseLeave(hideWidgetsEventHandler)); - this._toUnhook.push(this._editor.onDidChangeModel(hideWidgetsEventHandler)); - this._toUnhook.push(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e))); + this._toUnhook.add(this._editor.onMouseLeave(hideWidgetsEventHandler)); + this._toUnhook.add(this._editor.onDidChangeModel(hideWidgetsEventHandler)); + this._toUnhook.add(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e))); } private _unhookEvents(): void { - this._toUnhook = dispose(this._toUnhook); + this._toUnhook.clear(); } private _onModelDecorationsChanged(): void { @@ -227,6 +225,7 @@ export class ModesHoverController implements IEditorContribution { public dispose(): void { this._unhookEvents(); + this._toUnhook.dispose(); this._didChangeConfigurationHandler.dispose(); if (this._glyphWidget) { diff --git a/src/vs/editor/contrib/hover/hoverWidgets.ts b/src/vs/editor/contrib/hover/hoverWidgets.ts index ece230a25cc7..7ff6243bce7c 100644 --- a/src/vs/editor/contrib/hover/hoverWidgets.ts +++ b/src/vs/editor/contrib/hover/hoverWidgets.ts @@ -8,7 +8,6 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Widget } from 'vs/base/browser/ui/widget'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; @@ -25,7 +24,6 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent protected _showAtRange: Range | null; private _stoleFocus: boolean; private readonly scrollbar: DomScrollableElement; - private disposables: IDisposable[] = []; // Editor.IContentWidget.allowEditorOverflow public allowEditorOverflow = true; @@ -53,7 +51,7 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent this._domNode.className = 'monaco-editor-hover-content'; this.scrollbar = new DomScrollableElement(this._domNode, {}); - this.disposables.push(this.scrollbar); + this._register(this.scrollbar); this._containerDomNode.appendChild(this.scrollbar.getDomNode()); this.onkeydown(this._containerDomNode, (e: IKeyboardEvent) => { @@ -129,7 +127,6 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent public dispose(): void { this._editor.removeContentWidget(this); - this.disposables = dispose(this.disposables); super.dispose(); } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 966de575cc7e..8add6d787de7 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { IMarkdownString, MarkdownString, isEmptyMarkdownString, markedStringsEquals } from 'vs/base/common/htmlContent'; -import { Disposable, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -187,6 +187,10 @@ class ModesContentComputer implements IHoverComputer { } } +interface ActionSet extends IDisposable { + readonly actions: Action[]; +} + export class ModesContentHoverWidget extends ContentHoverWidget { static readonly ID = 'editor.contrib.modesContentHoverWidget'; @@ -436,7 +440,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this.updateContents(fragment); this._colorPicker.layout(); - this.renderDisposable = combinedDisposable([colorListener, colorChangeListener, widget, ...markdownDisposeables]); + this.renderDisposable = combinedDisposable(colorListener, colorChangeListener, widget, ...markdownDisposeables); }); } else { if (msg instanceof MarkerHover) { @@ -526,24 +530,25 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private renderMarkerStatusbar(markerHover: MarkerHover): HTMLElement { const hoverElement = $('div.hover-row.status-bar'); - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const actionsElement = dom.append(hoverElement, $('div.actions')); - disposables.push(this.renderAction(actionsElement, { + disposables.add(this.renderAction(actionsElement, { label: nls.localize('quick fixes', "Quick Fix..."), commandId: QuickFixAction.Id, run: async (target) => { const codeActionsPromise = this.getCodeActions(markerHover.marker); - disposables.push(toDisposable(() => codeActionsPromise.cancel())); + disposables.add(toDisposable(() => codeActionsPromise.cancel())); const actions = await codeActionsPromise; + disposables.add(actions); const elementPosition = dom.getDomNodePagePosition(target); this._contextMenuService.showContextMenu({ getAnchor: () => ({ x: elementPosition.left + 6, y: elementPosition.top + elementPosition.height + 6 }), - getActions: () => actions + getActions: () => actions.actions }); } })); if (markerHover.marker.severity === MarkerSeverity.Error || markerHover.marker.severity === MarkerSeverity.Warning || markerHover.marker.severity === MarkerSeverity.Info) { - disposables.push(this.renderAction(actionsElement, { + disposables.add(this.renderAction(actionsElement, { label: nls.localize('peek problem', "Peek Problem"), commandId: NextMarkerAction.ID, run: () => { @@ -553,24 +558,37 @@ export class ModesContentHoverWidget extends ContentHoverWidget { } })); } - this.renderDisposable = combinedDisposable(disposables); + this.renderDisposable = disposables; return hoverElement; } - private getCodeActions(marker: IMarker): CancelablePromise { + private getCodeActions(marker: IMarker): CancelablePromise { return createCancelablePromise(async cancellationToken => { const codeActions = await getCodeActions(this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken); if (codeActions.actions.length) { - return codeActions.actions.map(codeAction => new Action( - codeAction.command ? codeAction.command.id : codeAction.title, - codeAction.title, - undefined, - true, - () => applyCodeAction(codeAction, this._bulkEditService, this._commandService))); + const disposables = new DisposableStore(); + const actions: Action[] = []; + for (const codeAction of codeActions.actions) { + disposables.add(disposables); + actions.push(new Action( + codeAction.command ? codeAction.command.id : codeAction.title, + codeAction.title, + undefined, + true, + () => applyCodeAction(codeAction, this._bulkEditService, this._commandService))); + } + return { + actions: actions, + dispose: () => disposables.dispose() + }; } - return [ - new Action('', nls.localize('editor.action.quickFix.noneMessage', "No code actions available")) - ]; + + return { + actions: [ + new Action('', nls.localize('editor.action.quickFix.noneMessage', "No code actions available")) + ], + dispose() { } + }; }); } diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index b8d0ea3b425e..883b00ba04f7 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -27,7 +27,8 @@ export class Link implements ILink { toJSON(): ILink { return { range: this.range, - url: this.url + url: this.url, + tooltip: this.tooltip }; } @@ -39,6 +40,10 @@ export class Link implements ILink { return this._link.url; } + get tooltip(): string | undefined { + return this._link.tooltip; + } + resolve(token: CancellationToken): Promise { if (this._link.url) { try { @@ -143,7 +148,14 @@ export function getLinks(model: ITextModel, token: CancellationToken): Promise new LinksList(coalesce(lists))); + return Promise.all(promises).then(() => { + const result = new LinksList(coalesce(lists)); + if (!token.isCancellationRequested) { + return result; + } + result.dispose(); + return new LinksList([]); + }); } diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index f4aee3992ff8..a50c984ab6ac 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -111,6 +111,23 @@ class LinkOccurrence { } private static _getOptions(link: Link, useMetaKey: boolean, isActive: boolean): ModelDecorationOptions { + const options = { ...this._getBaseOptions(link, useMetaKey, isActive) }; + if (typeof link.tooltip === 'string') { + const message = new MarkdownString().appendText( + platform.isMacintosh + ? useMetaKey + ? nls.localize('links.custom.mac', "Cmd + click to {0}", link.tooltip) + : nls.localize('links.custom.mac.al', "Option + click to {0}", link.tooltip) + : useMetaKey + ? nls.localize('links.custom', "Ctrl + click to {0}", link.tooltip) + : nls.localize('links.custom.al', "Alt + click to {0}", link.tooltip) + ); + options.hoverMessage = message; + } + return options; + } + + private static _getBaseOptions(link: Link, useMetaKey: boolean, isActive: boolean): ModelDecorationOptions { if (link.url && /^command:/i.test(link.url.toString())) { if (useMetaKey) { return (isActive ? decoration.metaCommandActive : decoration.metaCommand); diff --git a/src/vs/editor/contrib/markdown/markdownRenderer.ts b/src/vs/editor/contrib/markdown/markdownRenderer.ts index 405eb1c8c1f8..531723bbf01b 100644 --- a/src/vs/editor/contrib/markdown/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdown/markdownRenderer.ts @@ -78,7 +78,7 @@ export class MarkdownRenderer { } render(markdown: IMarkdownString | undefined): IMarkdownRenderResult { - let disposeables: IDisposable[] = []; + const disposeables: IDisposable[] = []; let element: HTMLElement; if (!markdown) { diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index b500bd94d1da..7ce401349f6c 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { RevealTarget } from 'vs/editor/common/controller/cursorCommon'; @@ -427,7 +427,7 @@ export class MultiCursorSelectionController extends Disposable implements IEdito private readonly _editor: ICodeEditor; private _ignoreSelectionChange: boolean; private _session: MultiCursorSession | null; - private _sessionDispose: IDisposable[]; + private readonly _sessionDispose = this._register(new DisposableStore()); public static get(editor: ICodeEditor): MultiCursorSelectionController { return editor.getContribution(MultiCursorSelectionController.ID); @@ -438,7 +438,6 @@ export class MultiCursorSelectionController extends Disposable implements IEdito this._editor = editor; this._ignoreSelectionChange = false; this._session = null; - this._sessionDispose = []; } public dispose(): void { @@ -468,27 +467,25 @@ export class MultiCursorSelectionController extends Disposable implements IEdito } findController.getState().change(newState, false); - this._sessionDispose = [ - this._editor.onDidChangeCursorSelection((e) => { - if (this._ignoreSelectionChange) { - return; - } - this._endSession(); - }), - this._editor.onDidBlurEditorText(() => { + this._sessionDispose.add(this._editor.onDidChangeCursorSelection((e) => { + if (this._ignoreSelectionChange) { + return; + } + this._endSession(); + })); + this._sessionDispose.add(this._editor.onDidBlurEditorText(() => { + this._endSession(); + })); + this._sessionDispose.add(findController.getState().onFindReplaceStateChange((e) => { + if (e.matchCase || e.wholeWord) { this._endSession(); - }), - findController.getState().onFindReplaceStateChange((e) => { - if (e.matchCase || e.wholeWord) { - this._endSession(); - } - }) - ]; + } + })); } } private _endSession(): void { - this._sessionDispose = dispose(this._sessionDispose); + this._sessionDispose.clear(); if (this._session && this._session.isDisconnectedFromFindController) { const newState: INewFindReplaceState = { wholeWordOverride: FindOptionOverride.NotSet, diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index b105b4301985..7b57c130a3df 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -8,7 +8,7 @@ import { domEvent, stop } from 'vs/base/browser/event'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Event } from 'vs/base/common/event'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./parameterHints'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; @@ -25,12 +25,12 @@ import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameter const $ = dom.$; -export class ParameterHintsWidget implements IContentWidget, IDisposable { +export class ParameterHintsWidget extends Disposable implements IContentWidget, IDisposable { private static readonly ID = 'editor.widget.parameterHintsWidget'; private readonly markdownRenderer: MarkdownRenderer; - private renderDisposeables: IDisposable[]; + private readonly renderDisposeables = this._register(new DisposableStore()); private model: ParameterHintsModel | null; private readonly keyVisible: IContextKey; private readonly keyMultipleSignatures: IContextKey; @@ -41,7 +41,6 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { private visible: boolean; private announcedLabel: string | null; private scrollbar: DomScrollableElement; - private disposables: IDisposable[]; // Editor.IContentWidget.allowEditorOverflow allowEditorOverflow = true; @@ -52,14 +51,14 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { @IOpenerService openerService: IOpenerService, @IModeService modeService: IModeService, ) { + super(); this.markdownRenderer = new MarkdownRenderer(editor, modeService, openerService); this.model = new ParameterHintsModel(editor); this.keyVisible = Context.Visible.bindTo(contextKeyService); this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService); this.visible = false; - this.disposables = []; - this.disposables.push(this.model.onChangedHints(newParameterHints => { + this._register(this.model.onChangedHints(newParameterHints => { if (newParameterHints) { this.show(); this.render(newParameterHints); @@ -79,16 +78,16 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { const next = dom.append(buttons, $('.button.next')); const onPreviousClick = stop(domEvent(previous, 'click')); - onPreviousClick(this.previous, this, this.disposables); + this._register(onPreviousClick(this.previous, this)); const onNextClick = stop(domEvent(next, 'click')); - onNextClick(this.next, this, this.disposables); + this._register(onNextClick(this.next, this)); this.overloads = dom.append(wrapper, $('.overloads')); const body = $('.body'); this.scrollbar = new DomScrollableElement(body, {}); - this.disposables.push(this.scrollbar); + this._register(this.scrollbar); wrapper.appendChild(this.scrollbar.getDomNode()); this.signature = dom.append(body, $('.signature')); @@ -99,7 +98,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { this.hide(); this.element.style.userSelect = 'text'; - this.disposables.push(this.editor.onDidChangeCursorSelection(e => { + this._register(this.editor.onDidChangeCursorSelection(e => { if (this.visible) { this.editor.layoutContentWidget(this); } @@ -112,11 +111,11 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { updateFont(); - Event.chain(this.editor.onDidChangeConfiguration.bind(this.editor)) + this._register(Event.chain(this.editor.onDidChangeConfiguration.bind(this.editor)) .filter(e => e.fontInfo) - .on(updateFont, null, this.disposables); + .on(updateFont, null)); - this.disposables.push(this.editor.onDidLayoutChange(e => this.updateMaxHeight())); + this._register(this.editor.onDidLayoutChange(e => this.updateMaxHeight())); this.updateMaxHeight(); } @@ -190,8 +189,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { this.renderParameters(code, signature, hints.activeParameter); } - dispose(this.renderDisposeables); - this.renderDisposeables = []; + this.renderDisposeables.clear(); const activeParameter = signature.parameters[hints.activeParameter]; @@ -202,7 +200,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { } else { const renderedContents = this.markdownRenderer.render(activeParameter.documentation); dom.addClass(renderedContents.element, 'markdown-docs'); - this.renderDisposeables.push(renderedContents); + this.renderDisposeables.add(renderedContents); documentation.appendChild(renderedContents.element); } dom.append(this.docs, $('p', {}, documentation)); @@ -216,7 +214,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { } else { const renderedContents = this.markdownRenderer.render(signature.documentation); dom.addClass(renderedContents.element, 'markdown-docs'); - this.renderDisposeables.push(renderedContents); + this.renderDisposeables.add(renderedContents); dom.append(this.docs, renderedContents.element); } @@ -323,8 +321,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { } dispose(): void { - this.disposables = dispose(this.disposables); - this.renderDisposeables = dispose(this.renderDisposeables); + super.dispose(); if (this.model) { this.model.dispose(); diff --git a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css b/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css index 6d46559fe468..250acc6cc2a3 100644 --- a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css +++ b/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css @@ -19,14 +19,6 @@ cursor: pointer; } -.monaco-editor .peekview-widget .head .peekview-title .icon { - display: inline-block; - height: 16px; - width: 16px; - vertical-align: text-bottom; - margin-right: 4px; -} - .monaco-editor .peekview-widget .head .peekview-title .dirname:not(:empty) { font-size: 0.9em; margin-left: 0.5em; diff --git a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts index 935a9c68f13e..c93dfab47941 100644 --- a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts @@ -85,7 +85,6 @@ export abstract class PeekViewWidget extends ZoneWidget { private _onDidClose = new Emitter(); protected _headElement: HTMLDivElement; - protected _headingIcon: HTMLElement; protected _primaryHeading: HTMLElement; protected _secondaryHeading: HTMLElement; protected _metaHeading: HTMLElement; @@ -155,18 +154,18 @@ export abstract class PeekViewWidget extends ZoneWidget { dom.append(this._headElement, titleElement); dom.addStandardDisposableListener(titleElement, 'click', event => this._onTitleClick(event)); - this._headingIcon = dom.$('span'); + this._fillTitleIcon(titleElement); this._primaryHeading = dom.$('span.filename'); this._secondaryHeading = dom.$('span.dirname'); this._metaHeading = dom.$('span.meta'); - dom.append(titleElement, this._headingIcon, this._primaryHeading, this._secondaryHeading, this._metaHeading); + dom.append(titleElement, this._primaryHeading, this._secondaryHeading, this._metaHeading); const actionsContainer = dom.$('.peekview-actions'); dom.append(this._headElement, actionsContainer); const actionBarOptions = this._getActionBarOptions(); this._actionbarWidget = new ActionBar(actionsContainer, actionBarOptions); - this._disposables.push(this._actionbarWidget); + this._disposables.add(this._actionbarWidget); this._actionbarWidget.push(new Action('peekview.close', nls.localize('label.close', "Close"), 'close-peekview-action', true, () => { this.dispose(); @@ -174,6 +173,9 @@ export abstract class PeekViewWidget extends ZoneWidget { }), { label: false, icon: true }); } + protected _fillTitleIcon(container: HTMLElement): void { + } + protected _getActionBarOptions(): IActionBarOptions { return {}; } @@ -182,10 +184,6 @@ export abstract class PeekViewWidget extends ZoneWidget { // implement me } - public setTitleIcon(iconClassName: string): void { - this._headingIcon.className = iconClassName ? `icon ${iconClassName}` : ''; - } - public setTitle(primaryHeading: string, secondaryHeading?: string): void { this._primaryHeading.innerHTML = strings.escape(primaryHeading); this._primaryHeading.setAttribute('aria-label', primaryHeading); diff --git a/src/vs/editor/contrib/referenceSearch/referencesController.ts b/src/vs/editor/contrib/referenceSearch/referencesController.ts index 2f714b86bb64..5e230bac052d 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesController.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -32,11 +32,11 @@ export abstract class ReferencesController implements editorCommon.IEditorContri private static readonly ID = 'editor.contrib.referencesController'; + private readonly _disposables = new DisposableStore(); private readonly _editor: ICodeEditor; private _widget: ReferenceWidget | null; private _model: ReferencesModel | null; private _requestIdPool = 0; - private _disposables: IDisposable[] = []; private _ignoreModelChangeEvent = false; private readonly _referenceSearchVisible: IContextKey; @@ -91,8 +91,8 @@ export abstract class ReferencesController implements editorCommon.IEditorContri this._referenceSearchVisible.set(true); // close the widget on model/mode changes - this._disposables.push(this._editor.onDidChangeModelLanguage(() => { this.closeWidget(); })); - this._disposables.push(this._editor.onDidChangeModel(() => { + this._disposables.add(this._editor.onDidChangeModelLanguage(() => { this.closeWidget(); })); + this._disposables.add(this._editor.onDidChangeModel(() => { if (!this._ignoreModelChangeEvent) { this.closeWidget(); } @@ -103,7 +103,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri this._widget.setTitle(nls.localize('labelLoading', "Loading...")); this._widget.show(range); - this._disposables.push(this._widget.onDidClose(() => { + this._disposables.add(this._widget.onDidClose(() => { modelPromise.cancel(); if (this._widget) { this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.GLOBAL); @@ -112,7 +112,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri this.closeWidget(); })); - this._disposables.push(this._widget.onDidSelectReference(event => { + this._disposables.add(this._widget.onDidSelectReference(event => { let { element, kind } = event; switch (kind) { case 'open': @@ -205,7 +205,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri this._widget = null; } this._referenceSearchVisible.reset(); - this._disposables = dispose(this._disposables); + this._disposables.clear(); if (this._model) { dispose(this._model); this._model = null; diff --git a/src/vs/editor/contrib/referenceSearch/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/referencesModel.ts index f53a84c88ab7..f6a651ecc44f 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesModel.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesModel.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { basename } from 'vs/base/common/resources'; -import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, IReference, DisposableStore } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { defaultGenerator } from 'vs/base/common/idGenerator'; @@ -14,6 +14,7 @@ import { Range, IRange } from 'vs/editor/common/core/range'; import { Location, LocationLink } from 'vs/editor/common/modes'; import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { Position } from 'vs/editor/common/core/position'; +import { IMatch } from 'vs/base/common/filters'; export class OneReference { readonly id: string; @@ -61,7 +62,7 @@ export class FilePreview implements IDisposable { dispose(this._modelReference); } - preview(range: IRange, n: number = 8): { before: string; inside: string; after: string } | undefined { + preview(range: IRange, n: number = 8): { value: string; highlight: IMatch } | undefined { const model = this._modelReference.object.textEditorModel; if (!model) { @@ -73,13 +74,14 @@ export class FilePreview implements IDisposable { const beforeRange = new Range(startLineNumber, word.startColumn, startLineNumber, startColumn); const afterRange = new Range(endLineNumber, endColumn, endLineNumber, Number.MAX_VALUE); - const ret = { - before: model.getValueInRange(beforeRange).replace(/^\s+/, strings.empty), - inside: model.getValueInRange(range), - after: model.getValueInRange(afterRange).replace(/\s+$/, strings.empty) - }; + const before = model.getValueInRange(beforeRange).replace(/^\s+/, strings.empty); + const inside = model.getValueInRange(range); + const after = model.getValueInRange(afterRange).replace(/\s+$/, strings.empty); - return ret; + return { + value: before + inside + after, + highlight: { start: before.length, end: before.length + inside.length } + }; } } @@ -164,7 +166,7 @@ export class FileReferences implements IDisposable { export class ReferencesModel implements IDisposable { - private readonly _disposables: IDisposable[]; + private readonly _disposables = new DisposableStore(); readonly groups: FileReferences[] = []; readonly references: OneReference[] = []; @@ -172,7 +174,7 @@ export class ReferencesModel implements IDisposable { readonly onDidChangeReferenceRange: Event = this._onDidChangeReferenceRange.event; constructor(references: LocationLink[]) { - this._disposables = []; + // grouping and sorting const [providersFirst] = references; references.sort(ReferencesModel._compareReferences); @@ -190,7 +192,7 @@ export class ReferencesModel implements IDisposable { || !Range.equalsRange(ref.range, current.children[current.children.length - 1].range)) { let oneRef = new OneReference(current, ref.targetSelectionRange || ref.range, providersFirst === ref); - this._disposables.push(oneRef.onRefChanged((e) => this._onDidChangeReferenceRange.fire(e))); + this._disposables.add(oneRef.onRefChanged((e) => this._onDidChangeReferenceRange.fire(e))); this.references.push(oneRef); current.children.push(oneRef); } @@ -280,9 +282,8 @@ export class ReferencesModel implements IDisposable { dispose(): void { dispose(this.groups); - dispose(this._disposables); + this._disposables.dispose(); this.groups.length = 0; - this._disposables.length = 0; } private static _compareReferences(a: Location, b: Location): number { diff --git a/src/vs/editor/contrib/referenceSearch/referencesTree.ts b/src/vs/editor/contrib/referenceSearch/referencesTree.ts index 40778caf2de5..d10cbd643016 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesTree.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesTree.ts @@ -15,7 +15,6 @@ import * as dom from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { getBaseLabel } from 'vs/base/common/labels'; import { dirname, basename } from 'vs/base/common/resources'; -import { escape } from 'vs/base/common/strings'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; @@ -23,6 +22,7 @@ import { IListVirtualDelegate, IKeyboardNavigationLabelProvider, IIdentityProvid import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { FuzzyScore, createMatches, IMatch } from 'vs/base/common/filters'; +import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; //#region data source @@ -82,8 +82,14 @@ export class StringRepresentationProvider implements IKeyboardNavigationLabelPro constructor(@IKeybindingService private readonly _keybindingService: IKeybindingService) { } getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } { - // todo@joao `OneReference` elements are lazy and their "real" label - // isn't known yet + if (element instanceof OneReference) { + const { preview } = element.parent; + const parts = preview && preview.preview(element.range); + if (parts) { + return parts.value; + } + } + // FileReferences or unresolved OneReference return basename(element.uri); } @@ -161,31 +167,29 @@ export class FileReferencesRenderer implements ITreeRenderer, index: number, templateData: OneReferenceTemplate): void { - templateData.set(element.element); + renderElement(node: ITreeNode, index: number, templateData: OneReferenceTemplate): void { + templateData.set(node.element, node.filterData); } disposeTemplate(): void { - // } } diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index ee15970a603d..5f15ead33f7d 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -8,7 +8,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; -import { dispose, IDisposable, IReference } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, IReference, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; import 'vs/css!./media/referencesWidget'; @@ -47,22 +47,22 @@ class DecorationsManager implements IDisposable { private _decorations = new Map(); private _decorationIgnoreSet = new Set(); - private _callOnDispose: IDisposable[] = []; - private _callOnModelChange: IDisposable[] = []; + private readonly _callOnDispose = new DisposableStore(); + private readonly _callOnModelChange = new DisposableStore(); constructor(private _editor: ICodeEditor, private _model: ReferencesModel) { - this._callOnDispose.push(this._editor.onDidChangeModel(() => this._onModelChanged())); + this._callOnDispose.add(this._editor.onDidChangeModel(() => this._onModelChanged())); this._onModelChanged(); } public dispose(): void { - this._callOnModelChange = dispose(this._callOnModelChange); - this._callOnDispose = dispose(this._callOnDispose); + this._callOnModelChange.dispose(); + this._callOnDispose.dispose(); this.removeDecorations(); } private _onModelChanged(): void { - this._callOnModelChange = dispose(this._callOnModelChange); + this._callOnModelChange.clear(); const model = this._editor.getModel(); if (model) { for (const ref of this._model.groups) { @@ -78,7 +78,7 @@ class DecorationsManager implements IDisposable { if (!this._editor.hasModel()) { return; } - this._callOnModelChange.push(this._editor.getModel().onDidChangeDecorations((event) => this._onDecorationChanged())); + this._callOnModelChange.add(this._editor.getModel().onDidChangeDecorations((event) => this._onDecorationChanged())); const newDecorations: IModelDeltaDecoration[] = []; const newDecorationsActualIndex: number[] = []; @@ -194,8 +194,8 @@ export class ReferenceWidget extends PeekViewWidget { private _model: ReferencesModel | undefined; private _decorationsManager: DecorationsManager; - private _disposeOnNewModel: IDisposable[] = []; - private _callOnDispose: IDisposable[] = []; + private readonly _disposeOnNewModel = new DisposableStore(); + private readonly _callOnDispose = new DisposableStore(); private _onDidSelectReference = new Emitter(); private _tree: WorkbenchAsyncDataTree; @@ -222,15 +222,19 @@ export class ReferenceWidget extends PeekViewWidget { super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true }); this._applyTheme(themeService.getTheme()); - this._callOnDispose.push(themeService.onThemeChange(this._applyTheme.bind(this))); + this._callOnDispose.add(themeService.onThemeChange(this._applyTheme.bind(this))); this._peekViewService.addExclusiveWidget(editor, this); this.create(); } dispose(): void { this.setModel(undefined); - this._callOnDispose = dispose(this._callOnDispose); - dispose(this._preview, this._previewNotAvailableMessage, this._tree, this._previewModelReference); + this._callOnDispose.dispose(); + this._disposeOnNewModel.dispose(); + dispose(this._preview); + dispose(this._previewNotAvailableMessage); + dispose(this._tree); + dispose(this._previewModelReference); this._splitView.dispose(); super.dispose(); } @@ -344,11 +348,11 @@ export class ReferenceWidget extends PeekViewWidget { } }, Sizing.Distribute); - this._splitView.onDidSashChange(() => { + this._disposables.add(this._splitView.onDidSashChange(() => { if (this._dim.width) { this.layoutData.ratio = this._splitView.getViewSize(0) / this._dim.width; } - }, undefined, this._disposables); + }, undefined)); // listen on selection and focus let onEvent = (element: any, kind: 'show' | 'goto' | 'side') => { @@ -421,7 +425,7 @@ export class ReferenceWidget extends PeekViewWidget { public setModel(newModel: ReferencesModel | undefined): Promise { // clean up - this._disposeOnNewModel = dispose(this._disposeOnNewModel); + this._disposeOnNewModel.clear(); this._model = newModel; if (this._model) { return this._onNewModel(); @@ -443,13 +447,13 @@ export class ReferenceWidget extends PeekViewWidget { dom.hide(this._messageContainer); this._decorationsManager = new DecorationsManager(this._preview, this._model); - this._disposeOnNewModel.push(this._decorationsManager); + this._disposeOnNewModel.add(this._decorationsManager); // listen on model changes - this._disposeOnNewModel.push(this._model.onDidChangeReferenceRange(reference => this._tree.rerender(reference))); + this._disposeOnNewModel.add(this._model.onDidChangeReferenceRange(reference => this._tree.rerender(reference))); // listen on editor - this._disposeOnNewModel.push(this._preview.onMouseDown(e => { + this._disposeOnNewModel.add(this._preview.onMouseDown(e => { const { event, target } = e; if (event.detail !== 2) { return; @@ -566,7 +570,7 @@ export const peekViewEditorMatchHighlightBorder = registerColor('peekViewEditor. registerThemingParticipant((theme, collector) => { const findMatchHighlightColor = theme.getColor(peekViewResultsMatchHighlight); if (findMatchHighlightColor) { - collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch { background-color: ${findMatchHighlightColor}; }`); + collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { background-color: ${findMatchHighlightColor}; }`); } const referenceHighlightColor = theme.getColor(peekViewEditorMatchHighlight); if (referenceHighlightColor) { @@ -578,7 +582,7 @@ registerThemingParticipant((theme, collector) => { } const hcOutline = theme.getColor(activeContrastBorder); if (hcOutline) { - collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch { border: 1px dotted ${hcOutline}; box-sizing: border-box; }`); + collector.addRule(`.monaco-editor .reference-zone-widget .ref-tree .referenceMatch .highlight { border: 1px dotted ${hcOutline}; box-sizing: border-box; }`); } const resultsBackground = theme.getColor(peekViewResultsBackground); if (resultsBackground) { diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index e1d6bed0c475..2069ca47a5ec 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction, EditorCommand, registerEditorCommand, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; @@ -114,7 +114,7 @@ class RenameController extends Disposable implements IEditorContribution { private readonly editor: ICodeEditor, @INotificationService private readonly _notificationService: INotificationService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, - @IProgressService private readonly _progressService: IProgressService, + @ILocalProgressService private readonly _progressService: ILocalProgressService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IThemeService private readonly _themeService: IThemeService, ) { diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index 79961cb674fb..3b6042c5f9d6 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./renameInputField'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -24,10 +24,10 @@ export class RenameInputField implements IContentWidget, IDisposable { private _inputField: HTMLInputElement; private _visible: boolean; private readonly _visibleContextKey: IContextKey; - private _disposables: IDisposable[] = []; + private readonly _disposables = new DisposableStore(); // Editor.IContentWidget.allowEditorOverflow - public allowEditorOverflow: boolean = true; + allowEditorOverflow: boolean = true; constructor( editor: ICodeEditor, @@ -39,13 +39,13 @@ export class RenameInputField implements IContentWidget, IDisposable { this._editor = editor; this._editor.addContentWidget(this); - this._disposables.push(editor.onDidChangeConfiguration(e => { + this._disposables.add(editor.onDidChangeConfiguration(e => { if (e.fontInfo) { this.updateFont(); } })); - this._disposables.push(themeService.onThemeChange(theme => this.onThemeChange(theme))); + this._disposables.add(themeService.onThemeChange(theme => this.onThemeChange(theme))); } private onThemeChange(theme: ITheme): void { @@ -53,7 +53,7 @@ export class RenameInputField implements IContentWidget, IDisposable { } public dispose(): void { - this._disposables = dispose(this._disposables); + this._disposables.dispose(); this._editor.removeContentWidget(this); } @@ -138,9 +138,9 @@ export class RenameInputField implements IContentWidget, IDisposable { this._inputField.setAttribute('selectionEnd', selectionEnd.toString()); this._inputField.size = Math.max((where.endColumn - where.startColumn) * 1.1, 20); - const disposeOnDone: IDisposable[] = []; + const disposeOnDone = new DisposableStore(); const always = () => { - dispose(disposeOnDone); + disposeOnDone.dispose(); this._hide(); }; @@ -172,8 +172,8 @@ export class RenameInputField implements IContentWidget, IDisposable { } }; - disposeOnDone.push(this._editor.onDidChangeCursorSelection(onCursorChanged)); - disposeOnDone.push(this._editor.onDidBlurEditorWidget(() => this.cancelInput(false))); + disposeOnDone.add(this._editor.onDidChangeCursorSelection(onCursorChanged)); + disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(false))); this._show(); diff --git a/src/vs/editor/contrib/smartSelect/bracketSelections.ts b/src/vs/editor/contrib/smartSelect/bracketSelections.ts index 6fd74cf04654..cabec83178c7 100644 --- a/src/vs/editor/contrib/smartSelect/bracketSelections.ts +++ b/src/vs/editor/contrib/smartSelect/bracketSelections.ts @@ -115,8 +115,8 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider { } const innerBracket = Range.fromPositions(bracket.range.getEndPosition(), closing!.getStartPosition()); const outerBracket = Range.fromPositions(bracket.range.getStartPosition(), closing!.getEndPosition()); - bucket.push({ range: innerBracket, kind: 'statement.brackets' }); - bucket.push({ range: outerBracket, kind: 'statement.brackets.full' }); + bucket.push({ range: innerBracket }); + bucket.push({ range: outerBracket }); BracketSelectionRangeProvider._addBracketLeading(model, outerBracket, bucket); } } @@ -135,8 +135,8 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider { const startLine = bracket.startLineNumber; const column = model.getLineFirstNonWhitespaceColumn(startLine); if (column !== 0 && column !== bracket.startColumn) { - bucket.push({ range: Range.fromPositions(new Position(startLine, column), bracket.getEndPosition()), kind: 'statement.brackets.leading' }); - bucket.push({ range: Range.fromPositions(new Position(startLine, 1), bracket.getEndPosition()), kind: 'statement.brackets.leading.full' }); + bucket.push({ range: Range.fromPositions(new Position(startLine, column), bracket.getEndPosition()) }); + bucket.push({ range: Range.fromPositions(new Position(startLine, 1), bracket.getEndPosition()) }); } // xxxxxxxx @@ -147,8 +147,8 @@ export class BracketSelectionRangeProvider implements SelectionRangeProvider { if (aboveLine > 0) { const column = model.getLineFirstNonWhitespaceColumn(aboveLine); if (column === bracket.startColumn && column !== model.getLineLastNonWhitespaceColumn(aboveLine)) { - bucket.push({ range: Range.fromPositions(new Position(aboveLine, column), bracket.getEndPosition()), kind: 'statement.brackets.leading' }); - bucket.push({ range: Range.fromPositions(new Position(aboveLine, 1), bracket.getEndPosition()), kind: 'statement.brackets.leading.full' }); + bucket.push({ range: Range.fromPositions(new Position(aboveLine, column), bracket.getEndPosition()) }); + bucket.push({ range: Range.fromPositions(new Position(aboveLine, 1), bracket.getEndPosition()) }); } } } diff --git a/src/vs/editor/contrib/smartSelect/smartSelect.ts b/src/vs/editor/contrib/smartSelect/smartSelect.ts index 2d2bf8140e04..ab3664762740 100644 --- a/src/vs/editor/contrib/smartSelect/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/smartSelect.ts @@ -284,12 +284,12 @@ export function provideSelectionRanges(model: ITextModel, positions: Position[], if (cur.startLineNumber !== prev.startLineNumber || cur.endLineNumber !== prev.endLineNumber) { // add line/block range without leading/failing whitespace const rangeNoWhitespace = new Range(prev.startLineNumber, model.getLineFirstNonWhitespaceColumn(prev.startLineNumber), prev.endLineNumber, model.getLineLastNonWhitespaceColumn(prev.endLineNumber)); - if (rangeNoWhitespace.containsRange(prev) && !rangeNoWhitespace.equalsRange(prev)) { + if (rangeNoWhitespace.containsRange(prev) && !rangeNoWhitespace.equalsRange(prev) && cur.containsRange(rangeNoWhitespace) && !cur.equalsRange(rangeNoWhitespace)) { oneRangesWithTrivia.push(rangeNoWhitespace); } // add line/block range const rangeFull = new Range(prev.startLineNumber, 1, prev.endLineNumber, model.getLineMaxColumn(prev.endLineNumber)); - if (rangeFull.containsRange(prev) && !rangeFull.equalsRange(rangeNoWhitespace)) { + if (rangeFull.containsRange(prev) && !rangeFull.equalsRange(rangeNoWhitespace) && cur.containsRange(rangeFull) && !cur.equalsRange(rangeFull)) { oneRangesWithTrivia.push(rangeFull); } } diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index 240209c45326..4cbba32e1a3d 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { Range, IRange } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; -import { LanguageIdentifier, SelectionRangeProvider } from 'vs/editor/common/modes'; +import { LanguageIdentifier, SelectionRangeProvider, SelectionRangeRegistry } from 'vs/editor/common/modes'; import { MockMode, StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; @@ -320,4 +320,25 @@ suite('SmartSelect', () => { new Range(1, 1, 1, 21), ); }); + + test('Smart select: only add line ranges if they’re contained by the next range #73850', async function () { + + const reg = SelectionRangeRegistry.register('*', { + provideSelectionRanges() { + return [[ + { range: { startLineNumber: 1, startColumn: 10, endLineNumber: 1, endColumn: 11 } }, + { range: { startLineNumber: 1, startColumn: 10, endLineNumber: 3, endColumn: 2 } }, + { range: { startLineNumber: 1, startColumn: 1, endLineNumber: 3, endColumn: 2 } }, + ]]; + } + }); + + await assertGetRangesToPosition(['type T = {', '\tx: number', '}'], 1, 10, [ + new Range(1, 1, 3, 2), // all + new Range(1, 10, 3, 2), // { ... } + new Range(1, 10, 1, 11), // { + ]); + + reg.dispose(); + }); }); diff --git a/src/vs/editor/contrib/smartSelect/wordSelections.ts b/src/vs/editor/contrib/smartSelect/wordSelections.ts index 12ff057ef491..78c7a7252ef7 100644 --- a/src/vs/editor/contrib/smartSelect/wordSelections.ts +++ b/src/vs/editor/contrib/smartSelect/wordSelections.ts @@ -20,7 +20,7 @@ export class WordSelectionRangeProvider implements SelectionRangeProvider { this._addInWordRanges(bucket, model, position); this._addWordRanges(bucket, model, position); this._addWhitespaceLine(bucket, model, position); - bucket.push({ range: model.getFullModelRange(), kind: 'statement.all' }); + bucket.push({ range: model.getFullModelRange() }); } return result; } @@ -65,14 +65,14 @@ export class WordSelectionRangeProvider implements SelectionRangeProvider { } if (start < end) { - bucket.push({ range: new Range(pos.lineNumber, startColumn + start, pos.lineNumber, startColumn + end), kind: 'statement.word.part' }); + bucket.push({ range: new Range(pos.lineNumber, startColumn + start, pos.lineNumber, startColumn + end) }); } } private _addWordRanges(bucket: SelectionRange[], model: ITextModel, pos: Position): void { const word = model.getWordAtPosition(pos); if (word) { - bucket.push({ range: new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn), kind: 'statement.word' }); + bucket.push({ range: new Range(pos.lineNumber, word.startColumn, pos.lineNumber, word.endColumn) }); } } @@ -81,7 +81,7 @@ export class WordSelectionRangeProvider implements SelectionRangeProvider { && model.getLineFirstNonWhitespaceColumn(pos.lineNumber) === 0 && model.getLineLastNonWhitespaceColumn(pos.lineNumber) === 0 ) { - bucket.push({ range: new Range(pos.lineNumber, 1, pos.lineNumber, model.getLineMaxColumn(pos.lineNumber)), kind: 'statement.line' }); + bucket.push({ range: new Range(pos.lineNumber, 1, pos.lineNumber, model.getLineMaxColumn(pos.lineNumber)) }); } } } diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index e8745ff99e00..a955b229335d 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -17,7 +17,7 @@ import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { optional } from 'vs/platform/instantiation/common/instantiation'; -import { Choice, Placeholder, SnippetParser, Text, TextmateSnippet } from './snippetParser'; +import { Choice, Placeholder, SnippetParser, Text, TextmateSnippet, Marker } from './snippetParser'; import { ClipboardBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, SelectionBasedVariableResolver, TimeBasedVariableResolver, CommentBasedVariableResolver, WorkspaceBasedVariableResolver } from './snippetVariables'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import * as colors from 'vs/platform/theme/common/colorRegistry'; @@ -122,14 +122,14 @@ export class OneSnippet { } } - let skipThisPlaceholder = false; + let couldSkipThisPlaceholder = false; if (fwd === true && this._placeholderGroupsIdx < this._placeholderGroups.length - 1) { this._placeholderGroupsIdx += 1; - skipThisPlaceholder = true; + couldSkipThisPlaceholder = true; } else if (fwd === false && this._placeholderGroupsIdx > 0) { this._placeholderGroupsIdx -= 1; - skipThisPlaceholder = true; + couldSkipThisPlaceholder = true; } else { // the selection of the current placeholder might @@ -154,7 +154,7 @@ export class OneSnippet { // consider to skip this placeholder index when the decoration // range is empty but when the placeholder wasn't. that's a strong // hint that the placeholder has been deleted. (all placeholder must match this) - skipThisPlaceholder = skipThisPlaceholder && (range.isEmpty() && placeholder.toString().length > 0); + couldSkipThisPlaceholder = couldSkipThisPlaceholder && this._hasPlaceholderBeenCollapsed(placeholder); accessor.changeDecorationOptions(id, placeholder.isFinalTabstop ? OneSnippet._decor.activeFinal : OneSnippet._decor.active); activePlaceholders.add(placeholder); @@ -177,7 +177,25 @@ export class OneSnippet { return selections; })!; - return !skipThisPlaceholder ? newSelections : this.move(fwd); + return !couldSkipThisPlaceholder ? newSelections : this.move(fwd); + } + + private _hasPlaceholderBeenCollapsed(placeholder: Placeholder): boolean { + // A placeholder is empty when it wasn't empty when authored but + // when its tracking decoration is empty. This also applies to all + // potential parent placeholders + let marker: Marker | undefined = placeholder; + while (marker) { + if (marker instanceof Placeholder) { + const id = this._placeholderDecorations.get(marker)!; + const range = this._editor.getModel().getDecorationRange(id)!; + if (range.isEmpty() && marker.toString().length > 0) { + return true; + } + } + marker = marker.parent; + } + return false; } get isAtFirstPlaceholder() { diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts index 6d792a9a58b3..1c9c0dcbcff5 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts @@ -410,4 +410,22 @@ suite('SnippetController2', function () { ctrl.insert('export default $1'); assertContextKeys(contextKeys, true, false, true); }); + + test('Optional tabstop in snippets #72358', function () { + const ctrl = new SnippetController2(editor, logService, contextKeys); + model.setValue(''); + editor.setSelection(new Selection(1, 1, 1, 1)); + + ctrl.insert('${1:prop: {$2\\},}\nmore$0'); + assertContextKeys(contextKeys, true, false, true); + + assertSelections(editor, new Selection(1, 1, 1, 10)); + editor.trigger('test', Handler.Cut, {}); + + assertSelections(editor, new Selection(1, 1, 1, 1)); + + ctrl.next(); + assertSelections(editor, new Selection(2, 5, 2, 5)); + assertContextKeys(contextKeys, false, false, false); + }); }); diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 463f8d3df406..84dc94d08560 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -16,6 +16,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Range } from 'vs/editor/common/core/range'; import { FuzzyScore } from 'vs/base/common/filters'; +import { isDisposable, DisposableStore } from 'vs/base/common/lifecycle'; export const Context = { Visible: new RawContextKey('suggestWidgetVisible', false), @@ -121,7 +122,6 @@ export function provideSuggestionItems( token: CancellationToken = CancellationToken.None ): Promise { - const allSuggestions: CompletionItem[] = []; const wordUntil = model.getWordUntilPosition(position); const defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn); @@ -135,9 +135,12 @@ export function provideSuggestionItems( supports.unshift([_snippetSuggestSupport]); } + const allSuggestions: CompletionItem[] = []; + const disposables = new DisposableStore(); + let hasResult = false; + // add suggestions from contributed providers - providers are ordered in groups of // equal score and once a group produces a result the process stops - let hasResult = false; const factory = supports.map(supports => () => { // for each support in the group ask for suggestions return Promise.all(supports.map(provider => { @@ -162,6 +165,9 @@ export function provideSuggestionItems( allSuggestions.push(new CompletionItem(position, suggestion, container, provider, model)); } } + if (isDisposable(container)) { + disposables.add(container); + } } if (len !== allSuggestions.length && provider !== _snippetSuggestSupport) { @@ -177,6 +183,7 @@ export function provideSuggestionItems( return hasResult || token.isCancellationRequested; }).then(() => { if (token.isCancellationRequested) { + disposables.dispose(); return Promise.reject(canceled()); } return allSuggestions.sort(getSuggestionComparator(options.snippetSortOrder)); @@ -244,29 +251,35 @@ export function getSuggestionComparator(snippetConfig: SnippetSortOrder): (a: Co return _snippetComparators.get(snippetConfig)!; } -registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, position, args) => { +registerDefaultLanguageCommand('_executeCompletionItemProvider', async (model, position, args) => { const result: modes.CompletionList = { incomplete: false, suggestions: [] }; - let resolving: Promise[] = []; - let maxItemsToResolve = args['maxItemsToResolve'] || 0; + const disposables = new DisposableStore(); + const resolving: Promise[] = []; + const maxItemsToResolve = args['maxItemsToResolve'] || 0; - return provideSuggestionItems(model, position).then(items => { - for (const item of items) { - if (resolving.length < maxItemsToResolve) { - resolving.push(item.resolve(CancellationToken.None)); - } - result.incomplete = result.incomplete || item.container.incomplete; - result.suggestions.push(item.completion); + const items = await provideSuggestionItems(model, position); + for (const item of items) { + if (resolving.length < maxItemsToResolve) { + resolving.push(item.resolve(CancellationToken.None)); } - }).then(() => { - return Promise.all(resolving); - }).then(() => { + result.incomplete = result.incomplete || item.container.incomplete; + result.suggestions.push(item.completion); + if (isDisposable(item.container)) { + disposables.add(item.container); + } + } + + try { + await Promise.all(resolving); return result; - }); + } finally { + disposables.dispose(); + } }); interface SuggestController extends IEditorContribution { diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 1bf00c62e9e8..6104caaaacd2 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -9,8 +9,8 @@ import { createMatches } from 'vs/base/common/filters'; import * as strings from 'vs/base/common/strings'; import { Event, Emitter } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; -import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass } from 'vs/base/browser/dom'; +import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass, addDisposableListener } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IListEvent, IListRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -103,7 +103,9 @@ class Renderer implements IListRenderer renderTemplate(container: HTMLElement): ISuggestionTemplateData { const data = Object.create(null); - data.disposables = []; + const disposables = new DisposableStore(); + data.disposables = [disposables]; + data.root = container; addClass(data.root, 'show-file-icons'); @@ -114,7 +116,7 @@ class Renderer implements IListRenderer const main = append(text, $('.main')); data.iconLabel = new IconLabel(main, { supportHighlights: true }); - data.disposables.push(data.iconLabel); + disposables.add(data.iconLabel); data.typeLabel = append(main, $('span.type-label')); @@ -142,9 +144,9 @@ class Renderer implements IListRenderer configureFont(); - Event.chain(this.editor.onDidChangeConfiguration.bind(this.editor)) + disposables.add(Event.chain(this.editor.onDidChangeConfiguration.bind(this.editor)) .filter(e => e.fontInfo || e.contribInfo) - .on(configureFont, null, data.disposables); + .on(configureFont, null)); return data; } @@ -399,6 +401,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate(); private onDidFocusEmitter = new Emitter(); private onDidHideEmitter = new Emitter(); private onDidShowEmitter = new Emitter(); - readonly onDidSelect: Event = this.onDidSelectEmitter.event; readonly onDidFocus: Event = this.onDidFocusEmitter.event; readonly onDidHide: Event = this.onDidHideEmitter.event; @@ -464,6 +466,11 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { + if (e.target === this.element) { + this.hideWidget(); + } + })); this.messageElement = append(this.element, $('.message')); this.listElement = append(this.element, $('.tree')); @@ -480,7 +487,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate this.onListFocus(e)), this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged()), this.editor.onDidChangeConfiguration(e => e.contribInfo && applyIconStyle()) - ]; + ); this.suggestWidgetVisible = SuggestContext.Visible.bindTo(contextKeyService); this.suggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(contextKeyService); @@ -1152,7 +1159,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate('atEndOfWord', false); private readonly _ckAtEnd: IContextKey; - private readonly _confListener: IDisposable; private _enabled: boolean; private _selectionListener?: IDisposable; @@ -21,13 +20,15 @@ export class WordContextKey { private readonly _editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, ) { + super(); this._ckAtEnd = WordContextKey.AtEnd.bindTo(contextKeyService); - this._confListener = this._editor.onDidChangeConfiguration(e => e.contribInfo && this._update()); + this._register(this._editor.onDidChangeConfiguration(e => e.contribInfo && this._update())); this._update(); } dispose(): void { - dispose(this._confListener, this._selectionListener); + super.dispose(); + dispose(this._selectionListener); this._ckAtEnd.reset(); } diff --git a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts index 6a36320415da..75f4a5a13105 100644 --- a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import { IHorizontalSashLayoutProvider, ISashEvent, Orientation, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; import { Color, RGBA } from 'vs/base/common/color'; import { IdGenerator } from 'vs/base/common/idGenerator'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { EditorLayoutInfo } from 'vs/editor/common/config/editorOptions'; @@ -165,7 +165,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { private _positionMarkerId: string[] = []; protected _viewZone: ViewZoneDelegate | null; - protected _disposables: IDisposable[] = []; + protected readonly _disposables = new DisposableStore(); public container: HTMLElement; public domNode: HTMLElement; @@ -183,7 +183,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this.domNode.setAttribute('role', 'presentation'); } - this._disposables.push(this.editor.onDidLayoutChange((info: EditorLayoutInfo) => { + this._disposables.add(this.editor.onDidLayoutChange((info: EditorLayoutInfo) => { const width = this._getWidth(info); this.domNode.style.width = width + 'px'; this.domNode.style.left = this._getLeft(info) + 'px'; @@ -193,7 +193,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { public dispose(): void { - dispose(this._disposables); + this._disposables.dispose(); if (this._overlayWidget) { this.editor.removeOverlayWidget(this._overlayWidget); @@ -225,7 +225,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { this.domNode.appendChild(this.container); if (this.options.showArrow) { this._arrow = new Arrow(this.editor); - this._disposables.push(this._arrow); + this._disposables.add(this._arrow); } this._fillContainer(this.container); this._initSash(); @@ -470,7 +470,10 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { // --- sash private _initSash(): void { - this._resizeSash = new Sash(this.domNode, this, { orientation: Orientation.HORIZONTAL }); + if (this._resizeSash) { + return; + } + this._resizeSash = this._disposables.add(new Sash(this.domNode, this, { orientation: Orientation.HORIZONTAL })); if (!this.options.isResizeable) { this._resizeSash.hide(); @@ -478,7 +481,7 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { } let data: { startY: number; heightInLines: number; } | undefined; - this._disposables.push(this._resizeSash.onDidStart((e: ISashEvent) => { + this._disposables.add(this._resizeSash.onDidStart((e: ISashEvent) => { if (this._viewZone) { data = { startY: e.startY, @@ -487,11 +490,11 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { } })); - this._disposables.push(this._resizeSash.onDidEnd(() => { + this._disposables.add(this._resizeSash.onDidEnd(() => { data = undefined; })); - this._disposables.push(this._resizeSash.onDidChange((evt: ISashEvent) => { + this._disposables.add(this._resizeSash.onDidChange((evt: ISashEvent) => { if (data) { let lineDelta = (evt.currentY - data.startY) / this.editor.getConfiguration().lineHeight; let roundedLineDelta = lineDelta < 0 ? Math.ceil(lineDelta) : Math.floor(lineDelta); diff --git a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts index ad74dc5e01c9..2ff84f1defca 100644 --- a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts +++ b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts @@ -6,24 +6,23 @@ import 'vs/css!./iPadShowKeyboard'; import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -export class IPadShowKeyboard implements IEditorContribution { +export class IPadShowKeyboard extends Disposable implements IEditorContribution { private static readonly ID = 'editor.contrib.iPadShowKeyboard'; private readonly editor: ICodeEditor; private widget: ShowKeyboardWidget | null; - private toDispose: IDisposable[]; constructor(editor: ICodeEditor) { + super(); this.editor = editor; - this.toDispose = []; if (browser.isIPad) { - this.toDispose.push(editor.onDidChangeConfiguration(() => this.update())); + this._register(editor.onDidChangeConfiguration(() => this.update())); this.update(); } } @@ -48,7 +47,7 @@ export class IPadShowKeyboard implements IEditorContribution { } public dispose(): void { - this.toDispose = dispose(this.toDispose); + super.dispose(); if (this.widget) { this.widget.dispose(); this.widget = null; @@ -56,25 +55,24 @@ export class IPadShowKeyboard implements IEditorContribution { } } -class ShowKeyboardWidget implements IOverlayWidget { +class ShowKeyboardWidget extends Disposable implements IOverlayWidget { private static readonly ID = 'editor.contrib.ShowKeyboardWidget'; private readonly editor: ICodeEditor; private readonly _domNode: HTMLElement; - private _toDispose: IDisposable[]; constructor(editor: ICodeEditor) { + super(); this.editor = editor; this._domNode = document.createElement('textarea'); this._domNode.className = 'iPadShowKeyboard'; - this._toDispose = []; - this._toDispose.push(dom.addDisposableListener(this._domNode, 'touchstart', (e) => { + this._register(dom.addDisposableListener(this._domNode, 'touchstart', (e) => { this.editor.focus(); })); - this._toDispose.push(dom.addDisposableListener(this._domNode, 'focus', (e) => { + this._register(dom.addDisposableListener(this._domNode, 'focus', (e) => { this.editor.focus(); })); @@ -83,7 +81,7 @@ class ShowKeyboardWidget implements IOverlayWidget { public dispose(): void { this.editor.removeOverlayWidget(this); - this._toDispose = dispose(this._toDispose); + super.dispose(); } // ----- IOverlayWidget API diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index acfb20217be3..b03c4d4006fb 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Emitter, Event } from 'vs/base/common/event'; import { Keybinding, ResolvedKeybinding, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; -import { IDisposable, IReference, ImmortalReference, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, IReference, ImmortalReference, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { OS, isLinux, isMacintosh } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; @@ -25,7 +25,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; import { Configuration, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -37,8 +37,8 @@ import { IKeybindingItem, KeybindingsRegistry } from 'vs/platform/keybinding/com import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { ILabelService, ResourceLabelFormatter } from 'vs/platform/label/common/label'; -import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification } from 'vs/platform/notification/common/notification'; -import { IProgressRunner, IProgressService } from 'vs/platform/progress/common/progress'; +import { INotification, INotificationHandle, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; +import { IProgressRunner, ILocalProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -136,7 +136,7 @@ export class SimpleEditorModelResolverService implements ITextModelService { } } -export class SimpleProgressService implements IProgressService { +export class SimpleLocalProgressService implements ILocalProgressService { _serviceBrand: any; private static NULL_PROGRESS_RUNNER: IProgressRunner = { @@ -148,7 +148,7 @@ export class SimpleProgressService implements IProgressService { show(infinite: true, delay?: number): IProgressRunner; show(total: number, delay?: number): IProgressRunner; show(): IProgressRunner { - return SimpleProgressService.NULL_PROGRESS_RUNNER; + return SimpleLocalProgressService.NULL_PROGRESS_RUNNER; } showWhile(promise: Promise, delay?: number): Promise { @@ -220,6 +220,10 @@ export class SimpleNotificationService implements INotificationService { public prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { return SimpleNotificationService.NO_OP; } + + public status(message: string | Error, options?: IStatusMessageOptions): IDisposable { + return Disposable.None; + } } export class StandaloneCommandService implements ICommandService { @@ -291,7 +295,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { throw new Error(`Invalid keybinding`); } - let toDispose: IDisposable[] = []; + const toDispose = new DisposableStore(); this._dynamicKeybindings.push({ keybinding: keybinding, @@ -301,7 +305,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { weight2: 0 }); - toDispose.push(toDisposable(() => { + toDispose.add(toDisposable(() => { for (let i = 0; i < this._dynamicKeybindings.length; i++) { let kb = this._dynamicKeybindings[i]; if (kb.command === commandId) { @@ -314,7 +318,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { let commandService = this._commandService; if (commandService instanceof StandaloneCommandService) { - toDispose.push(commandService.addCommand({ + toDispose.add(commandService.addCommand({ id: commandId, handler: handler })); @@ -323,7 +327,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { } this.updateResolver({ source: KeybindingSource.Default }); - return combinedDisposable(toDispose); + return toDispose; } private updateResolver(event: IKeybindingEvent): void { @@ -446,7 +450,17 @@ export class SimpleConfigurationService implements IConfigurationService { } public getConfigurationData(): IConfigurationData | null { - return null; + const emptyModel: IConfigurationModel = { + contents: {}, + keys: [], + overrides: [] + }; + return { + defaults: emptyModel, + user: emptyModel, + workspace: emptyModel, + folders: {} + }; } } diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 9082b724ecf3..532eb6fdf755 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -5,7 +5,7 @@ import * as browser from 'vs/base/browser/browser'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { Disposable, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -227,13 +227,13 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon }; - let toDispose: IDisposable[] = []; + const toDispose = new DisposableStore(); // Generate a unique id to allow the same descriptor.id across multiple editor instances const uniqueId = this.getId() + ':' + id; // Register the command - toDispose.push(CommandsRegistry.registerCommand(uniqueId, run)); + toDispose.add(CommandsRegistry.registerCommand(uniqueId, run)); // Register the context menu item if (contextMenuGroupId) { @@ -246,16 +246,14 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon group: contextMenuGroupId, order: contextMenuOrder }; - toDispose.push(MenuRegistry.appendMenuItem(MenuId.EditorContext, menuItem)); + toDispose.add(MenuRegistry.appendMenuItem(MenuId.EditorContext, menuItem)); } // Register the keybindings if (Array.isArray(keybindings)) { - toDispose = toDispose.concat( - keybindings.map((kb) => { - return this._standaloneKeybindingService.addDynamicKeybinding(uniqueId, kb, run, keybindingsWhen); - }) - ); + for (const kb of keybindings) { + toDispose.add(this._standaloneKeybindingService.addDynamicKeybinding(uniqueId, kb, run, keybindingsWhen)); + } } // Finally, register an internal editor action @@ -270,11 +268,11 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon // Store it under the original id, such that trigger with the original id will work this._actions[id] = internalAction; - toDispose.push(toDisposable(() => { + toDispose.add(toDisposable(() => { delete this._actions[id]; })); - return combinedDisposable(toDispose); + return toDispose; } } diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index ee41d4a723e1..57af2eb87e0e 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -250,7 +250,7 @@ export interface IEncodedLineTokens { * - f = foreground ColorId (9 bits) * - b = background ColorId (9 bits) * - The color value for each colorId is defined in IStandaloneThemeData.customTokenColors: - * e.g colorId = 1 is stored in IStandaloneThemeData.customTokenColors[1]. Color id = 0 means no color, + * e.g. colorId = 1 is stored in IStandaloneThemeData.customTokenColors[1]. Color id = 0 means no color, * id = 1 is for the default foreground color, id = 2 for the default background. */ tokens: Uint32Array; @@ -427,7 +427,7 @@ export function registerCodeLensProvider(languageId: string, provider: modes.Cod */ export function registerCodeActionProvider(languageId: string, provider: CodeActionProvider): IDisposable { return modes.CodeActionProviderRegistry.register(languageId, { - provideCodeActions: (model: model.ITextModel, range: Range, context: modes.CodeActionContext, token: CancellationToken): (modes.Command | modes.CodeAction)[] | Promise<(modes.Command | modes.CodeAction)[]> => { + provideCodeActions: (model: model.ITextModel, range: Range, context: modes.CodeActionContext, token: CancellationToken): modes.CodeActionList | Promise => { let markers = StaticServices.markerService.get().read({ resource: model.uri }).filter(m => { return Range.areIntersectingOrTouching(m, range); }); @@ -510,7 +510,7 @@ export interface CodeActionProvider { /** * Provide commands for the given document and range. */ - provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): (modes.Command | modes.CodeAction)[] | Promise<(modes.Command | modes.CodeAction)[]>; + provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): modes.CodeActionList | Promise; } /** diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 5f9df5ce9779..db063e351292 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -13,7 +13,7 @@ import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; -import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService } from 'vs/editor/standalone/browser/simpleServices'; +import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleLocalProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService } from 'vs/editor/standalone/browser/simpleServices'; import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; @@ -36,7 +36,7 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { MarkerService } from 'vs/platform/markers/common/markerService'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -150,7 +150,7 @@ export module StaticServices { export const codeEditorService = define(ICodeEditorService, (o) => new StandaloneCodeEditorServiceImpl(standaloneThemeService.get(o))); - export const progressService = define(IProgressService, () => new SimpleProgressService()); + export const localProgressService = define(ILocalProgressService, () => new SimpleLocalProgressService()); export const storageService = define(IStorageService, () => new InMemoryStorageService()); diff --git a/src/vs/editor/test/common/model/textModel.test.ts b/src/vs/editor/test/common/model/textModel.test.ts index ef24605e4054..e244a529da42 100644 --- a/src/vs/editor/test/common/model/textModel.test.ts +++ b/src/vs/editor/test/common/model/textModel.test.ts @@ -26,17 +26,21 @@ function testGuessIndentation(defaultInsertSpaces: boolean, defaultTabSize: numb assert.equal(r.tabSize, expectedTabSize, msg); } -function assertGuess(expectedInsertSpaces: boolean | undefined, expectedTabSize: number | undefined, text: string[], msg?: string): void { +function assertGuess(expectedInsertSpaces: boolean | undefined, expectedTabSize: number | undefined | [number], text: string[], msg?: string): void { if (typeof expectedInsertSpaces === 'undefined') { // cannot guess insertSpaces if (typeof expectedTabSize === 'undefined') { // cannot guess tabSize testGuessIndentation(true, 13370, true, 13370, text, msg); testGuessIndentation(false, 13371, false, 13371, text, msg); - } else { + } else if (typeof expectedTabSize === 'number') { // can guess tabSize testGuessIndentation(true, 13370, true, expectedTabSize, text, msg); testGuessIndentation(false, 13371, false, expectedTabSize, text, msg); + } else { + // can only guess tabSize when insertSpaces is true + testGuessIndentation(true, 13370, true, expectedTabSize[0], text, msg); + testGuessIndentation(false, 13371, false, 13371, text, msg); } } else { // can guess insertSpaces @@ -44,10 +48,19 @@ function assertGuess(expectedInsertSpaces: boolean | undefined, expectedTabSize: // cannot guess tabSize testGuessIndentation(true, 13370, expectedInsertSpaces, 13370, text, msg); testGuessIndentation(false, 13371, expectedInsertSpaces, 13371, text, msg); - } else { + } else if (typeof expectedTabSize === 'number') { // can guess tabSize testGuessIndentation(true, 13370, expectedInsertSpaces, expectedTabSize, text, msg); testGuessIndentation(false, 13371, expectedInsertSpaces, expectedTabSize, text, msg); + } else { + // can only guess tabSize when insertSpaces is true + if (expectedInsertSpaces === true) { + testGuessIndentation(true, 13370, expectedInsertSpaces, expectedTabSize[0], text, msg); + testGuessIndentation(false, 13371, expectedInsertSpaces, expectedTabSize[0], text, msg); + } else { + testGuessIndentation(true, 13370, expectedInsertSpaces, 13370, text, msg); + testGuessIndentation(false, 13371, expectedInsertSpaces, 13371, text, msg); + } } } } @@ -219,7 +232,7 @@ suite('Editor Model - TextModel', () => { '\tx' ], '7xTAB'); - assertGuess(undefined, 2, [ + assertGuess(undefined, [2], [ '\tx', ' x', '\tx', @@ -239,7 +252,7 @@ suite('Editor Model - TextModel', () => { '\tx', ' x' ], '4x1, 4xTAB'); - assertGuess(false, 2, [ + assertGuess(false, undefined, [ '\tx', '\tx', ' x', @@ -250,7 +263,7 @@ suite('Editor Model - TextModel', () => { '\tx', ' x', ], '4x2, 5xTAB'); - assertGuess(false, 2, [ + assertGuess(false, undefined, [ '\tx', '\tx', 'x', @@ -261,7 +274,7 @@ suite('Editor Model - TextModel', () => { '\tx', ' x', ], '1x2, 5xTAB'); - assertGuess(false, 4, [ + assertGuess(false, undefined, [ '\tx', '\tx', 'x', @@ -272,7 +285,7 @@ suite('Editor Model - TextModel', () => { '\tx', ' x', ], '1x4, 5xTAB'); - assertGuess(false, 2, [ + assertGuess(false, undefined, [ '\tx', '\tx', 'x', @@ -524,7 +537,7 @@ suite('Editor Model - TextModel', () => { ' \t x', '\tx' ], 'mixed whitespace 1'); - assertGuess(false, 4, [ + assertGuess(false, undefined, [ '\tx', '\t x' ], 'mixed whitespace 2'); @@ -575,6 +588,26 @@ suite('Editor Model - TextModel', () => { ]); }); + test('issue #70832: Broken indentation detection', () => { + assertGuess(false, undefined, [ + 'x', + 'x', + 'x', + 'x', + ' x', + ' x', + ' x', + ' x', + ' x', + ' x', + ' x', + ' x', + ' x', + ' x', + 'x', + ]); + }); + test('validatePosition', () => { let m = TextModel.createFromString('line one\nline two'); diff --git a/src/vs/loader.js b/src/vs/loader.js index 3a57c5d59d8a..6bbc54af693b 100644 --- a/src/vs/loader.js +++ b/src/vs/loader.js @@ -235,6 +235,7 @@ var AMDLoader; *--------------------------------------------------------------------------------------------*/ var AMDLoader; (function (AMDLoader) { + ; var ConfigurationOptionsUtil = (function () { function ConfigurationOptionsUtil() { } @@ -305,22 +306,8 @@ var AMDLoader; if (typeof options.nodeCachedData.writeDelay !== 'number' || options.nodeCachedData.writeDelay < 0) { options.nodeCachedData.writeDelay = 1000 * 7; } - if (typeof options.nodeCachedData.onData !== 'function') { - options.nodeCachedData.onData = function (err) { - if (err && err.errorCode === 'cachedDataRejected') { - console.warn('Rejected cached data from file: ' + err.path); - } - else if (err && err.errorCode) { - console.error('Problems handling cached data file: ' + err.path); - console.error(err.detail); - } - else if (err) { - console.error(err); - } - }; - } if (!options.nodeCachedData.path || typeof options.nodeCachedData.path !== 'string') { - options.nodeCachedData.onData('INVALID cached data configuration, \'path\' MUST be set'); + options.onError('INVALID cached data configuration, \'path\' MUST be set'); options.nodeCachedData = undefined; } } @@ -660,7 +647,6 @@ var AMDLoader; this._env = env; this._didInitialize = false; this._didPatchNodeRequire = false; - this._hasCreateCachedData = false; } NodeScriptLoader.prototype._init = function (nodeRequire) { if (this._didInitialize) { @@ -672,14 +658,17 @@ var AMDLoader; this._vm = nodeRequire('vm'); this._path = nodeRequire('path'); this._crypto = nodeRequire('crypto'); - // check for `createCachedData`-api - this._hasCreateCachedData = typeof (new this._vm.Script('').createCachedData) === 'function'; }; // patch require-function of nodejs such that we can manually create a script // from cached data. this is done by overriding the `Module._compile` function NodeScriptLoader.prototype._initNodeRequire = function (nodeRequire, moduleManager) { + // It is important to check for `nodeCachedData` first and then set `_didPatchNodeRequire`. + // That's because `nodeCachedData` is set _after_ calling this for the first time... var nodeCachedData = moduleManager.getConfig().getOptionsLiteral().nodeCachedData; - if (!nodeCachedData || this._didPatchNodeRequire) { + if (!nodeCachedData) { + return; + } + if (this._didPatchNodeRequire) { return; } this._didPatchNodeRequire = true; @@ -704,25 +693,28 @@ var AMDLoader; return require; } Module.prototype._compile = function (content, filename) { - // remove shebang - content = content.replace(/^#!.*/, ''); - // create wrapper function - var wrapper = Module.wrap(content); - var cachedDataPath = that._getCachedDataPath(nodeCachedData.seed, nodeCachedData.path, filename); + // remove shebang and create wrapper function + var scriptSource = Module.wrap(content.replace(/^#!.*/, '')); + // create script + var recorder = moduleManager.getRecorder(); + var cachedDataPath = that._getCachedDataPath(nodeCachedData, filename); var options = { filename: filename }; try { options.cachedData = that._fs.readFileSync(cachedDataPath); + recorder.record(60 /* CachedDataFound */, cachedDataPath); } - catch (e) { - options.produceCachedData = !that._hasCreateCachedData; + catch (_e) { + recorder.record(61 /* CachedDataMissed */, cachedDataPath); } - var script = new that._vm.Script(wrapper, options); + var script = new that._vm.Script(scriptSource, options); var compileWrapper = script.runInThisContext(options); + // run script var dirname = that._path.dirname(filename); var require = makeRequireFunction(this); var args = [this.exports, require, this, filename, dirname, process, _commonjsGlobal, Buffer]; var result = compileWrapper.apply(this.exports, args); - that._processCachedData(moduleManager, script, wrapper, cachedDataPath, !options.cachedData); + // cached data aftermath + setTimeout(function () { return that._handleCachedData(script, cachedDataPath, !options.cachedData, moduleManager); }, Math.ceil(moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay * Math.random())); return result; }; }; @@ -749,57 +741,34 @@ var AMDLoader; } else { scriptSrc = AMDLoader.Utilities.fileUriToFilePath(this._env.isWindows, scriptSrc); - this._fs.readFile(scriptSrc, { encoding: 'utf8' }, function (err, data) { + var normalizedScriptSrc_1 = this._path.normalize(scriptSrc); + var vmScriptPathOrUri_1 = this._getElectronRendererScriptPathOrUri(normalizedScriptSrc_1); + var wantsCachedData_1 = Boolean(opts.nodeCachedData); + var cachedDataPath_1 = wantsCachedData_1 ? this._getCachedDataPath(opts.nodeCachedData, scriptSrc) : undefined; + this._readSourceAndCachedData(normalizedScriptSrc_1, cachedDataPath_1, recorder, function (err, data, cachedData) { if (err) { errorback(err); return; } - var normalizedScriptSrc = _this._path.normalize(scriptSrc); - var vmScriptSrc = normalizedScriptSrc; - // Make the script src friendly towards electron - if (_this._env.isElectronRenderer) { - var driveLetterMatch = vmScriptSrc.match(/^([a-z])\:(.*)/i); - if (driveLetterMatch) { - // windows - vmScriptSrc = "file:///" + (driveLetterMatch[1].toUpperCase() + ':' + driveLetterMatch[2]).replace(/\\/g, '/'); - } - else { - // nix - vmScriptSrc = "file://" + vmScriptSrc; - } - } - var contents, prefix = '(function (require, define, __filename, __dirname) { ', suffix = '\n});'; + var scriptSource; if (data.charCodeAt(0) === NodeScriptLoader._BOM) { - contents = prefix + data.substring(1) + suffix; + scriptSource = NodeScriptLoader._PREFIX + data.substring(1) + NodeScriptLoader._SUFFIX; } else { - contents = prefix + data + suffix; - } - contents = nodeInstrumenter(contents, normalizedScriptSrc); - if (!opts.nodeCachedData) { - _this._loadAndEvalScript(moduleManager, scriptSrc, vmScriptSrc, contents, { filename: vmScriptSrc }, recorder, callback, errorback); - } - else { - var cachedDataPath_1 = _this._getCachedDataPath(opts.nodeCachedData.seed, opts.nodeCachedData.path, scriptSrc); - _this._fs.readFile(cachedDataPath_1, function (_err, cachedData) { - // create script options - var options = { - filename: vmScriptSrc, - produceCachedData: !_this._hasCreateCachedData && typeof cachedData === 'undefined', - cachedData: cachedData - }; - var script = _this._loadAndEvalScript(moduleManager, scriptSrc, vmScriptSrc, contents, options, recorder, callback, errorback); - _this._processCachedData(moduleManager, script, contents, cachedDataPath_1, !options.cachedData); - }); + scriptSource = NodeScriptLoader._PREFIX + data + NodeScriptLoader._SUFFIX; } + scriptSource = nodeInstrumenter(scriptSource, normalizedScriptSrc_1); + var scriptOpts = { filename: vmScriptPathOrUri_1, cachedData: cachedData }; + var script = _this._createAndEvalScript(moduleManager, scriptSource, scriptOpts, callback, errorback); + _this._handleCachedData(script, cachedDataPath_1, wantsCachedData_1 && !cachedData, moduleManager); }); } }; - NodeScriptLoader.prototype._loadAndEvalScript = function (moduleManager, scriptSrc, vmScriptSrc, contents, options, recorder, callback, errorback) { - // create script, run script - recorder.record(31 /* NodeBeginEvaluatingScript */, scriptSrc); + NodeScriptLoader.prototype._createAndEvalScript = function (moduleManager, contents, options, callback, errorback) { + var recorder = moduleManager.getRecorder(); + recorder.record(31 /* NodeBeginEvaluatingScript */, options.filename); var script = new this._vm.Script(contents, options); - var r = script.runInThisContext(options); + var ret = script.runInThisContext(options); var globalDefineFunc = moduleManager.getGlobalAMDDefineFunc(); var receivedDefineCall = false; var localDefineFunc = function () { @@ -807,89 +776,112 @@ var AMDLoader; return globalDefineFunc.apply(null, arguments); }; localDefineFunc.amd = globalDefineFunc.amd; - r.call(AMDLoader.global, moduleManager.getGlobalAMDRequireFunc(), localDefineFunc, vmScriptSrc, this._path.dirname(scriptSrc)); - // signal done - recorder.record(32 /* NodeEndEvaluatingScript */, scriptSrc); + ret.call(AMDLoader.global, moduleManager.getGlobalAMDRequireFunc(), localDefineFunc, options.filename, this._path.dirname(options.filename)); + recorder.record(32 /* NodeEndEvaluatingScript */, options.filename); if (receivedDefineCall) { callback(); } else { - errorback(new Error("Didn't receive define call in " + scriptSrc + "!")); + errorback(new Error("Didn't receive define call in " + options.filename + "!")); } return script; }; - NodeScriptLoader.prototype._getCachedDataPath = function (seed, basedir, filename) { - var hash = this._crypto.createHash('md5').update(filename, 'utf8').update(seed, 'utf8').digest('hex'); + NodeScriptLoader.prototype._getElectronRendererScriptPathOrUri = function (path) { + if (!this._env.isElectronRenderer) { + return path; + } + var driveLetterMatch = path.match(/^([a-z])\:(.*)/i); + if (driveLetterMatch) { + // windows + return "file:///" + (driveLetterMatch[1].toUpperCase() + ':' + driveLetterMatch[2]).replace(/\\/g, '/'); + } + else { + // nix + return "file://" + path; + } + }; + NodeScriptLoader.prototype._getCachedDataPath = function (config, filename) { + var hash = this._crypto.createHash('md5').update(filename, 'utf8').update(config.seed, 'utf8').digest('hex'); var basename = this._path.basename(filename).replace(/\.js$/, ''); - return this._path.join(basedir, basename + "-" + hash + ".code"); + return this._path.join(config.path, basename + "-" + hash + ".code"); }; - NodeScriptLoader.prototype._processCachedData = function (moduleManager, script, contents, cachedDataPath, createCachedData) { + NodeScriptLoader.prototype._handleCachedData = function (script, cachedDataPath, createCachedData, moduleManager) { var _this = this; if (script.cachedDataRejected) { - // data rejected => delete cache file - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData({ - errorCode: 'cachedDataRejected', - path: cachedDataPath + // cached data got rejected -> delete and re-create + this._fs.unlink(cachedDataPath, function (err) { + moduleManager.getRecorder().record(62 /* CachedDataRejected */, cachedDataPath); + _this._createAndWriteCachedData(script, cachedDataPath, moduleManager); + if (err) { + moduleManager.getConfig().onError(err); + } }); - NodeScriptLoader._runSoon(function () { - return _this._fs.unlink(cachedDataPath, function (err) { - if (err) { - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData({ - errorCode: 'unlink', - path: cachedDataPath, - detail: err - }); - } - }); - }, moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay / 2); } - else if (script.cachedDataProduced) { - // data produced => tell outside world - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData(undefined, { - path: cachedDataPath - }); - // data produced => write cache file - NodeScriptLoader._runSoon(function () { - return _this._fs.writeFile(cachedDataPath, script.cachedData, function (err) { + else if (createCachedData) { + // no cached data, but wanted + this._createAndWriteCachedData(script, cachedDataPath, moduleManager); + } + }; + NodeScriptLoader.prototype._createAndWriteCachedData = function (script, cachedDataPath, moduleManager) { + var _this = this; + var timeout = Math.ceil(moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay * (1 + Math.random())); + var lastSize = -1; + var iteration = 0; + var createLoop = function () { + setTimeout(function () { + var cachedData = script.createCachedData(); + if (cachedData.length === 0 || cachedData.length === lastSize || iteration >= 5) { + return; + } + lastSize = cachedData.length; + _this._fs.writeFile(cachedDataPath, cachedData, function (err) { if (err) { - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData({ - errorCode: 'writeFile', - path: cachedDataPath, - detail: err - }); + moduleManager.getConfig().onError(err); } + moduleManager.getRecorder().record(63 /* CachedDataCreated */, cachedDataPath); + createLoop(); }); - }, moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay); + }, timeout * (Math.pow(4, iteration++))); + }; + // with some delay (`timeout`) create cached data + // and repeat that (with backoff delay) until the + // data seems to be not changing anymore + createLoop(); + }; + NodeScriptLoader.prototype._readSourceAndCachedData = function (sourcePath, cachedDataPath, recorder, callback) { + if (!cachedDataPath) { + // no cached data case + this._fs.readFile(sourcePath, { encoding: 'utf8' }, callback); } - else if (this._hasCreateCachedData && createCachedData) { - // NEW world - // data produced => tell outside world - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData(undefined, { - path: cachedDataPath + else { + // cached data case: read both files in parallel + var source_1; + var cachedData_1; + var steps_1 = 2; + var step_1 = function (err) { + if (err) { + callback(err); + } + else if (--steps_1 === 0) { + callback(undefined, source_1, cachedData_1); + } + }; + this._fs.readFile(sourcePath, { encoding: 'utf8' }, function (err, data) { + source_1 = data; + step_1(err); + }); + this._fs.readFile(cachedDataPath, function (err, data) { + cachedData_1 = data && data.length > 0 ? data : undefined; + step_1(); // ignored: cached data is optional + recorder.record(err ? 61 /* CachedDataMissed */ : 60 /* CachedDataFound */, cachedDataPath); }); - // soon'ish create and save cached data - NodeScriptLoader._runSoon(function () { - var data = script.createCachedData(contents); - _this._fs.writeFile(cachedDataPath, data, function (err) { - if (!err) { - return; - } - moduleManager.getConfig().getOptionsLiteral().nodeCachedData.onData({ - errorCode: 'writeFile', - path: cachedDataPath, - detail: err - }); - }); - }, moduleManager.getConfig().getOptionsLiteral().nodeCachedData.writeDelay); } }; - NodeScriptLoader._runSoon = function (callback, minTimeout) { - var timeout = minTimeout + Math.ceil(Math.random() * minTimeout); - setTimeout(callback, timeout); - }; return NodeScriptLoader; }()); NodeScriptLoader._BOM = 0xFEFF; + NodeScriptLoader._PREFIX = '(function (require, define, __filename, __dirname) { '; + NodeScriptLoader._SUFFIX = '\n});'; function createScriptLoader(env) { return new OnlyOnceScriptLoader(env); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 59f31d489366..447f45fab0f4 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4275,7 +4275,7 @@ declare namespace monaco.languages { * - f = foreground ColorId (9 bits) * - b = background ColorId (9 bits) * - The color value for each colorId is defined in IStandaloneThemeData.customTokenColors: - * e.g colorId = 1 is stored in IStandaloneThemeData.customTokenColors[1]. Color id = 0 means no color, + * e.g. colorId = 1 is stored in IStandaloneThemeData.customTokenColors[1]. Color id = 0 means no color, * id = 1 is for the default foreground color, id = 2 for the default background. */ tokens: Uint32Array; @@ -4437,7 +4437,7 @@ declare namespace monaco.languages { /** * Provide commands for the given document and range. */ - provideCodeActions(model: editor.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): (Command | CodeAction)[] | Promise<(Command | CodeAction)[]>; + provideCodeActions(model: editor.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): CodeActionList | Promise; } /** @@ -4898,6 +4898,10 @@ declare namespace monaco.languages { isPreferred?: boolean; } + export interface CodeActionList extends IDisposable { + readonly actions: ReadonlyArray; + } + /** * Represents a parameter of a callable-signature. A parameter can * have a label and a doc-comment. @@ -5258,6 +5262,7 @@ declare namespace monaco.languages { export interface ILink { range: IRange; url?: Uri | string; + tooltip?: string; } export interface ILinksList { @@ -5346,7 +5351,6 @@ declare namespace monaco.languages { } export interface SelectionRange { - kind: string; range: IRange; } @@ -5453,16 +5457,21 @@ declare namespace monaco.languages { arguments?: any[]; } - export interface ICodeLensSymbol { + export interface CodeLens { range: IRange; id?: string; command?: Command; } + export interface CodeLensList { + lenses: CodeLens[]; + dispose(): void; + } + export interface CodeLensProvider { onDidChange?: IEvent; - provideCodeLenses(model: editor.ITextModel, token: CancellationToken): ProviderResult; - resolveCodeLens?(model: editor.ITextModel, codeLens: ICodeLensSymbol, token: CancellationToken): ProviderResult; + provideCodeLenses(model: editor.ITextModel, token: CancellationToken): ProviderResult; + resolveCodeLens?(model: editor.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } export interface ILanguageExtensionPoint { diff --git a/src/vs/platform/actions/common/menuService.ts b/src/vs/platform/actions/common/menuService.ts index ef9588a4647c..0e5b6686ed7b 100644 --- a/src/vs/platform/actions/common/menuService.ts +++ b/src/vs/platform/actions/common/menuService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { IMenu, IMenuActionOptions, IMenuItem, IMenuService, isIMenuItem, ISubmenuItem, MenuId, MenuItemAction, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey'; @@ -30,7 +30,7 @@ type MenuItemGroup = [string, Array]; class Menu implements IMenu { private readonly _onDidChange = new Emitter(); - private readonly _disposables: IDisposable[] = []; + private readonly _dispoables = new DisposableStore(); private _menuGroups: MenuItemGroup[]; private _contextKeys: Set; @@ -44,19 +44,24 @@ class Menu implements IMenu { // rebuild this menu whenever the menu registry reports an // event for this MenuId - Event.debounce( + this._dispoables.add(Event.debounce( Event.filter(MenuRegistry.onDidChangeMenu, menuId => menuId === this._id), () => { }, 50 - )(this._build, this, this._disposables); + )(this._build, this)); // when context keys change we need to check if the menu also // has changed - Event.debounce( + this._dispoables.add(Event.debounce( this._contextKeyService.onDidChangeContext, (last, event) => last || event.affectsSome(this._contextKeys), 50 - )(e => e && this._onDidChange.fire(undefined), this, this._disposables); + )(e => e && this._onDidChange.fire(undefined), this)); + } + + dispose(): void { + this._dispoables.dispose(); + this._onDidChange.dispose(); } private _build(): void { @@ -95,11 +100,6 @@ class Menu implements IMenu { this._onDidChange.fire(this); } - dispose() { - dispose(this._disposables); - this._onDidChange.dispose(); - } - get onDidChange(): Event { return this._onDidChange.event; } diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index 75bee0b9508f..c17cf275843b 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -244,7 +244,7 @@ export class BackupMainService implements IBackupMainService { return []; } - const seenIds: { [id: string]: boolean } = Object.create(null); + const seenIds: Set = new Set(); const result: IWorkspaceBackupInfo[] = []; // Validate Workspaces @@ -254,8 +254,8 @@ export class BackupMainService implements IBackupMainService { return []; // wrong format, skip all entries } - if (!seenIds[workspace.id]) { - seenIds[workspace.id] = true; + if (!seenIds.has(workspace.id)) { + seenIds.add(workspace.id); const backupPath = this.getBackupPath(workspace.id); const hasBackups = await this.hasBackups(backupPath); @@ -283,11 +283,11 @@ export class BackupMainService implements IBackupMainService { } const result: URI[] = []; - const seen: { [id: string]: boolean } = Object.create(null); + const seenIds: Set = new Set(); for (let folderURI of folderWorkspaces) { const key = getComparisonKey(folderURI); - if (!seen[key]) { - seen[key] = true; + if (!seenIds.has(key)) { + seenIds.add(key); const backupPath = this.getBackupPath(this.getFolderHash(folderURI)); const hasBackups = await this.hasBackups(backupPath); @@ -315,7 +315,7 @@ export class BackupMainService implements IBackupMainService { } const result: IEmptyWindowBackupInfo[] = []; - const seen: { [id: string]: boolean } = Object.create(null); + const seenIds: Set = new Set(); // Validate Empty Windows for (let backupInfo of emptyWorkspaces) { @@ -324,8 +324,8 @@ export class BackupMainService implements IBackupMainService { return []; } - if (!seen[backupFolder]) { - seen[backupFolder] = true; + if (!seenIds.has(backupFolder)) { + seenIds.add(backupFolder); const backupPath = this.getBackupPath(backupFolder); if (await this.hasBackups(backupPath)) { diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index c8cf252e69ab..2c93e7b17ca8 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -131,11 +131,13 @@ export interface IDefaultConfigurationExtension { defaults: { [key: string]: {} }; } -export const allSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} }; -export const applicationSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} }; -export const machineSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} }; -export const windowSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} }; -export const resourceSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} }; +type SettingProperties = { [key: string]: any }; + +export const allSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; +export const applicationSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; +export const machineSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; +export const windowSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; +export const resourceSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} }; export const editorConfigurationSchemaId = 'vscode://schemas/settings/editor'; const contributionRegistry = Registry.as(JSONExtensions.JSONContribution); diff --git a/src/vs/platform/configuration/node/configuration.ts b/src/vs/platform/configuration/node/configuration.ts deleted file mode 100644 index 53d7d1da6ddb..000000000000 --- a/src/vs/platform/configuration/node/configuration.ts +++ /dev/null @@ -1,56 +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 { Disposable } from 'vs/base/common/lifecycle'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { ConfigurationModelParser, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; -import { ConfigWatcher } from 'vs/base/node/config'; -import { Event, Emitter } from 'vs/base/common/event'; - -export class NodeBasedUserConfiguration extends Disposable { - - private userConfigModelWatcher: ConfigWatcher; - private initializePromise: Promise; - - private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); - readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; - - constructor(private settingsPath: string) { - super(); - } - - initialize(): Promise { - if (!this.initializePromise) { - this.initializePromise = new Promise((c, e) => { - this.userConfigModelWatcher = new ConfigWatcher(this.settingsPath, { - changeBufferDelay: 300, onError: error => onUnexpectedError(error), defaultConfig: new ConfigurationModelParser(this.settingsPath), parse: (content: string, parseErrors: any[]) => { - const userConfigModelParser = new ConfigurationModelParser(this.settingsPath); - userConfigModelParser.parseContent(content); - parseErrors = [...userConfigModelParser.errors]; - return userConfigModelParser; - }, initCallback: () => c(undefined) - }); - this._register(this.userConfigModelWatcher); - - // Listeners - this._register(this.userConfigModelWatcher.onDidUpdateConfiguration(() => this._onDidChangeConfiguration.fire(this.userConfigModelWatcher.getConfig().configurationModel))); - }); - } - return this.initializePromise.then(() => this.userConfigModelWatcher.getConfig().configurationModel); - } - - initializeSync(): ConfigurationModel { - this.initialize(); - return this.userConfigModelWatcher.getConfig().configurationModel; - } - - reload(): Promise { - return this.initialize().then(() => new Promise(c => this.userConfigModelWatcher.reload(userConfigModelParser => c(userConfigModelParser.configurationModel)))); - } - - getConfigurationModel(): ConfigurationModel { - return this.userConfigModelWatcher.getConfig().configurationModel; - } -} \ No newline at end of file diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index eb55d0e780e1..01cef6c8c373 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -7,40 +7,49 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService, IConfigurationChangeEvent, IConfigurationOverrides, ConfigurationTarget, compare, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration'; -import { DefaultConfigurationModel, Configuration, ConfigurationChangeEvent, ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; +import { DefaultConfigurationModel, Configuration, ConfigurationChangeEvent, ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { NodeBasedUserConfiguration } from 'vs/platform/configuration/node/configuration'; +import { ConfigWatcher } from 'vs/base/node/config'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class ConfigurationService extends Disposable implements IConfigurationService, IDisposable { _serviceBrand: any; - private _configuration: Configuration; - private userConfiguration: NodeBasedUserConfiguration; + private configuration: Configuration; + private userConfigModelWatcher: ConfigWatcher | undefined; private readonly _onDidChangeConfiguration: Emitter = this._register(new Emitter()); readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; constructor( - configurationPath: string + private readonly configurationPath: string ) { super(); - - this.userConfiguration = this._register(new NodeBasedUserConfiguration(configurationPath)); - - // Initialize - const defaults = new DefaultConfigurationModel(); - const user = this.userConfiguration.initializeSync(); - this._configuration = new Configuration(defaults, user); - - // Listeners - this._register(this.userConfiguration.onDidChangeConfiguration(userConfigurationModel => this.onDidChangeUserConfiguration(userConfigurationModel))); + this.configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); this._register(Registry.as(Extensions.Configuration).onDidUpdateConfiguration(configurationProperties => this.onDidDefaultConfigurationChange(configurationProperties))); } - get configuration(): Configuration { - return this._configuration; + initialize(): Promise { + if (this.userConfigModelWatcher) { + this.userConfigModelWatcher.dispose(); + } + + return new Promise((c, e) => { + this.userConfigModelWatcher = this._register(new ConfigWatcher(this.configurationPath, { + changeBufferDelay: 300, onError: error => onUnexpectedError(error), defaultConfig: new ConfigurationModelParser(this.configurationPath), parse: (content: string, parseErrors: any[]) => { + const userConfigModelParser = new ConfigurationModelParser(this.configurationPath); + userConfigModelParser.parseContent(content); + parseErrors = [...userConfigModelParser.errors]; + return userConfigModelParser; + }, initCallback: () => { + this.configuration = new Configuration(new DefaultConfigurationModel(), this.userConfigModelWatcher!.getConfig().configurationModel); + this._register(this.userConfigModelWatcher!.onDidUpdateConfiguration(() => this.onDidChangeUserConfiguration(this.userConfigModelWatcher!.getConfig().configurationModel))); + c(); + } + })); + }); } getConfigurationData(): IConfigurationData { @@ -85,21 +94,26 @@ export class ConfigurationService extends Disposable implements IConfigurationSe } reloadConfiguration(folder?: IWorkspaceFolder): Promise { - return folder ? Promise.resolve(undefined) : - this.userConfiguration.reload().then(userConfigurationModel => this.onDidChangeUserConfiguration(userConfigurationModel)); + if (this.userConfigModelWatcher) { + return new Promise(c => this.userConfigModelWatcher!.reload(userConfigModelParser => { + this.onDidChangeUserConfiguration(userConfigModelParser.configurationModel); + c(); + })); + } + return this.initialize(); } private onDidChangeUserConfiguration(userConfigurationModel: ConfigurationModel): void { - const { added, updated, removed } = compare(this._configuration.localUserConfiguration, userConfigurationModel); + const { added, updated, removed } = compare(this.configuration.localUserConfiguration, userConfigurationModel); const changedKeys = [...added, ...updated, ...removed]; if (changedKeys.length) { - this._configuration.updateLocalUserConfiguration(userConfigurationModel); + this.configuration.updateLocalUserConfiguration(userConfigurationModel); this.trigger(changedKeys, ConfigurationTarget.USER); } } private onDidDefaultConfigurationChange(keys: string[]): void { - this._configuration.updateDefaultConfiguration(new DefaultConfigurationModel()); + this.configuration.updateDefaultConfiguration(new DefaultConfigurationModel()); this.trigger(keys, ConfigurationTarget.DEFAULT); } @@ -110,9 +124,9 @@ export class ConfigurationService extends Disposable implements IConfigurationSe private getTargetConfiguration(target: ConfigurationTarget): any { switch (target) { case ConfigurationTarget.DEFAULT: - return this._configuration.defaults.contents; + return this.configuration.defaults.contents; case ConfigurationTarget.USER: - return this._configuration.localUserConfiguration.contents; + return this.configuration.localUserConfiguration.contents; } return {}; } diff --git a/src/vs/platform/configuration/test/common/configuration.test.ts b/src/vs/platform/configuration/test/common/configuration.test.ts index 1e3662c3dd6e..4ff1a7973f77 100644 --- a/src/vs/platform/configuration/test/common/configuration.test.ts +++ b/src/vs/platform/configuration/test/common/configuration.test.ts @@ -32,7 +32,7 @@ suite('Configuration', () => { assert.deepEqual(target, { 'a': { 'b': 2 } }); }); - test('removeFromValueTree: remove a single segemented key', () => { + test('removeFromValueTree: remove a single segmented key', () => { let target = { 'a': 1 }; removeFromValueTree(target, 'a'); @@ -40,7 +40,7 @@ suite('Configuration', () => { assert.deepEqual(target, {}); }); - test('removeFromValueTree: remove a single segemented key when its value is undefined', () => { + test('removeFromValueTree: remove a single segmented key when its value is undefined', () => { let target = { 'a': undefined }; removeFromValueTree(target, 'a'); @@ -48,7 +48,7 @@ suite('Configuration', () => { assert.deepEqual(target, {}); }); - test('removeFromValueTree: remove a multi segemented key when its value is undefined', () => { + test('removeFromValueTree: remove a multi segmented key when its value is undefined', () => { let target = { 'a': { 'b': 1 } }; removeFromValueTree(target, 'a.b'); @@ -56,7 +56,7 @@ suite('Configuration', () => { assert.deepEqual(target, {}); }); - test('removeFromValueTree: remove a multi segemented key when its value is array', () => { + test('removeFromValueTree: remove a multi segmented key when its value is array', () => { let target = { 'a': { 'b': [1] } }; removeFromValueTree(target, 'a.b'); @@ -64,7 +64,7 @@ suite('Configuration', () => { assert.deepEqual(target, {}); }); - test('removeFromValueTree: remove a multi segemented key first segment value is array', () => { + test('removeFromValueTree: remove a multi segmented key first segment value is array', () => { let target = { 'a': [1] }; removeFromValueTree(target, 'a.0'); @@ -80,7 +80,7 @@ suite('Configuration', () => { assert.deepEqual(target, {}); }); - test('removeFromValueTree: remove a multi segemented key when the first node has more values', () => { + test('removeFromValueTree: remove a multi segmented key when the first node has more values', () => { let target = { 'a': { 'b': { 'c': 1 }, 'd': 1 } }; removeFromValueTree(target, 'a.b.c'); @@ -88,7 +88,7 @@ suite('Configuration', () => { assert.deepEqual(target, { 'a': { 'd': 1 } }); }); - test('removeFromValueTree: remove a multi segemented key when in between node has more values', () => { + test('removeFromValueTree: remove a multi segmented key when in between node has more values', () => { let target = { 'a': { 'b': { 'c': { 'd': 1 }, 'd': 1 } } }; removeFromValueTree(target, 'a.b.c.d'); @@ -96,7 +96,7 @@ suite('Configuration', () => { assert.deepEqual(target, { 'a': { 'b': { 'd': 1 } } }); }); - test('removeFromValueTree: remove a multi segemented key when the last but one node has more values', () => { + test('removeFromValueTree: remove a multi segmented key when the last but one node has more values', () => { let target = { 'a': { 'b': { 'c': 1, 'd': 1 } } }; removeFromValueTree(target, 'a.b.c'); diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index adb8538d5c2f..4d8d25f0907a 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -82,7 +82,7 @@ suite('ConfigurationModel', () => { assert.deepEqual(testObject.keys, ['a.b']); }); - test('removeValue: remove a single segemented key', () => { + test('removeValue: remove a single segmented key', () => { let testObject = new ConfigurationModel({ 'a': 1 }, ['a']); testObject.removeValue('a'); @@ -91,7 +91,7 @@ suite('ConfigurationModel', () => { assert.deepEqual(testObject.keys, []); }); - test('removeValue: remove a multi segemented key', () => { + test('removeValue: remove a multi segmented key', () => { let testObject = new ConfigurationModel({ 'a': { 'b': 1 } }, ['a.b']); testObject.removeValue('a.b'); diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index 90fad2fafcb2..fc86fe7cb27f 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -21,6 +21,7 @@ suite('ConfigurationService - Node', () => { fs.writeFileSync(res.testFile, '{ "foo": "bar" }'); const service = new ConfigurationService(res.testFile); + await service.initialize(); const config = service.getValue<{ foo: string; }>(); @@ -38,6 +39,7 @@ suite('ConfigurationService - Node', () => { fs.writeFileSync(res.testFile, '{ "testworkbench.editor.tabs": true }'); const service = new ConfigurationService(res.testFile); + await service.initialize(); const config = service.getValue<{ testworkbench: { editor: { @@ -60,6 +62,7 @@ suite('ConfigurationService - Node', () => { fs.writeFileSync(res.testFile, ',,,,'); const service = new ConfigurationService(res.testFile); + await service.initialize(); const config = service.getValue<{ foo: string; }>(); @@ -69,13 +72,14 @@ suite('ConfigurationService - Node', () => { return res.cleanUp(); }); - test('missing file does not explode', () => { + test('missing file does not explode', async () => { const id = uuid.generateUuid(); const parentDir = path.join(os.tmpdir(), 'vsctests', id); const newDir = path.join(parentDir, 'config', id); const testFile = path.join(newDir, 'config.json'); const service = new ConfigurationService(testFile); + await service.initialize(); const config = service.getValue<{ foo: string }>(); assert.ok(config); @@ -87,6 +91,7 @@ suite('ConfigurationService - Node', () => { const res = await testFile('config', 'config.json'); const service = new ConfigurationService(res.testFile); + await service.initialize(); return new Promise((c, e) => { service.onDidChangeConfiguration(() => { assert.equal(service.getValue('foo'), 'bar'); @@ -104,6 +109,7 @@ suite('ConfigurationService - Node', () => { fs.writeFileSync(res.testFile, '{ "foo": "bar" }'); const service = new ConfigurationService(res.testFile); + await service.initialize(); let config = service.getValue<{ foo: string; }>(); @@ -130,7 +136,7 @@ suite('ConfigurationService - Node', () => { return res.cleanUp(); }); - test('model defaults', () => { + test('model defaults', async () => { interface ITestSetting { configuration: { service: { @@ -152,6 +158,7 @@ suite('ConfigurationService - Node', () => { }); let serviceWithoutFile = new ConfigurationService('__testFile'); + await serviceWithoutFile.initialize(); let setting = serviceWithoutFile.getValue(); assert.ok(setting); @@ -194,6 +201,8 @@ suite('ConfigurationService - Node', () => { const r = await testFile('config', 'config.json'); const service = new ConfigurationService(r.testFile); + service.initialize(); + let res = service.inspect('something.missing'); assert.strictEqual(res.value, undefined); assert.strictEqual(res.default, undefined); @@ -230,6 +239,8 @@ suite('ConfigurationService - Node', () => { const r = await testFile('config', 'config.json'); const service = new ConfigurationService(r.testFile); + service.initialize(); + let res = service.inspect('lookup.service.testNullSetting'); assert.strictEqual(res.default, null); assert.strictEqual(res.value, null); diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index 1d0f5c8bb6fe..234433710e78 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -5,7 +5,7 @@ import 'vs/css!./contextMenuHandler'; -import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { ActionRunner, IRunEvent } from 'vs/base/common/actions'; import { Menu } from 'vs/base/browser/ui/menu/menu'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -104,7 +104,7 @@ export class ContextMenuHandler { this.contextViewService.hideContextView(true); }, null, menuDisposables); - return combinedDisposable([...menuDisposables, menu]); + return combinedDisposable(...menuDisposables, menu); }, focus: () => { diff --git a/src/vs/platform/credentials/common/credentials.ts b/src/vs/platform/credentials/common/credentials.ts new file mode 100644 index 000000000000..f538dd648669 --- /dev/null +++ b/src/vs/platform/credentials/common/credentials.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const ICredentialsService = createDecorator('ICredentialsService'); + +export interface ICredentialsService { + _serviceBrand: any; + getPassword(service: string, account: string): Promise; + setPassword(service: string, account: string, password: string): Promise; + deletePassword(service: string, account: string): Promise; + findPassword(service: string): Promise; +} diff --git a/src/vs/platform/credentials/node/credentialsService.ts b/src/vs/platform/credentials/node/credentialsService.ts new file mode 100644 index 000000000000..c86aece4d2ff --- /dev/null +++ b/src/vs/platform/credentials/node/credentialsService.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 { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { IdleValue } from 'vs/base/common/async'; + +type KeytarModule = { + getPassword(service: string, account: string): Promise; + setPassword(service: string, account: string, password: string): Promise; + deletePassword(service: string, account: string): Promise; + findPassword(service: string): Promise; +}; + +export class KeytarCredentialsService implements ICredentialsService { + + _serviceBrand: any; + + private readonly _keytar = new IdleValue>(() => import('keytar')); + + async getPassword(service: string, account: string): Promise { + const keytar = await this._keytar.getValue(); + return keytar.getPassword(service, account); + } + + async setPassword(service: string, account: string, password: string): Promise { + const keytar = await this._keytar.getValue(); + return keytar.setPassword(service, account, password); + } + + async deletePassword(service: string, account: string): Promise { + const keytar = await this._keytar.getValue(); + return keytar.deletePassword(service, account); + } + + async findPassword(service: string): Promise { + const keytar = await this._keytar.getValue(); + return keytar.findPassword(service); + } +} diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 590c0cbdbe96..29e20c79cd5c 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -36,7 +36,7 @@ export interface IConfirmationResult { /** * This will only be defined if the confirmation was created - * with the checkox option defined. + * with the checkbox option defined. */ checkboxChecked?: boolean; } diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 7f7a939264ec..f5772d42964d 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -10,7 +10,7 @@ import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProces import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom'; import * as electron from 'electron'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { Terminal } from 'vscode-xterm'; +import { Terminal } from 'xterm'; import { timeout } from 'vs/base/common/async'; import { coalesce } from 'vs/base/common/arrays'; diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index fab602c5924e..90e93eae7e2e 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -210,5 +210,5 @@ export async function serve( const channel = new DriverChannel(driver); server.registerChannel('driver', channel); - return combinedDisposable([server, windowServer]); + return combinedDisposable(server, windowServer); } diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 3b8d1d395fe1..f3785d10a6ae 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -95,15 +95,17 @@ export interface IEditorOptions { readonly forceReload?: boolean; /** - * Will reveal the editor if it is already opened and visible in any of the opened editor groups. Note - * that this option is just a hint that might be ignored if the user wants to open an editor explicitly + * Will reveal the editor if it is already opened and visible in any of the opened editor groups. + * + * Note that this option is just a hint that might be ignored if the user wants to open an editor explicitly * to the side of another one or into a specific editor group. */ readonly revealIfVisible?: boolean; /** - * Will reveal the editor if it is already opened (even when not visible) in any of the opened editor groups. Note - * that this option is just a hint that might be ignored if the user wants to open an editor explicitly + * Will reveal the editor if it is already opened (even when not visible) in any of the opened editor groups. + * + * Note that this option is just a hint that might be ignored if the user wants to open an editor explicitly * to the side of another one or into a specific editor group. */ readonly revealIfOpened?: boolean; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 0ad08e381a17..61b323aafb7e 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -27,7 +27,6 @@ export interface ParsedArgs { 'prof-startup'?: string; 'prof-startup-prefix'?: string; 'prof-append-timers'?: string; - 'prof-modules'?: string; verbose?: boolean; trace?: boolean; 'trace-category-filter'?: string; @@ -69,6 +68,7 @@ export interface ParsedArgs { 'driver'?: string; 'driver-verbose'?: boolean; remote?: string; + 'disable-user-env-probe'?: boolean; // {{SQL CARBON EDIT}} aad?: boolean; database?: string; @@ -105,7 +105,7 @@ export interface IEnvironmentService { appNameLong: string; appQuality?: string; appSettingsHome: string; - appSettingsPath: string; + settingsResource: URI; appKeybindingsPath: string; machineSettingsHome: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index e7b612ea04c7..aca016a42253 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -8,7 +8,7 @@ import * as os from 'os'; import { localize } from 'vs/nls'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { join } from 'vs/base/common/path'; -import { writeFileSync } from 'fs'; +import { writeFileSync } from 'vs/base/node/pfs'; /** * This code is also used by standalone cli's. Avoid adding any other dependencies. @@ -54,7 +54,6 @@ export const options: Option[] = [ { id: 'verbose', type: 'boolean', cat: 't', description: localize('verbose', "Print verbose output (implies --wait).") }, { id: 'log', type: 'string', cat: 't', args: 'level', description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.") }, { id: 'status', type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") }, - { id: 'prof-modules', type: 'boolean', alias: 'p', cat: 't', description: localize('prof-modules', "Capture performance markers while loading JS modules and print them with 'F1 > Developer: Startup Performance") }, { id: 'prof-startup', type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup") }, { id: 'disable-extensions', type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, { id: 'disable-extension', type: 'string', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") }, diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 977cb51bbd05..50888922d54f 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -21,7 +21,9 @@ import { URI } from 'vs/base/common/uri'; const xdgRuntimeDir = process.env['XDG_RUNTIME_DIR']; function getNixIPCHandle(userDataPath: string, type: string): string { - if (xdgRuntimeDir) { + const vscodePortable = process.env['VSCODE_PORTABLE']; + + if (xdgRuntimeDir && !vscodePortable) { const scope = crypto.createHash('md5').update(userDataPath).digest('hex').substr(0, 8); return path.join(xdgRuntimeDir, `vscode-${scope}-${pkg.version}-${type}.sock`); } @@ -93,6 +95,7 @@ export class EnvironmentService implements IEnvironmentService { @memoize get userDataPath(): string { const vscodePortable = process.env['VSCODE_PORTABLE']; + if (vscodePortable) { return path.join(vscodePortable, 'user-data'); } @@ -108,7 +111,7 @@ export class EnvironmentService implements IEnvironmentService { get appSettingsHome(): string { return path.join(this.userDataPath, 'User'); } @memoize - get appSettingsPath(): string { return path.join(this.appSettingsHome, 'settings.json'); } + get settingsResource(): URI { return URI.file(path.join(this.appSettingsHome, 'settings.json')); } @memoize get machineSettingsHome(): string { return path.join(this.userDataPath, 'Machine'); } @@ -170,6 +173,7 @@ export class EnvironmentService implements IEnvironmentService { } const vscodePortable = process.env['VSCODE_PORTABLE']; + if (vscodePortable) { return path.join(vscodePortable, 'extensions'); } @@ -289,6 +293,7 @@ export function parseDebugPort(debugArg: string | undefined, debugBrkArg: string const portStr = debugBrkArg || debugArg; const port = Number(portStr) || (!isBuild ? defaultBuildPort : null); const brk = port ? Boolean(!!debugBrkArg) : false; + return { port, break: brk, debugId }; } @@ -303,9 +308,9 @@ function parsePathArg(arg: string | undefined, process: NodeJS.Process): string if (path.normalize(arg) === resolved) { return resolved; - } else { - return path.resolve(process.env['VSCODE_CWD'] || process.cwd(), arg); } + + return path.resolve(process.env['VSCODE_CWD'] || process.cwd(), arg); } export function parseUserDataDir(args: ParsedArgs, process: NodeJS.Process): string { diff --git a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts index b21c8851fb5a..59077e43363a 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts @@ -5,6 +5,7 @@ import { ILocalExtension, IGalleryExtension, IExtensionIdentifier, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { compareIgnoreCase } from 'vs/base/common/strings'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean { if (a.uuid && b.uuid) { @@ -16,6 +17,24 @@ export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifi return compareIgnoreCase(a.id, b.id) === 0; } +export class ExtensionIdentifierWithVersion { + constructor( + readonly identifier: IExtensionIdentifier, + readonly version: string + ) { } + + key(): string { + return `${this.identifier.id}-${this.version}`; + } + + equals(o: any): boolean { + if (!(o instanceof ExtensionIdentifierWithVersion)) { + return false; + } + return areSameExtensions(this.identifier, o.identifier) && this.version === o.version; + } +} + export function adoptToGalleryExtensionId(id: string): string { return id.toLocaleLowerCase(); } @@ -89,7 +108,7 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension): }; } -export const BetterMergeId = 'pprice.better-merge'; +export const BetterMergeId = new ExtensionIdentifier('pprice.better-merge'); export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set { const result = new Set(); diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index 00505439411c..0bdaba0e6caa 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -828,7 +828,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { /* __GDPR__ "galleryService:requestError" : { "url" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "cdn": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "cdn": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "message": { "classification": "CallstackOrException", "purpose": "FeatureInsight" } } */ diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 62f9c97fc775..86167face60f 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -21,7 +21,7 @@ import { INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localizeManifest } from '../common/extensionNls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Limiter, createCancelablePromise, CancelablePromise, Queue } from 'vs/base/common/async'; @@ -44,7 +44,7 @@ import { Schemas } from 'vs/base/common/network'; import { CancellationToken } from 'vs/base/common/cancellation'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; -import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT} import product from 'vs/platform/product/node/product'; @@ -117,16 +117,16 @@ export class ExtensionManagementService extends Disposable implements IExtension private readonly manifestCache: ExtensionsManifestCache; private readonly extensionLifecycle: ExtensionsLifecycle; - private readonly _onInstallExtension = new Emitter(); + private readonly _onInstallExtension = this._register(new Emitter()); readonly onInstallExtension: Event = this._onInstallExtension.event; - private readonly _onDidInstallExtension = new Emitter(); + private readonly _onDidInstallExtension = this._register(new Emitter()); readonly onDidInstallExtension: Event = this._onDidInstallExtension.event; - private readonly _onUninstallExtension = new Emitter(); + private readonly _onUninstallExtension = this._register(new Emitter()); readonly onUninstallExtension: Event = this._onUninstallExtension.event; - private _onDidUninstallExtension = new Emitter(); + private _onDidUninstallExtension = this._register(new Emitter()); onDidUninstallExtension: Event = this._onDidUninstallExtension.event; constructor( @@ -287,9 +287,6 @@ export class ExtensionManagementService extends Disposable implements IExtension async installFromGallery(extension: IGalleryExtension): Promise { const startTime = new Date().getTime(); - this.logService.info('Installing extension:', extension.identifier.id); - this._onInstallExtension.fire({ identifier: extension.identifier, gallery: extension }); - const onDidInstallExtensionSuccess = (extension: IGalleryExtension, operation: InstallOperation, local: ILocalExtension) => { this.logService.info(`Extensions installed successfully:`, extension.identifier.id); this._onDidInstallExtension.fire({ identifier: extension.identifier, gallery: extension, local, operation }); @@ -317,6 +314,9 @@ export class ExtensionManagementService extends Disposable implements IExtension let cancellablePromise = this.installingExtensions.get(key); if (!cancellablePromise) { + this.logService.info('Installing extension:', extension.identifier.id); + this._onInstallExtension.fire({ identifier: extension.identifier, gallery: extension }); + let operation: InstallOperation = InstallOperation.Install; let cancellationToken: CancellationToken, successCallback: (a?: any) => void, errorCallback: (e?: any) => any | null; cancellablePromise = createCancelablePromise(token => { cancellationToken = token; return new Promise((c, e) => { successCallback = c; errorCallback = e; }); }); @@ -367,7 +367,7 @@ export class ExtensionManagementService extends Disposable implements IExtension const compatibleExtension = await this.galleryService.getCompatibleExtension(extension); if (!compatibleExtension) { - return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE)); + return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE)); } return compatibleExtension; @@ -500,12 +500,12 @@ export class ExtensionManagementService extends Disposable implements IExtension e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING))); } - private rename(identfier: IExtensionIdentifier, extractPath: string, renamePath: string, retryUntil: number): Promise { + private rename(identifier: IExtensionIdentifier, extractPath: string, renamePath: string, retryUntil: number): Promise { return pfs.rename(extractPath, renamePath) .then(undefined, error => { if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) { - this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`, identfier.id); - return this.rename(identfier, extractPath, renamePath, retryUntil); + this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`, identifier.id); + return this.rename(identifier, extractPath, renamePath, retryUntil); } return Promise.reject(new ExtensionManagementError(error.message || nls.localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING)); }); @@ -743,7 +743,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this.logService.trace('Started scanning system extensions'); const systemExtensionsPromise = this.scanExtensions(this.systemExtensionsPath, ExtensionType.System) .then(result => { - this.logService.info('Scanned system extensions:', result.length); + this.logService.trace('Scanned system extensions:', result.length); return result; }); if (this.environmentService.isBuilt) { @@ -756,7 +756,7 @@ export class ExtensionManagementService extends Disposable implements IExtension if (devSystemExtensionsList.length) { return this.scanExtensions(this.devSystemExtensionsPath, ExtensionType.System) .then(result => { - this.logService.info('Scanned dev system extensions:', result.length); + this.logService.trace('Scanned dev system extensions:', result.length); return result.filter(r => devSystemExtensionsList.some(id => areSameExtensions(r.identifier, { id }))); }); } else { @@ -776,7 +776,7 @@ export class ExtensionManagementService extends Disposable implements IExtension const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => e.identifier); extensions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]); } - this.logService.info('Scanned user extensions:', extensions.length); + this.logService.trace('Scanned user extensions:', extensions.length); return extensions; }); } @@ -806,19 +806,29 @@ export class ExtensionManagementService extends Disposable implements IExtension .then(undefined, () => null); } - removeDeprecatedExtensions(): Promise { - return this.removeUninstalledExtensions() - .then(() => this.removeOutdatedExtensions()); + async removeDeprecatedExtensions(): Promise { + await this.removeUninstalledExtensions(); + await this.removeOutdatedExtensions(); } - private removeUninstalledExtensions(): Promise { - return this.getUninstalledExtensions() - .then(uninstalled => this.scanExtensions(this.extensionsPath, ExtensionType.User) // All user extensions - .then(extensions => { - const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]); - return Promise.all(toRemove.map(e => this.extensionLifecycle.postUninstall(e).then(() => this.removeUninstalledExtension(e)))); - }) - ).then(() => undefined); + private async removeUninstalledExtensions(): Promise { + const uninstalled = await this.getUninstalledExtensions(); + const extensions = await this.scanExtensions(this.extensionsPath, ExtensionType.User); // All user extensions + const installed: Set = new Set(); + for (const e of extensions) { + if (!uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]) { + installed.add(e.identifier.id.toLowerCase()); + } + } + const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => e.identifier); + await Promise.all(byExtension.map(async e => { + const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]; + if (!installed.has(latest.identifier.id.toLowerCase())) { + await this.extensionLifecycle.postUninstall(latest); + } + })); + const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]); + await Promise.all(toRemove.map(e => this.removeUninstalledExtension(e))); } private removeOutdatedExtensions(): Promise { @@ -845,8 +855,8 @@ export class ExtensionManagementService extends Disposable implements IExtension return pfs.rimraf(extension.location.fsPath).then(() => this.logService.info('Deleted from disk', extension.identifier.id, extension.location.fsPath)); } - private isUninstalled(identfier: ExtensionIdentifierWithVersion): Promise { - return this.filterUninstalled(identfier).then(uninstalled => uninstalled.length === 1); + private isUninstalled(identifier: ExtensionIdentifierWithVersion): Promise { + return this.filterUninstalled(identifier).then(uninstalled => uninstalled.length === 1); } private filterUninstalled(...identifiers: ExtensionIdentifierWithVersion[]): Promise { diff --git a/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts b/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts index 7804936c4356..e160016700e9 100644 --- a/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts +++ b/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts @@ -16,11 +16,11 @@ export class ExtensionsManifestCache extends Disposable { constructor( private readonly environmentService: IEnvironmentService, - extensionsManagementServuce: IExtensionManagementService + extensionsManagementService: IExtensionManagementService ) { super(); - this._register(extensionsManagementServuce.onDidInstallExtension(e => this.onDidInstallExtension(e))); - this._register(extensionsManagementServuce.onDidUninstallExtension(e => this.onDidUnInstallExtension(e))); + this._register(extensionsManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e))); + this._register(extensionsManagementService.onDidUninstallExtension(e => this.onDidUnInstallExtension(e))); } private onDidInstallExtension(e: DidInstallExtensionEvent): void { diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index a9f503039c56..b3eb2e9d81f1 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import * as strings from 'vs/base/common/strings'; import { ILocalization } from 'vs/platform/localizations/common/localizations'; import { URI } from 'vs/base/common/uri'; @@ -109,24 +108,6 @@ export interface IExtensionContributions { export type ExtensionKind = 'ui' | 'workspace'; -export class ExtensionIdentifierWithVersion { - constructor( - readonly identifier: IExtensionIdentifier, - readonly version: string - ) { } - - key(): string { - return `${this.identifier.id}-${this.version}`; - } - - equals(o: any): boolean { - if (!(o instanceof ExtensionIdentifierWithVersion)) { - return false; - } - return areSameExtensions(this.identifier, o.identifier) && this.version === o.version; - } -} - export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier { return thing && typeof thing === 'object' diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 051f19e14631..49fc2e784a8b 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -517,7 +517,7 @@ interface IBaseStat { resource: URI; /** - * The name which is the last segement + * The name which is the last segment * of the {{path}}. */ name: string; @@ -531,7 +531,7 @@ interface IBaseStat { size?: number; /** - * The last modifictaion date represented + * The last modification date represented * as millis from unix epoch. * * The value may or may not be resolved as diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 7788d772d1a3..ae198bf70c6e 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -22,6 +22,8 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { getSimpleWorkspaceLabel } from 'vs/platform/label/common/label'; import { toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData } from 'vs/platform/history/electron-main/historyStorage'; import { exists } from 'vs/base/node/pfs'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; export class HistoryMainService implements IHistoryMainService { @@ -37,10 +39,10 @@ export class HistoryMainService implements IHistoryMainService { private static readonly recentlyOpenedStorageKey = 'openedPathsList'; - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private _onRecentlyOpenedChange = new Emitter(); - onRecentlyOpenedChange: CommonEvent = this._onRecentlyOpenedChange.event; + readonly onRecentlyOpenedChange: CommonEvent = this._onRecentlyOpenedChange.event; private macOSRecentDocumentsUpdater: ThrottledDelayer; @@ -48,9 +50,21 @@ export class HistoryMainService implements IHistoryMainService { @IStateService private readonly stateService: IStateService, @ILogService private readonly logService: ILogService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @ILifecycleService lifecycleService: ILifecycleService ) { this.macOSRecentDocumentsUpdater = new ThrottledDelayer(800); + + lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList()); + } + + private handleWindowsJumpList(): void { + if (!isWindows) { + return; // only on windows + } + + this.updateWindowsJumpList(); + this.onRecentlyOpenedChange(() => this.updateWindowsJumpList()); } addRecentlyOpened(newlyAdded: IRecent[]): void { diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index e56e69cd7d21..95e7a9af99bd 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -15,7 +15,6 @@ import { IKeybindingEvent, IKeybindingService, IKeyboardEvent } from 'vs/platfor import { IResolveResult, KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; interface CurrentChord { @@ -26,45 +25,32 @@ interface CurrentChord { export abstract class AbstractKeybindingService extends Disposable implements IKeybindingService { public _serviceBrand: any; + protected readonly _onDidUpdateKeybindings: Emitter = this._register(new Emitter()); + get onDidUpdateKeybindings(): Event { + return this._onDidUpdateKeybindings ? this._onDidUpdateKeybindings.event : Event.None; // Sinon stubbing walks properties on prototype + } + private _currentChord: CurrentChord | null; private _currentChordChecker: IntervalTimer; private _currentChordStatusMessage: IDisposable | null; - protected _onDidUpdateKeybindings: Emitter; - - private _contextKeyService: IContextKeyService; - private _statusService: IStatusbarService | undefined; - private _notificationService: INotificationService; - protected _commandService: ICommandService; - protected _telemetryService: ITelemetryService; constructor( - contextKeyService: IContextKeyService, - commandService: ICommandService, - telemetryService: ITelemetryService, - notificationService: INotificationService, - statusService?: IStatusbarService + private _contextKeyService: IContextKeyService, + protected _commandService: ICommandService, + protected _telemetryService: ITelemetryService, + private _notificationService: INotificationService, ) { super(); - this._contextKeyService = contextKeyService; - this._commandService = commandService; - this._telemetryService = telemetryService; - this._statusService = statusService; - this._notificationService = notificationService; this._currentChord = null; this._currentChordChecker = new IntervalTimer(); this._currentChordStatusMessage = null; - this._onDidUpdateKeybindings = this._register(new Emitter()); } public dispose(): void { super.dispose(); } - get onDidUpdateKeybindings(): Event { - return this._onDidUpdateKeybindings ? this._onDidUpdateKeybindings.event : Event.None; // Sinon stubbing walks properties on prototype - } - protected abstract _getResolver(): KeybindingResolver; protected abstract _documentHasFocus(): boolean; public abstract resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[]; @@ -128,9 +114,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK keypress: firstPart, label: keypressLabel }; - if (this._statusService) { - this._currentChordStatusMessage = this._statusService.setStatusMessage(nls.localize('first.chord', "({0}) was pressed. Waiting for second key of chord...", keypressLabel)); - } + this._currentChordStatusMessage = this._notificationService.status(nls.localize('first.chord', "({0}) was pressed. Waiting for second key of chord...", keypressLabel)); const chordEnterTime = Date.now(); this._currentChordChecker.cancelAndSet(() => { @@ -192,9 +176,9 @@ export abstract class AbstractKeybindingService extends Disposable implements IK return shouldPreventDefault; } - if (this._statusService && this._currentChord) { + if (this._currentChord) { if (!resolveResult || !resolveResult.commandId) { - this._statusService.setStatusMessage(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", this._currentChord.label, keypressLabel), 10 * 1000 /* 10s */); + this._notificationService.status(nls.localize('missing.chord', "The key combination ({0}, {1}) is not a command.", this._currentChord.label, keypressLabel), { hideAfter: 10 * 1000 /* 10s */ }); shouldPreventDefault = true; } } diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index d477d1679cf5..ab4bdcadfd58 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { KeyChord, KeyCode, KeyMod, Keybinding, ResolvedKeybinding, SimpleKeybinding, createKeybinding, createSimpleKeybinding } from 'vs/base/common/keyCodes'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { OS } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -14,9 +13,9 @@ import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; -import { INotification, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification } from 'vs/platform/notification/common/notification'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; +import { INotification, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; function createContext(ctx: any) { return { @@ -35,10 +34,9 @@ suite('AbstractKeybindingService', () => { resolver: KeybindingResolver, contextKeyService: IContextKeyService, commandService: ICommandService, - notificationService: INotificationService, - statusService?: IStatusbarService + notificationService: INotificationService ) { - super(contextKeyService, commandService, NullTelemetryService, notificationService, statusService); + super(contextKeyService, commandService, NullTelemetryService, notificationService); this._resolver = resolver; } @@ -129,7 +127,7 @@ suite('AbstractKeybindingService', () => { }; let notificationService: INotificationService = { - _serviceBrand: undefined, + _serviceBrand: {} as ServiceIdentifier, notify: (notification: INotification) => { showMessageCalls.push({ sev: notification.severity, message: notification.message }); return new NoOpNotification(); @@ -148,13 +146,8 @@ suite('AbstractKeybindingService', () => { }, prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions) { throw new Error('not implemented'); - } - }; - - let statusbarService: IStatusbarService = { - _serviceBrand: undefined, - addEntry: undefined!, - setStatusMessage: (message: string, autoDisposeAfter?: number, delayBy?: number): IDisposable => { + }, + status(message: string, options?: IStatusMessageOptions) { statusMessageCalls!.push(message); return { dispose: () => { @@ -166,7 +159,7 @@ suite('AbstractKeybindingService', () => { let resolver = new KeybindingResolver(items, []); - return new TestKeybindingService(resolver, contextKeyService, commandService, notificationService, statusbarService); + return new TestKeybindingService(resolver, contextKeyService, commandService, notificationService); }; }); diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index 45470961db06..1f01c4528ba8 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -8,7 +8,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IURLService } from 'vs/platform/url/common/url'; import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { OpenContext, IWindowSettings } from 'vs/platform/windows/common/windows'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { whenDeleted } from 'vs/base/node/pfs'; @@ -107,7 +107,7 @@ export class LaunchChannel implements IServerChannel { export class LaunchChannelClient implements ILaunchService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; constructor(private channel: IChannel) { } @@ -134,7 +134,7 @@ export class LaunchChannelClient implements ILaunchService { export class LaunchService implements ILaunchService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; constructor( @ILogService private readonly logService: ILogService, diff --git a/src/vs/platform/lifecycle/common/lifecycle.ts b/src/vs/platform/lifecycle/common/lifecycle.ts index ac1c8b6c997f..22e4d2ca0b6f 100644 --- a/src/vs/platform/lifecycle/common/lifecycle.ts +++ b/src/vs/platform/lifecycle/common/lifecycle.ts @@ -29,7 +29,7 @@ export interface BeforeShutdownEvent { /** * The reason why the application will be shutting down. */ - reason: ShutdownReason; + readonly reason: ShutdownReason; } /** @@ -51,7 +51,7 @@ export interface WillShutdownEvent { /** * The reason why the application is shutting down. */ - reason: ShutdownReason; + readonly reason: ShutdownReason; } export const enum ShutdownReason { diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts index 3c53a936cbc1..1a0a1e90896a 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts @@ -7,11 +7,12 @@ import { ipcMain as ipc, app } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/common/state'; import { Event, Emitter } from 'vs/base/common/event'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; +import { Barrier } from 'vs/base/common/async'; export const ILifecycleService = createDecorator('lifecycleService'); @@ -38,42 +39,48 @@ export interface ShutdownEvent { } export interface ILifecycleService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; /** * Will be true if the program was restarted (e.g. due to explicit request or update). */ - wasRestarted: boolean; + readonly wasRestarted: boolean; /** * Will be true if the program was requested to quit. */ - quitRequested: boolean; + readonly quitRequested: boolean; + + /** + * A flag indicating in what phase of the lifecycle we currently are. + */ + phase: LifecycleMainPhase; /** * An event that fires when the application is about to shutdown before any window is closed. * The shutdown can still be prevented by any window that vetos this event. */ - onBeforeShutdown: Event; + readonly onBeforeShutdown: Event; /** * An event that fires after the onBeforeShutdown event has been fired and after no window has * vetoed the shutdown sequence. At this point listeners are ensured that the application will * quit without veto. */ - onWillShutdown: Event; + readonly onWillShutdown: Event; /** * An event that fires before a window closes. This event is fired after any veto has been dealt * with so that listeners know for sure that the window will close without veto. */ - onBeforeWindowClose: Event; + readonly onBeforeWindowClose: Event; /** * An event that fires before a window is about to unload. Listeners can veto this event to prevent * the window from unloading. */ - onBeforeWindowUnload: Event; + readonly onBeforeWindowUnload: Event; /** * Unload a window for the provided reason. All lifecycle event handlers are triggered. @@ -94,6 +101,32 @@ export interface ILifecycleService { * Forcefully shutdown the application. No livecycle event handlers are triggered. */ kill(code?: number): void; + + /** + * Returns a promise that resolves when a certain lifecycle phase + * has started. + */ + when(phase: LifecycleMainPhase): Promise; +} + +export const enum LifecycleMainPhase { + + /** + * The first phase signals that we are about to startup. + */ + Starting = 1, + + /** + * Services are ready and first window is about to open. + */ + Ready = 2, + + /** + * This phase signals a point in time after the window has opened + * and is typically the best place to do work that is not required + * for the window to open. + */ + AfterWindowOpen = 3 } export class LifecycleService extends Disposable implements ILifecycleService { @@ -102,7 +135,7 @@ export class LifecycleService extends Disposable implements ILifecycleService { private static readonly QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted - private windowToCloseRequest: { [windowId: string]: boolean } = Object.create(null); + private windowToCloseRequest: Set = new Set(); private oneTimeListenerTokenGenerator = 0; private windowCounter = 0; @@ -129,6 +162,11 @@ export class LifecycleService extends Disposable implements ILifecycleService { private readonly _onBeforeWindowUnload = this._register(new Emitter()); readonly onBeforeWindowUnload: Event = this._onBeforeWindowUnload.event; + private _phase: LifecycleMainPhase = LifecycleMainPhase.Starting; + get phase(): LifecycleMainPhase { return this._phase; } + + private phaseWhen = new Map(); + constructor( @ILogService private readonly logService: ILogService, @IStateService private readonly stateService: IStateService @@ -136,6 +174,7 @@ export class LifecycleService extends Disposable implements ILifecycleService { super(); this.handleRestarted(); + this.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); } private handleRestarted(): void { @@ -146,10 +185,6 @@ export class LifecycleService extends Disposable implements ILifecycleService { } } - ready(): void { - this.registerListeners(); - } - private registerListeners(): void { // before-quit: an event that is fired if application quit was @@ -238,6 +273,40 @@ export class LifecycleService extends Disposable implements ILifecycleService { return this.pendingWillShutdownPromise; } + set phase(value: LifecycleMainPhase) { + if (value < this.phase) { + throw new Error('Lifecycle cannot go backwards'); + } + + if (this._phase === value) { + return; + } + + this.logService.trace(`lifecycle (main): phase changed (value: ${value})`); + + this._phase = value; + + const barrier = this.phaseWhen.get(this._phase); + if (barrier) { + barrier.open(); + this.phaseWhen.delete(this._phase); + } + } + + async when(phase: LifecycleMainPhase): Promise { + if (phase <= this._phase) { + return; + } + + let barrier = this.phaseWhen.get(phase); + if (!barrier) { + barrier = new Barrier(); + this.phaseWhen.set(phase, barrier); + } + + await barrier.wait(); + } + registerWindow(window: ICodeWindow): void { // track window count @@ -248,8 +317,8 @@ export class LifecycleService extends Disposable implements ILifecycleService { // The window already acknowledged to be closed const windowId = window.id; - if (this.windowToCloseRequest[windowId]) { - delete this.windowToCloseRequest[windowId]; + if (this.windowToCloseRequest.has(windowId)) { + this.windowToCloseRequest.delete(windowId); return; } @@ -260,11 +329,11 @@ export class LifecycleService extends Disposable implements ILifecycleService { e.preventDefault(); this.unload(window, UnloadReason.CLOSE).then(veto => { if (veto) { - delete this.windowToCloseRequest[windowId]; + this.windowToCloseRequest.delete(windowId); return; } - this.windowToCloseRequest[windowId] = true; + this.windowToCloseRequest.add(windowId); // Fire onBeforeWindowClose before actually closing this.logService.trace(`Lifecycle#onBeforeWindowClose.fire() - window ID ${windowId}`); @@ -390,10 +459,6 @@ export class LifecycleService extends Disposable implements ILifecycleService { }); } - /** - * A promise that completes to indicate if the quit request has been veto'd - * by the user or not. - */ quit(fromUpdate?: boolean): Promise { if (this.pendingQuitPromise) { return this.pendingQuitPromise; diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index a1aea75a55a7..83cf9898bc81 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -8,7 +8,7 @@ import { IListMouseEvent, IListTouchEvent, IListRenderer, IListVirtualDelegate } import { IPagedRenderer, PagedList } from 'vs/base/browser/ui/list/listPaging'; import { DefaultStyleController, IListOptions, IMultipleSelectionController, IOpenController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List } from 'vs/base/browser/ui/list/listWidget'; import { Emitter, Event } from 'vs/base/common/event'; -import { combinedDisposable, Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, dispose, IDisposable, toDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; @@ -78,7 +78,7 @@ export class ListService implements IListService { this._lastFocusedWidget = widget; } - const result = combinedDisposable([ + return combinedDisposable( widget.onDidFocus(() => this._lastFocusedWidget = widget), toDisposable(() => this.lists.splice(this.lists.indexOf(registeredList), 1)), widget.onDidDispose(() => { @@ -87,9 +87,7 @@ export class ListService implements IListService { this._lastFocusedWidget = undefined; } }) - ]); - - return result; + ); } } @@ -200,18 +198,18 @@ class WorkbenchOpenController extends Disposable implements IOpenController { } function toWorkbenchListOptions(options: IListOptions, configurationService: IConfigurationService, keybindingService: IKeybindingService): [IListOptions, IDisposable] { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const result = { ...options }; if (options.multipleSelectionSupport !== false && !options.multipleSelectionController) { const multipleSelectionController = new MultipleSelectionController(configurationService); result.multipleSelectionController = multipleSelectionController; - disposables.push(multipleSelectionController); + disposables.add(multipleSelectionController); } const openController = new WorkbenchOpenController(configurationService, options.openController); result.openController = openController; - disposables.push(openController); + disposables.add(openController); if (options.keyboardNavigationLabelProvider) { const tlp = options.keyboardNavigationLabelProvider; @@ -222,7 +220,7 @@ function toWorkbenchListOptions(options: IListOptions, configurationServic }; } - return [result, combinedDisposable(disposables)]; + return [result, disposables]; } let sharedListStyleSheet: HTMLStyleElement; @@ -283,7 +281,7 @@ export class WorkbenchList extends List { this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); - this.disposables.push(combinedDisposable([ + this.disposables.push( this.contextKeyService, (listService as ListService).register(this), attachListStyler(this, themeService), @@ -301,7 +299,7 @@ export class WorkbenchList extends List { this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0); }) - ])); + ); this.registerListeners(); } @@ -324,7 +322,7 @@ export class WorkbenchPagedList extends PagedList { readonly contextKeyService: IContextKeyService; private readonly configurationService: IConfigurationService; - private disposables: IDisposable[]; + private readonly disposables: DisposableStore; private _useAltAsMultipleSelectionModifier: boolean; @@ -351,7 +349,8 @@ export class WorkbenchPagedList extends PagedList { } ); - this.disposables = [workbenchListOptionsDisposable]; + this.disposables = new DisposableStore(); + this.disposables.add(workbenchListOptionsDisposable); this.contextKeyService = createScopedContextKeyService(contextKeyService, this); this.configurationService = configurationService; @@ -361,17 +360,15 @@ export class WorkbenchPagedList extends PagedList { this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); - this.disposables.push(combinedDisposable([ - this.contextKeyService, - (listService as ListService).register(this), - attachListStyler(this, themeService) - ])); + this.disposables.add(this.contextKeyService); + this.disposables.add((listService as ListService).register(this)); + this.disposables.add(attachListStyler(this, themeService)); this.registerListeners(); } private registerListeners(): void { - this.disposables.push(this.configurationService.onDidChangeConfiguration(e => { + this.disposables.add(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(multiSelectModifierSettingKey)) { this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService); } @@ -385,7 +382,7 @@ export class WorkbenchPagedList extends PagedList { dispose(): void { super.dispose(); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } @@ -533,7 +530,7 @@ function massageControllerOptions(options: IControllerOptions): IControllerOptio */ export class WorkbenchTreeController extends DefaultController { - protected disposables: IDisposable[] = []; + protected readonly disposables = new DisposableStore(); constructor( options: IControllerOptions, @@ -549,7 +546,7 @@ export class WorkbenchTreeController extends DefaultController { } private registerListeners(): void { - this.disposables.push(this.configurationService.onDidChangeConfiguration(e => { + this.disposables.add(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(openModeSettingKey)) { this.setOpenMode(this.getOpenModeSetting()); } @@ -561,7 +558,7 @@ export class WorkbenchTreeController extends DefaultController { } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); } } diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index c50401d9f91e..25a67c3b41b5 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -186,7 +186,7 @@ export class ConsoleLogService extends AbstractLogService implements ILogService export class MultiplexLogService extends AbstractLogService implements ILogService { _serviceBrand: any; - constructor(private logServices: ILogService[]) { + constructor(private readonly logServices: ReadonlyArray) { super(); if (logServices.length) { this.setLevel(logServices[0].getLevel()); diff --git a/src/vs/platform/log/node/spdlogService.ts b/src/vs/platform/log/node/spdlogService.ts index b4326bab5e30..ca02c8c3ac5f 100644 --- a/src/vs/platform/log/node/spdlogService.ts +++ b/src/vs/platform/log/node/spdlogService.ts @@ -4,24 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'vs/base/common/path'; -import { ILogService, LogLevel, NullLogService, AbstractLogService } from 'vs/platform/log/common/log'; +import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log'; import * as spdlog from 'spdlog'; -import { BufferLogService } from 'vs/platform/log/common/bufferLog'; -export async function createSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string): Promise { +async function createSpdLogLogger(processName: string, logsFolder: string): Promise { // Do not crash if spdlog cannot be loaded try { - const _spdlog: typeof spdlog = require.__$__nodeRequire('spdlog'); + const _spdlog = await import('spdlog'); _spdlog.setAsyncMode(8192, 500); const logfilePath = path.join(logsFolder, `${processName}.log`); - const logger = await _spdlog.createRotatingLoggerAsync(processName, logfilePath, 1024 * 1024 * 5, 6); - logger.setLevel(0); - - return new SpdLogService(logger, logLevel); + return _spdlog.createRotatingLoggerAsync(processName, logfilePath, 1024 * 1024 * 5, 6); } catch (e) { console.error(e); } - return new NullLogService(); + return null; } export function createRotatingLogger(name: string, filename: string, filesize: number, filecount: number): spdlog.RotatingLogger { @@ -29,45 +25,88 @@ export function createRotatingLogger(name: string, filename: string, filesize: n return _spdlog.createRotatingLogger(name, filename, filesize, filecount); } -export function createBufferSpdLogService(processName: string, logLevel: LogLevel, logsFolder: string): ILogService { - const bufferLogService = new BufferLogService(); - createSpdLogService(processName, logLevel, logsFolder).then(logger => bufferLogService.logger = logger); - return bufferLogService; +interface ILog { + level: LogLevel; + message: string; +} + +function log(logger: spdlog.RotatingLogger, level: LogLevel, message: string): void { + switch (level) { + case LogLevel.Trace: return logger.trace(message); + case LogLevel.Debug: return logger.debug(message); + case LogLevel.Info: return logger.info(message); + case LogLevel.Warning: return logger.warn(message); + case LogLevel.Error: return logger.error(message); + case LogLevel.Critical: return logger.critical(message); + default: throw new Error('Invalid log level'); + } } -class SpdLogService extends AbstractLogService implements ILogService { +export class SpdLogService extends AbstractLogService implements ILogService { _serviceBrand: any; - constructor( - private readonly logger: spdlog.RotatingLogger, - level: LogLevel = LogLevel.Error - ) { + private buffer: ILog[] = []; + private _loggerCreationPromise: Promise | undefined = undefined; + private _logger: spdlog.RotatingLogger | undefined; + + constructor(private readonly name: string, private readonly logsFolder: string, level: LogLevel) { super(); this.setLevel(level); + this._createSpdLogLogger(); + this._register(this.onDidChangeLogLevel(level => { + if (this._logger) { + this._logger.setLevel(level); + } + })); + } + + private _createSpdLogLogger(): Promise { + if (!this._loggerCreationPromise) { + this._loggerCreationPromise = createSpdLogLogger(this.name, this.logsFolder) + .then(logger => { + if (logger) { + this._logger = logger; + this._logger.setLevel(this.getLevel()); + for (const { level, message } of this.buffer) { + log(this._logger, level, message); + } + this.buffer = []; + } + }); + } + return this._loggerCreationPromise; + } + + private _log(level: LogLevel, message: string): void { + if (this._logger) { + log(this._logger, level, message); + } else if (this.getLevel() <= level) { + this.buffer.push({ level, message }); + } } trace(): void { if (this.getLevel() <= LogLevel.Trace) { - this.logger.trace(this.format(arguments)); + this._log(LogLevel.Trace, this.format(arguments)); } } debug(): void { if (this.getLevel() <= LogLevel.Debug) { - this.logger.debug(this.format(arguments)); + this._log(LogLevel.Debug, this.format(arguments)); } } info(): void { if (this.getLevel() <= LogLevel.Info) { - this.logger.info(this.format(arguments)); + this._log(LogLevel.Info, this.format(arguments)); } } warn(): void { if (this.getLevel() <= LogLevel.Warning) { - this.logger.warn(this.format(arguments)); + this._log(LogLevel.Warning, this.format(arguments)); } } @@ -78,21 +117,33 @@ class SpdLogService extends AbstractLogService implements ILogService { if (arg instanceof Error) { const array = Array.prototype.slice.call(arguments) as any[]; array[0] = arg.stack; - this.logger.error(this.format(array)); + this._log(LogLevel.Error, this.format(array)); } else { - this.logger.error(this.format(arguments)); + this._log(LogLevel.Error, this.format(arguments)); } } } critical(): void { if (this.getLevel() <= LogLevel.Critical) { - this.logger.critical(this.format(arguments)); + this._log(LogLevel.Critical, this.format(arguments)); } } dispose(): void { - this.logger.drop(); + if (this._logger) { + this.disposeLogger(); + } else if (this._loggerCreationPromise) { + this._loggerCreationPromise.then(() => this.disposeLogger()); + } + this._loggerCreationPromise = undefined; + } + + private disposeLogger(): void { + if (this._logger) { + this._logger.drop(); + this._logger = undefined; + } } private format(args: any): string { diff --git a/src/vs/platform/markers/common/markers.ts b/src/vs/platform/markers/common/markers.ts index 49d8bbc6a41c..77ddb65a5861 100644 --- a/src/vs/platform/markers/common/markers.ts +++ b/src/vs/platform/markers/common/markers.ts @@ -71,6 +71,15 @@ export namespace MarkerSeverity { case Severity.Ignore: return MarkerSeverity.Hint; } } + + export function toSeverity(severity: MarkerSeverity): Severity { + switch (severity) { + case MarkerSeverity.Error: return Severity.Error; + case MarkerSeverity.Warning: return Severity.Warning; + case MarkerSeverity.Info: return Severity.Info; + case MarkerSeverity.Hint: return Severity.Ignore; + } + } } /** diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 926016c9828a..0451959be9d4 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import BaseSeverity from 'vs/base/common/severity'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IAction } from 'vs/base/common/actions'; import { Event, Emitter } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; export import Severity = BaseSeverity; @@ -69,14 +70,14 @@ export interface INotificationActions { * Primary actions show up as buttons as part of the message and will close * the notification once clicked. */ - primary?: IAction[]; + primary?: ReadonlyArray; /** * Secondary actions are meant to provide additional configuration or context * for the notification and will show up less prominent. A notification does not * close automatically when invoking a secondary action. */ - secondary?: IAction[]; + secondary?: ReadonlyArray; } export interface INotificationProgress { @@ -172,6 +173,21 @@ export interface IPromptOptions extends INotificationProperties { onCancel?: () => void; } +export interface IStatusMessageOptions { + + /** + * An optional timeout after which the status message should show. By default + * the status message will show immediately. + */ + showAfter?: number; + + /** + * An optional timeout after which the status message is to be hidden. By default + * the status message will not hide until another status message is displayed. + */ + hideAfter?: number; +} + /** * A service to bring up notifications and non-modal prompts. * @@ -179,7 +195,7 @@ export interface IPromptOptions extends INotificationProperties { */ export interface INotificationService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; /** * Show the provided notification to the user. The returned `INotificationHandle` @@ -221,16 +237,24 @@ export interface INotificationService { * @returns a handle on the notification to e.g. hide it or update message, buttons, etc. */ prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle; + + /** + * Shows a status message in the status area with the provied text. + * + * @param message the message to show as status + * @param options provides some optional configuration options + * + * @returns a disposable to hide the status message + */ + status(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable; } export class NoOpNotification implements INotificationHandle { + readonly progress = new NoOpProgress(); private readonly _onDidClose: Emitter = new Emitter(); - - get onDidClose(): Event { - return this._onDidClose.event; - } + get onDidClose(): Event { return this._onDidClose.event; } updateSeverity(severity: Severity): void { } updateMessage(message: NotificationMessage): void { } diff --git a/src/vs/platform/notification/test/common/testNotificationService.ts b/src/vs/platform/notification/test/common/testNotificationService.ts index 7f6ec1ef9f76..9a00472d7c87 100644 --- a/src/vs/platform/notification/test/common/testNotificationService.ts +++ b/src/vs/platform/notification/test/common/testNotificationService.ts @@ -3,31 +3,36 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotificationService, INotificationHandle, NoOpNotification, Severity, INotification, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotificationHandle, NoOpNotification, Severity, INotification, IPromptChoice, IPromptOptions, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; export class TestNotificationService implements INotificationService { - public _serviceBrand: any; + _serviceBrand: any; private static readonly NO_OP: INotificationHandle = new NoOpNotification(); - public info(message: string): INotificationHandle { + info(message: string): INotificationHandle { return this.notify({ severity: Severity.Info, message }); } - public warn(message: string): INotificationHandle { + warn(message: string): INotificationHandle { return this.notify({ severity: Severity.Warning, message }); } - public error(error: string | Error): INotificationHandle { + error(error: string | Error): INotificationHandle { return this.notify({ severity: Severity.Error, message: error }); } - public notify(notification: INotification): INotificationHandle { + notify(notification: INotification): INotificationHandle { return TestNotificationService.NO_OP; } - public prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { + prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { return TestNotificationService.NO_OP; } + + status(message: string | Error, options?: IStatusMessageOptions): IDisposable { + return Disposable.None; + } } \ No newline at end of file diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 5f47886895df..b25f1799b2fc 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -10,8 +10,13 @@ export const IProductService = createDecorator('productService' export interface IProductService { _serviceBrand: any; - version?: string; + version: string; commit?: string; + nameLong: string; + urlProtocol: string; + extensionAllowedProposedApi: string[]; + uiExtensions?: string[]; + enableTelemetry: boolean; } diff --git a/src/vs/platform/product/node/product.ts b/src/vs/platform/product/node/product.ts index b06984c69fcd..4ecce8cf1321 100644 --- a/src/vs/platform/product/node/product.ts +++ b/src/vs/platform/product/node/product.ts @@ -81,7 +81,6 @@ export interface IProductConfiguration { hockeyApp: { 'win32-ia32': string; 'win32-x64': string; - 'linux-ia32': string; 'linux-x64': string; 'darwin': string; }; diff --git a/src/vs/platform/product/node/productService.ts b/src/vs/platform/product/node/productService.ts index 45deb8e564fd..66f5e6504c56 100644 --- a/src/vs/platform/product/node/productService.ts +++ b/src/vs/platform/product/node/productService.ts @@ -11,9 +11,17 @@ export class ProductService implements IProductService { _serviceBrand: any; - get version(): string | undefined { return pkg.version; } + get version(): string { return pkg.version; } get commit(): string | undefined { return product.commit; } + get nameLong(): string { return product.nameLong; } + + get urlProtocol(): string { return product.urlProtocol; } + + get extensionAllowedProposedApi(): string[] { return product.extensionAllowedProposedApi; } + + get uiExtensions(): string[] | undefined { return product.uiExtensions; } + get enableTelemetry(): boolean { return product.enableTelemetry; } } diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index b605d13714e4..17b6b6e1c25c 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -3,15 +3,32 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; export const IProgressService = createDecorator('progressService'); +/** + * A progress service that can be used to report progress to various locations of the UI. + */ export interface IProgressService { - _serviceBrand: any; + + _serviceBrand: ServiceIdentifier; + + withProgress(options: IProgressOptions | IProgressNotificationOptions | IProgressCompositeOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise; +} + +export const ILocalProgressService = createDecorator('localProgressService'); + +/** + * A progress service that will report progress local to the UI piece triggered from. E.g. + * if used from an action of a viewlet, the progress will be reported in that viewlet. + */ +export interface ILocalProgressService { + + _serviceBrand: ServiceIdentifier; /** * Show progress customized with the provided flags. @@ -44,9 +61,14 @@ export interface IProgressOptions { } export interface IProgressNotificationOptions extends IProgressOptions { - location: ProgressLocation.Notification; - primaryActions?: IAction[]; - secondaryActions?: IAction[]; + readonly location: ProgressLocation.Notification; + readonly primaryActions?: ReadonlyArray; + readonly secondaryActions?: ReadonlyArray; +} + +export interface IProgressCompositeOptions extends IProgressOptions { + location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string; + delay?: number; } export interface IProgressStep { @@ -54,15 +76,6 @@ export interface IProgressStep { increment?: number; } -export const IProgressService2 = createDecorator('progressService2'); - -export interface IProgressService2 { - - _serviceBrand: any; - - withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise; -} - export interface IProgressRunner { total(value: number): void; worked(value: number): void; @@ -118,7 +131,7 @@ export class LongRunningOperation { private currentProgressTimeout: any; constructor( - private progressService: IProgressService + private localProgressService: ILocalProgressService ) { } start(progressDelay: number): IOperation { @@ -131,7 +144,7 @@ export class LongRunningOperation { const newOperationToken = new CancellationTokenSource(); this.currentProgressTimeout = setTimeout(() => { if (newOperationId === this.currentOperationId) { - this.currentProgressRunner = this.progressService.show(true); + this.currentProgressRunner = this.localProgressService.show(true); } }, progressDelay); diff --git a/src/vs/platform/registry/common/platform.ts b/src/vs/platform/registry/common/platform.ts index 04c360723a74..9049e880ed82 100644 --- a/src/vs/platform/registry/common/platform.ts +++ b/src/vs/platform/registry/common/platform.ts @@ -31,26 +31,22 @@ export interface IRegistry { class RegistryImpl implements IRegistry { - private data: { [id: string]: any; }; - - constructor() { - this.data = {}; - } + private readonly data = new Map(); public add(id: string, data: any): void { Assert.ok(Types.isString(id)); Assert.ok(Types.isObject(data)); - Assert.ok(!this.data.hasOwnProperty(id), 'There is already an extension with this id'); + Assert.ok(!this.data.has(id), 'There is already an extension with this id'); - this.data[id] = data; + this.data.set(id, data); } public knows(id: string): boolean { - return this.data.hasOwnProperty(id); + return this.data.has(id); } public as(id: string): any { - return this.data[id] || null; + return this.data.get(id) || null; } } diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 3bc199103c0d..02f066378e80 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -21,14 +21,11 @@ export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverS } clearResolvedAuthority(authority: string): void { - throw new Error(`Not implemented`); } setResolvedAuthority(resolvedAuthority: ResolvedAuthority) { - throw new Error(`Not implemented`); } setResolvedAuthorityError(authority: string, err: any): void { - throw new Error(`Not implemented`); } } diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index c3654c6e1fc6..60eade54f4a1 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { IRequestOptions, IRequestContext, IRequestFunction, request } from 'vs/base/node/request'; import { getProxyAgent } from 'vs/base/node/proxy'; @@ -16,21 +16,21 @@ import { CancellationToken } from 'vs/base/common/cancellation'; * This service exposes the `request` API, while using the global * or configured proxy settings. */ -export class RequestService implements IRequestService { +export class RequestService extends Disposable implements IRequestService { _serviceBrand: any; private proxyUrl?: string; private strictSSL: boolean; private authorization?: string; - private disposables: IDisposable[] = []; constructor( @IConfigurationService configurationService: IConfigurationService, @ILogService private readonly logService: ILogService ) { + super(); this.configure(configurationService.getValue()); - configurationService.onDidChangeConfiguration(() => this.configure(configurationService.getValue()), this, this.disposables); + this._register(configurationService.onDidChangeConfiguration(() => this.configure(configurationService.getValue()), this)); } private configure(config: IHTTPConfiguration) { diff --git a/src/vs/platform/severityIcon/common/severityIcon.ts b/src/vs/platform/severityIcon/common/severityIcon.ts new file mode 100644 index 000000000000..cb40532c6547 --- /dev/null +++ b/src/vs/platform/severityIcon/common/severityIcon.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Severity from 'vs/base/common/severity'; +import { registerThemingParticipant, ITheme, LIGHT } from 'vs/platform/theme/common/themeService'; +import { Color } from 'vs/base/common/color'; + +const errorStart = encodeURIComponent(``); +const errorDarkStart = encodeURIComponent(``); + +const warningStart = encodeURIComponent(``); +const warningDarkStart = encodeURIComponent(``); + +const infoStart = encodeURIComponent(``); +const infoDarkStart = encodeURIComponent(``); + +export namespace SeverityIcon { + + export function getSVGData(severity: Severity, theme: ITheme): string { + switch (severity) { + case Severity.Ignore: + const ignoreColor = theme.type === LIGHT ? Color.fromHex('#1BA1E2') : Color.fromHex('#1BA1E2'); + return theme.type === LIGHT ? infoStart + encodeURIComponent(ignoreColor.toString()) + infoEnd + : infoDarkStart + encodeURIComponent(ignoreColor.toString()) + infoDarkEnd; + case Severity.Info: + const infoColor = theme.type === LIGHT ? Color.fromHex('#1BA1E2') : Color.fromHex('#1BA1E2'); + return theme.type === LIGHT ? infoStart + encodeURIComponent(infoColor.toString()) + infoEnd + : infoDarkStart + encodeURIComponent(infoColor.toString()) + infoDarkEnd; + case Severity.Warning: + const warningColor = theme.type === LIGHT ? Color.fromHex('#fc0') : Color.fromHex('#fc0'); + return theme.type === LIGHT ? warningStart + encodeURIComponent(warningColor.toString()) + warningEnd + : warningDarkStart + encodeURIComponent(warningColor.toString()) + warningDarkEnd; + case Severity.Error: + const errorColor = theme.type === LIGHT ? Color.fromHex('#E51400') : Color.fromHex('#F48771'); + return theme.type === LIGHT ? errorStart + encodeURIComponent(errorColor.toString()) + errorEnd + : errorDarkStart + encodeURIComponent(errorColor.toString()) + errorDarkEnd; + } + return ''; + } + + export function className(severity: Severity): string { + switch (severity) { + case Severity.Ignore: + return 'severity-icon severity-ignore'; + case Severity.Info: + return 'severity-icon severity-info'; + case Severity.Warning: + return 'severity-icon severity-warning'; + case Severity.Error: + return 'severity-icon severity-error'; + } + return ''; + } +} + +function getCSSRule(severity: Severity, theme: ITheme): string { + return `.${SeverityIcon.className(severity).split(' ').join('.')} { background: url("data:image/svg+xml,${SeverityIcon.getSVGData(severity, theme)}") center center no-repeat; height: 16px; width: 16px; }`; +} + +registerThemingParticipant((theme, collector) => { + collector.addRule(getCSSRule(Severity.Error, theme)); + collector.addRule(getCSSRule(Severity.Warning, theme)); + collector.addRule(getCSSRule(Severity.Info, theme)); + collector.addRule(getCSSRule(Severity.Ignore, theme)); +}); \ No newline at end of file diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index b853abd108ee..1c65b9a89659 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -27,27 +27,36 @@ export class FileStorage { } async init(): Promise { + if (this._database) { + return; // return if database was already loaded + } + + const database = await this.loadAsync(); + + if (this._database) { + return; // return if database was already loaded + } + + this._database = database; + } + + private loadSync(): object { try { - const contents = await readFile(this.dbPath); + this.lastFlushedSerializedDatabase = fs.readFileSync(this.dbPath).toString(); - try { - this.lastFlushedSerializedDatabase = contents.toString(); - this._database = JSON.parse(this.lastFlushedSerializedDatabase); - } catch (error) { - this._database = {}; - } + return JSON.parse(this.lastFlushedSerializedDatabase); } catch (error) { if (error.code !== 'ENOENT') { this.onError(error); } - this._database = {}; + return {}; } } - private loadSync(): object { + private async loadAsync(): Promise { try { - this.lastFlushedSerializedDatabase = fs.readFileSync(this.dbPath).toString(); + this.lastFlushedSerializedDatabase = (await readFile(this.dbPath)).toString(); return JSON.parse(this.lastFlushedSerializedDatabase); } catch (error) { diff --git a/src/vs/platform/statusbar/common/statusbar.ts b/src/vs/platform/statusbar/common/statusbar.ts index cd7f01e10af3..e95e4adf6201 100644 --- a/src/vs/platform/statusbar/common/statusbar.ts +++ b/src/vs/platform/statusbar/common/statusbar.ts @@ -3,15 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export const IStatusbarService = createDecorator('statusbarService'); export const enum StatusbarAlignment { - LEFT, RIGHT + LEFT, + RIGHT } /** @@ -51,11 +51,6 @@ export interface IStatusbarEntry { */ readonly arguments?: any[]; - /** - * An optional extension ID if this entry is provided from an extension. - */ - readonly extensionId?: ExtensionIdentifier; - /** * Wether to show a beak above the status bar entry. */ @@ -64,18 +59,24 @@ export interface IStatusbarEntry { export interface IStatusbarService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; /** * Adds an entry to the statusbar with the given alignment and priority. Use the returned accessor * to update or remove the statusbar entry. + * + * @param id identifier of the entry is needed to allow users to hide entries via settings + * @param name human readable name the entry is about + * @param alignment either LEFT or RIGHT + * @param priority items get arranged from highest priority to lowest priority from left to right + * in their respective alignment slot */ - addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority?: number): IStatusbarEntryAccessor; + addEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority?: number): IStatusbarEntryAccessor; /** - * Prints something to the status bar area with optional auto dispose and delay. + * Allows to update an entry's visibilty with the provided ID. */ - setStatusMessage(message: string, autoDisposeAfter?: number, delayBy?: number): IDisposable; + updateEntryVisibility(id: string, visible: boolean): void; } export interface IStatusbarEntryAccessor extends IDisposable { diff --git a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts index e92747789ce5..aeab37b08117 100644 --- a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts @@ -14,7 +14,7 @@ export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; // {{ SQL CARBON EDIT }} import product from 'vs/platform/product/node/product'; -export async function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, installSourcePath: string): Promise<{ [name: string]: string | undefined }> { +export async function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, installSourcePath: string, remoteAuthority?: string): Promise<{ [name: string]: string | undefined }> { const result = await resolveCommonProperties(commit, version, machineId, installSourcePath); const instanceId = storageService.get(instanceStorageKey, StorageScope.GLOBAL)!; const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; @@ -32,11 +32,9 @@ export async function resolveWorkbenchCommonProperties(storageService: IStorageS // result['common.isNewSession'] = !lastSessionDate ? '1' : '0'; // __GDPR__COMMON__ "common.instanceId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } // result['common.instanceId'] = instanceId; + // __GDPR__COMMON__ "common.remoteAuthority" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + // result['common.remoteAuthority'] = cleanRemoteAuthority(remoteAuthority); - // __GDPR__COMMON__ "common.version.shell" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } - result['common.version.shell'] = process.versions && process.versions['electron']; - // __GDPR__COMMON__ "common.version.renderer" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } - result['common.version.renderer'] = process.versions && process.versions['chrome']; // {{SQL CARBON EDIT}} result['common.application.name'] = product.nameLong; // {{SQL CARBON EDIT}} @@ -62,5 +60,20 @@ function setUsageDates(storageService: IStorageService): void { // monthly last usage date const monthlyLastUseDate = storageService.get('telemetry.monthlyLastUseDate', StorageScope.GLOBAL, appStartDate.toUTCString()); storageService.store('telemetry.monthlyLastUseDate', monthlyLastUseDate, StorageScope.GLOBAL); +} + +function cleanRemoteAuthority(remoteAuthority?: string): string { + if (!remoteAuthority) { + return 'none'; + } + + let ret = 'other'; + // Whitelisted remote authorities + ['ssh-remote', 'dev-container', 'wsl'].forEach((res: string) => { + if (remoteAuthority!.indexOf(`${res}+`) === 0) { + ret = res; + } + }); + return ret; } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 5a01929b6477..5f42216ccb5d 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -266,6 +266,18 @@ export const menuSelectionBackground = registerColor('menu.selectionBackground', export const menuSelectionBorder = registerColor('menu.selectionBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('menuSelectionBorder', "Border color of the selected menu item in menus.")); export const menuSeparatorBackground = registerColor('menu.separatorBackground', { dark: '#BBBBBB', light: '#888888', hc: contrastBorder }, nls.localize('menuSeparatorBackground', "Color of a separator menu item in menus.")); +export const editorErrorForeground = registerColor('editorError.foreground', { dark: '#F48771', light: '#E51400', hc: null }, nls.localize('editorError.foreground', 'Foreground color of error squigglies in the editor.')); +export const editorErrorBorder = registerColor('editorError.border', { dark: null, light: null, hc: Color.fromHex('#E47777').transparent(0.8) }, nls.localize('errorBorder', 'Border color of error boxes in the editor.')); + +export const editorWarningForeground = registerColor('editorWarning.foreground', { dark: '#FFCC00', light: '#E9A700', hc: null }, nls.localize('editorWarning.foreground', 'Foreground color of warning squigglies in the editor.')); +export const editorWarningBorder = registerColor('editorWarning.border', { dark: null, light: null, hc: Color.fromHex('#FFCC00').transparent(0.8) }, nls.localize('warningBorder', 'Border color of warning boxes in the editor.')); + +export const editorInfoForeground = registerColor('editorInfo.foreground', { dark: '#008000', light: '#008000', hc: null }, nls.localize('editorInfo.foreground', 'Foreground color of info squigglies in the editor.')); +export const editorInfoBorder = registerColor('editorInfo.border', { dark: null, light: null, hc: Color.fromHex('#71B771').transparent(0.8) }, nls.localize('infoBorder', 'Border color of info boxes in the editor.')); + +export const editorHintForeground = registerColor('editorHint.foreground', { dark: Color.fromHex('#eeeeee').transparent(0.7), light: '#6c6c6c', hc: null }, nls.localize('editorHint.foreground', 'Foreground color of hint squigglies in the editor.')); +export const editorHintBorder = registerColor('editorHint.border', { dark: null, light: null, hc: Color.fromHex('#eeeeee').transparent(0.8) }, nls.localize('hintBorder', 'Border color of hint boxes in the editor.')); + /** * Editor background color. * Because of bug https://monacotools.visualstudio.com/DefaultCollection/Monaco/_workitems/edit/13254 diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 479077ef303a..31b5c9282fb9 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -351,4 +351,4 @@ export const defaultDialogStyles = { export function attachDialogStyler(widget: IThemable, themeService: IThemeService, style?: IDialogStyleOverrides): IDisposable { return attachStyler(themeService, { ...defaultDialogStyles, ...style }, widget); -} +} \ No newline at end of file diff --git a/src/vs/platform/theme/electron-main/themeMainService.ts b/src/vs/platform/theme/electron-main/themeMainService.ts new file mode 100644 index 000000000000..0ff6648cac32 --- /dev/null +++ b/src/vs/platform/theme/electron-main/themeMainService.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { systemPreferences, ipcMain as ipc } from 'electron'; +import { IStateService } from 'vs/platform/state/common/state'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +const DEFAULT_BG_LIGHT = '#FFFFFF'; +const DEFAULT_BG_DARK = '#1E1E1E'; +const DEFAULT_BG_HC_BLACK = '#000000'; + +const THEME_STORAGE_KEY = 'theme'; +const THEME_BG_STORAGE_KEY = 'themeBackground'; + +export const IThemeMainService = createDecorator('themeMainService'); + +export interface IThemeMainService { + _serviceBrand: any; + + getBackgroundColor(): string; +} + +export class ThemeMainService implements IThemeMainService { + + _serviceBrand: any; + + constructor(@IStateService private stateService: IStateService) { + ipc.on('vscode:changeColorTheme', (e: Event, windowId: number, broadcast: string) => { + // Theme changes + if (typeof broadcast === 'string') { + this.storeBackgroundColor(JSON.parse(broadcast)); + } + }); + } + + private storeBackgroundColor(data: { baseTheme: string, background: string }): void { + this.stateService.setItem(THEME_STORAGE_KEY, data.baseTheme); + this.stateService.setItem(THEME_BG_STORAGE_KEY, data.background); + } + + public getBackgroundColor(): string { + if (isWindows && systemPreferences.isInvertedColorScheme()) { + return DEFAULT_BG_HC_BLACK; + } + + let background = this.stateService.getItem(THEME_BG_STORAGE_KEY, null); + if (!background) { + let baseTheme: string; + if (isWindows && systemPreferences.isInvertedColorScheme()) { + baseTheme = 'hc-black'; + } else { + baseTheme = this.stateService.getItem(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0]; + } + + background = (baseTheme === 'hc-black') ? DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? DEFAULT_BG_LIGHT : DEFAULT_BG_DARK); + } + + if (isMacintosh && background.toUpperCase() === DEFAULT_BG_DARK) { + background = '#171717'; // https://github.com/electron/electron/issues/5150 + } + + return background; + } +} \ No newline at end of file diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index c4a6f6d947f3..0431b5368316 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -239,10 +239,10 @@ export class Win32UpdateService extends AbstractUpdateService { }); const readyMutexName = `${product.win32MutexName}-ready`; - const isActive = (require.__$__nodeRequire('windows-mutex') as any).isActive; + const mutex = await import('windows-mutex'); // poll for mutex-ready - pollUntil(() => isActive(readyMutexName)) + pollUntil(() => mutex.isActive(readyMutexName)) .then(() => this.setState(State.Ready(update))); } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index d9843090f27e..94bd4e509cb9 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; -import { IProcessEnvironment, isMacintosh, isLinux } from 'vs/base/common/platform'; +import { IProcessEnvironment, isMacintosh, isLinux, isWeb } from 'vs/base/common/platform'; import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; @@ -282,6 +282,10 @@ export interface IWindowSettings { } export function getTitleBarStyle(configurationService: IConfigurationService, environment: IEnvironmentService, isExtensionDevelopment = environment.isExtensionDevelopment): 'native' | 'custom' { + if (isWeb) { + return 'custom'; + } + const configuration = configurationService.getValue('window'); const isDev = !environment.isBuilt || isExtensionDevelopment; diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 45d6fd87a2f2..88b695be11b8 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -92,7 +92,6 @@ export interface IWindowsMainService { readonly onWindowClose: Event; // methods - ready(initialUserEnv: IProcessEnvironment): void; reload(win: ICodeWindow, cli?: ParsedArgs): void; enterWorkspace(win: ICodeWindow, path: URI): Promise; closeWorkspace(win: ICodeWindow): void; diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index b0140617f577..f73f47ccc377 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -403,19 +403,20 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable buttons = [ok, copy]; } - this.windowsMainService.showMessageBox({ + const result = await this.windowsMainService.showMessageBox({ title: product.nameLong, type: 'info', message: product.nameLong, detail: `\n${detail}`, buttons, noLink: true, - defaultId: buttons.indexOf(ok) - }, this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow()).then(result => { - if (buttons[result.button] === copy) { - clipboard.writeText(detail); - } - }); + defaultId: buttons.indexOf(ok), + cancelId: buttons.indexOf(ok) + }, this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow()); + + if (buttons[result.button] === copy) { + clipboard.writeText(detail); + } } async handleURL(uri: URI): Promise { diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index f654292fcae1..0908b463356d 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -228,7 +228,7 @@ export function toWorkspaceFolder(resource: URI): WorkspaceFolder { export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], workspaceConfigFile: URI): WorkspaceFolder[] { let result: WorkspaceFolder[] = []; - let seen: { [uri: string]: boolean } = Object.create(null); + let seen: Set = new Set(); const relativeTo = resources.dirname(workspaceConfigFile); for (let configuredFolder of configuredFolders) { @@ -252,8 +252,8 @@ export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], if (uri) { // remove duplicates let comparisonKey = resources.getComparisonKey(uri); - if (!seen[comparisonKey]) { - seen[comparisonKey] = true; + if (!seen.has(comparisonKey)) { + seen.add(comparisonKey); const name = configuredFolder.name || resources.basenameOrAuthority(uri); result.push(new WorkspaceFolder({ uri, name, index: result.length }, configuredFolder)); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 6783daf65e77..9d1a2892cab5 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1101,7 +1101,7 @@ declare module 'vscode' { /** * The column in which this editor shows. Will be `undefined` in case this - * isn't one of the main editors, e.g an embedded editor, or when the editor + * isn't one of the main editors, e.g. an embedded editor, or when the editor * column is larger than three. */ viewColumn?: ViewColumn; @@ -1900,7 +1900,7 @@ declare module 'vscode' { * * *Note* that a document selector that is just a language identifier selects *all* * documents, even those that are not saved on disk. Only use such selectors when - * a feature works without further context, e.g without the need to resolve related + * a feature works without further context, e.g. without the need to resolve related * 'files'. * * @sample `let sel:DocumentSelector = { scheme: 'file', language: 'typescript' }`; @@ -2595,7 +2595,7 @@ declare module 'vscode' { name: string; /** - * More detail for this symbol, e.g the signature of a function. + * More detail for this symbol, e.g. the signature of a function. */ detail: string; @@ -2605,12 +2605,12 @@ declare module 'vscode' { kind: SymbolKind; /** - * The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g comments and code. + * The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code. */ range: Range; /** - * The range that should be selected and reveal when this symbol is being picked, e.g the name of a function. + * The range that should be selected and reveal when this symbol is being picked, e.g. the name of a function. * Must be contained by the [`range`](#DocumentSymbol.range). */ selectionRange: Range; @@ -3515,6 +3515,10 @@ declare module 'vscode' { * * The editor will only resolve a completion item once. * + * *Note* that accepting a completion item will not wait for it to be resolved. Because of that [`insertText`](#CompletionItem.insertText), + * [`additionalTextEdits`](#CompletionItem.additionalTextEdits), and [`command`](#CompletionItem.command) should not + * be changed when resolving an item. + * * @param item A completion item currently active in the UI. * @param token A cancellation token. * @return The resolved completion item or a thenable that resolves to of such. It is OK to return the given @@ -3645,7 +3649,7 @@ declare module 'vscode' { * * For some languages one color can have multiple presentations, e.g. css can represent the color red with * the constant `Red`, the hex-value `#ff0000`, or in rgba and hsla forms. In csharp other representations - * apply, e.g `System.Drawing.Color.Red`. + * apply, e.g. `System.Drawing.Color.Red`. */ export class ColorPresentation { @@ -4231,7 +4235,7 @@ declare module 'vscode' { /** * Represents a related message and source code location for a diagnostic. This should be - * used to point to code locations that cause or related to a diagnostics, e.g when duplicating + * used to point to code locations that cause or related to a diagnostics, e.g. when duplicating * a symbol in a scope. */ export class DiagnosticRelatedInformation { @@ -6676,7 +6680,7 @@ declare module 'vscode' { * the following rules: * * - The uri-scheme must be `vscode.env.uriScheme`; - * - The uri-authority must be the extension id (eg. `my.extension`); + * - The uri-authority must be the extension id (e.g. `my.extension`); * - The uri-path, -query and -fragment parts are arbitrary. * * For example, if the `my.extension` extension registers a uri handler, it will only @@ -8417,9 +8421,9 @@ declare module 'vscode' { /** * Creates a new [source control](#SourceControl) instance. * - * @param id An `id` for the source control. Something short, eg: `git`. - * @param label A human-readable string for the source control. Eg: `Git`. - * @param rootUri An optional Uri of the root of the source control. Eg: `Uri.parse(workspaceRoot)`. + * @param id An `id` for the source control. Something short, e.g.: `git`. + * @param label A human-readable string for the source control. E.g.: `Git`. + * @param rootUri An optional Uri of the root of the source control. E.g.: `Uri.parse(workspaceRoot)`. * @return An instance of [source control](#SourceControl). */ export function createSourceControl(id: string, label: string, rootUri?: Uri): SourceControl; @@ -8983,7 +8987,7 @@ declare module 'vscode' { /** * The uri of the document the thread has been created on. */ - readonly resource: Uri; + readonly uri: Uri; /** * The range the comment thread is located within the document. The thread icon will be shown @@ -9097,7 +9101,7 @@ declare module 'vscode' { } /** - * Command argument for actions registered in `comments/commentThread/actions`. + * Command argument for actions registered in `comments/commentThread/context`. */ export interface CommentReply { /** @@ -9147,7 +9151,7 @@ declare module 'vscode' { * Create a [comment thread](#CommentThread). The comment thread will be displayed in visible text editors (if the resource matches) * and Comments Panel once created. * - * @param resource The uri of the document the thread has been created on. + * @param uri The uri of the document the thread has been created on. * @param range The range the comment thread is located within the document. * @param comments The ordered comments of the thread. */ diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 7ef9a092db92..ed028a55a2d4 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -137,32 +137,20 @@ declare module 'vscode' { // #region Joh - code insets - /** - */ - export class CodeInset { - range: Range; - height?: number; - constructor(range: Range, height?: number); - } - - export interface CodeInsetProvider { - onDidChangeCodeInsets?: Event; - provideCodeInsets(document: TextDocument, token: CancellationToken): ProviderResult; - resolveCodeInset(codeInset: CodeInset, webview: Webview, token: CancellationToken): ProviderResult; + export interface WebviewEditorInset { + readonly editor: TextEditor; + readonly range: Range; + readonly webview: Webview; + readonly onDidDispose: Event; + dispose(): void; } - export namespace languages { - - /** - * Register a code inset provider. - * - */ - export function registerCodeInsetProvider(selector: DocumentSelector, provider: CodeInsetProvider): Disposable; + export namespace window { + export function createWebviewTextEditorInset(editor: TextEditor, range: Range, options?: WebviewOptions): WebviewEditorInset; } //#endregion - //#region Joh - read/write in chunks export interface FileSystemProvider { @@ -1063,7 +1051,7 @@ declare module 'vscode' { * @param range The range the comment thread is located within the document. * @param comments The ordered comments of the thread. */ - createCommentThread(id: string, uri: Uri, range: Range, comments: Comment[]): CommentThread; + createCommentThread(id: string, resource: Uri, range: Range, comments: Comment[]): CommentThread; /** * Optional new comment thread factory. @@ -1099,6 +1087,17 @@ declare module 'vscode' { //#region Terminal + export interface TerminalOptions { + /** + * When enabled the terminal will run the process as normal but not be surfaced to the user + * until `Terminal.show` is called. The typical usage for this is when you need to run + * something that may need interactivity but only want to tell the user about it when + * interaction is needed. Note that the terminals will still be exposed to all extensions + * as normal. + */ + runInBackground?: boolean; + } + /** * An [event](#Event) which fires when a [Terminal](#Terminal)'s dimensions change. */ @@ -1426,4 +1425,18 @@ declare module 'vscode' { //#endregion + //#region DocumentLink tooltip mjbvz + + interface DocumentLink { + /** + * The tooltip text when you hover over this link. + * + * If a tooltip is provided, is will be displayed in a string that includes instructions on how to + * trigger the link, such as `cmd + click to {0}`. The specific instructions vary depending on OS, + * user settings, and localization. + */ + tooltip?: string; + } + + // #endregion } diff --git a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts similarity index 51% rename from src/vs/workbench/api/electron-browser/extensionHost.contribution.ts rename to src/vs/workbench/api/browser/extensionHost.contribution.ts index 2cb4fd0a1de5..6f9b4734f6f7 100644 --- a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -14,47 +14,47 @@ import { ColorExtensionPoint } from 'vs/workbench/services/themes/common/colorEx import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint'; // --- mainThread participants -import '../browser/mainThreadClipboard'; -import '../browser/mainThreadCommands'; -import '../browser/mainThreadConfiguration'; -import '../browser/mainThreadConsole'; -// {{SQL CARBON EDIT}} -// import '../browser/mainThreadDebugService'; -import '../browser/mainThreadDecorations'; -import '../browser/mainThreadDiagnostics'; -import '../browser/mainThreadDialogs'; -import '../browser/mainThreadDocumentContentProviders'; -import '../browser/mainThreadDocuments'; -import '../browser/mainThreadDocumentsAndEditors'; -import '../browser/mainThreadEditor'; -import '../browser/mainThreadEditors'; -import '../browser/mainThreadErrors'; -import '../browser/mainThreadExtensionService'; -import '../browser/mainThreadFileSystem'; -import '../browser/mainThreadFileSystemEventService'; -import '../browser/mainThreadHeapService'; -import '../browser/mainThreadKeytar'; -import '../browser/mainThreadLanguageFeatures'; -import '../browser/mainThreadLanguages'; -import '../browser/mainThreadLogService'; -import '../browser/mainThreadMessageService'; -import '../browser/mainThreadOutputService'; -import '../browser/mainThreadProgress'; -import '../browser/mainThreadQuickOpen'; -import '../browser/mainThreadSaveParticipant'; -import '../browser/mainThreadSCM'; -import '../browser/mainThreadSearch'; -import '../browser/mainThreadStatusBar'; -import '../browser/mainThreadStorage'; -import '../browser/mainThreadTelemetry'; -import '../browser/mainThreadTerminalService'; -import '../browser/mainThreadTreeViews'; -import '../browser/mainThreadUrls'; -import '../browser/mainThreadWindow'; -import '../browser/mainThreadWorkspace'; -import '../browser/mainThreadComments'; -import '../browser/mainThreadTask'; +import './mainThreadCodeInsets'; +import './mainThreadClipboard'; +import './mainThreadCommands'; +import './mainThreadConfiguration'; +import './mainThreadConsole'; +// import './mainThreadDebugService'; {{SQL CARBON EDIT}} @anthonydresser comment out debug service +import './mainThreadDecorations'; +import './mainThreadDiagnostics'; +import './mainThreadDialogs'; +import './mainThreadDocumentContentProviders'; +import './mainThreadDocuments'; +import './mainThreadDocumentsAndEditors'; +import './mainThreadEditor'; +import './mainThreadEditors'; +import './mainThreadErrors'; +import './mainThreadExtensionService'; +import './mainThreadFileSystem'; +import './mainThreadFileSystemEventService'; +import './mainThreadHeapService'; +import './mainThreadKeytar'; +import './mainThreadLanguageFeatures'; +import './mainThreadLanguages'; +import './mainThreadLogService'; +import './mainThreadMessageService'; +import './mainThreadOutputService'; +import './mainThreadProgress'; +import './mainThreadQuickOpen'; +import './mainThreadSaveParticipant'; +import './mainThreadSCM'; +import './mainThreadSearch'; +import './mainThreadStatusBar'; +import './mainThreadStorage'; +import './mainThreadTelemetry'; +import './mainThreadTerminalService'; +import './mainThreadTreeViews'; +import './mainThreadUrls'; +import './mainThreadWindow'; import './mainThreadWebview'; +import './mainThreadWorkspace'; +import './mainThreadComments'; +import './mainThreadTask'; import 'vs/workbench/api/common/apiCommands'; export class ExtensionPoints implements IWorkbenchContribution { diff --git a/src/vs/workbench/api/browser/mainThreadClipboard.ts b/src/vs/workbench/api/browser/mainThreadClipboard.ts index 2cd50d121462..94ae9eff3956 100644 --- a/src/vs/workbench/api/browser/mainThreadClipboard.ts +++ b/src/vs/workbench/api/browser/mainThreadClipboard.ts @@ -8,7 +8,7 @@ import { MainContext, MainThreadClipboardShape } from '../common/extHost.protoco import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @extHostNamedCustomer(MainContext.MainThreadClipboard) -export class MainThreadCommands implements MainThreadClipboardShape { +export class MainThreadClipboard implements MainThreadClipboardShape { constructor( _context: any, diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts new file mode 100644 index 000000000000..f26bd86290b4 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { UriComponents, URI } from 'vs/base/common/uri'; +import * as modes from 'vs/editor/common/modes'; +import { MainContext, MainThreadEditorInsetsShape, IExtHostContext, ExtHostEditorInsetsShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; +import { extHostNamedCustomer } from '../common/extHostCustomers'; +import { IRange } from 'vs/editor/common/core/range'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { IWebviewService, Webview } from 'vs/workbench/contrib/webview/common/webview'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IActiveCodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser'; + +// todo@joh move these things back into something like contrib/insets +class EditorWebviewZone implements IViewZone { + + readonly domNode: HTMLElement; + readonly afterLineNumber: number; + readonly afterColumn: number; + readonly heightInLines: number; + + private _id: number; + // suppressMouseDown?: boolean | undefined; + // heightInPx?: number | undefined; + // minWidthInPx?: number | undefined; + // marginDomNode?: HTMLElement | null | undefined; + // onDomNodeTop?: ((top: number) => void) | undefined; + // onComputedHeight?: ((height: number) => void) | undefined; + + constructor( + readonly editor: IActiveCodeEditor, + readonly range: IRange, + readonly webview: Webview, + ) { + this.domNode = document.createElement('div'); + this.afterLineNumber = range.startLineNumber; + this.afterColumn = range.startColumn; + this.heightInLines = range.endLineNumber - range.startLineNumber; + + editor.changeViewZones(accessor => this._id = accessor.addZone(this)); + webview.mountTo(this.domNode); + } + + dispose(): void { + this.editor.changeViewZones(accessor => accessor.removeZone(this._id)); + } +} + +@extHostNamedCustomer(MainContext.MainThreadEditorInsets) +export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { + + private readonly _proxy: ExtHostEditorInsetsShape; + private readonly _disposables = new DisposableStore(); + private readonly _insets = new Map(); + + constructor( + context: IExtHostContext, + @ICodeEditorService private readonly _editorService: ICodeEditorService, + @IWebviewService private readonly _webviewService: IWebviewService, + ) { + this._proxy = context.getProxy(ExtHostContext.ExtHostEditorInsets); + } + + dispose(): void { + this._disposables.dispose(); + } + + async $createEditorInset(handle: number, id: string, uri: UriComponents, range: IRange, options: modes.IWebviewOptions): Promise { + + let editor: IActiveCodeEditor | undefined; + id = id.substr(0, id.indexOf(',')); //todo@joh HACK + + for (const candidate of this._editorService.listCodeEditors()) { + if (candidate.getId() === id && candidate.hasModel() && candidate.getModel()!.uri.toString() === URI.revive(uri).toString()) { + editor = candidate; + break; + } + } + + if (!editor) { + setTimeout(() => this._proxy.$onDidDispose(handle)); + return; + } + + const disposables = new DisposableStore(); + + const webview = this._webviewService.createWebview({ + enableFindWidget: false, + allowSvgs: false, + extension: undefined + }, { + allowScripts: options.enableScripts + }); + + const webviewZone = new EditorWebviewZone(editor, range, webview); + + const remove = () => { + disposables.dispose(); + this._proxy.$onDidDispose(handle); + this._insets.delete(handle); + }; + + disposables.add(editor.onDidChangeModel(remove)); + disposables.add(editor.onDidDispose(remove)); + disposables.add(webviewZone); + disposables.add(webview); + disposables.add(webview.onMessage(msg => this._proxy.$onDidReceiveMessage(handle, msg))); + + this._insets.set(handle, webviewZone); + } + + $disposeEditorInset(handle: number): void { + const inset = this._insets.get(handle); + if (inset) { + this._insets.delete(handle); + inset.dispose(); + } + } + + $setHtml(handle: number, value: string): void { + const inset = this._insets.get(handle); + if (inset) { + inset.webview.html = value; + } + } + + $setOptions(handle: number, options: modes.IWebviewOptions): void { + const inset = this._insets.get(handle); + if (inset) { + inset.webview.options = options; + } + } + + $postMessage(handle: number, value: any): Promise { + const inset = this._insets.get(handle); + if (inset) { + inset.webview.sendMessage(value); + return Promise.resolve(true); + } + return Promise.resolve(false); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadCommands.ts b/src/vs/workbench/api/browser/mainThreadCommands.ts index acf8aa9d87f4..625e8c30d0d5 100644 --- a/src/vs/workbench/api/browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCommands.ts @@ -12,7 +12,7 @@ import { revive } from 'vs/base/common/marshalling'; @extHostNamedCustomer(MainContext.MainThreadCommands) export class MainThreadCommands implements MainThreadCommandsShape { - private readonly _disposables = new Map(); + private readonly _commandRegistrations = new Map(); private readonly _generateCommandsDocumentationRegistration: IDisposable; private readonly _proxy: ExtHostCommandsShape; @@ -26,8 +26,8 @@ export class MainThreadCommands implements MainThreadCommandsShape { } dispose() { - this._disposables.forEach(value => value.dispose()); - this._disposables.clear(); + this._commandRegistrations.forEach(value => value.dispose()); + this._commandRegistrations.clear(); this._generateCommandsDocumentationRegistration.dispose(); } @@ -53,7 +53,7 @@ export class MainThreadCommands implements MainThreadCommandsShape { } $registerCommand(id: string): void { - this._disposables.set( + this._commandRegistrations.set( id, CommandsRegistry.registerCommand(id, (accessor, ...args) => { return this._proxy.$executeContributedCommand(id, ...args).then(result => { @@ -64,10 +64,10 @@ export class MainThreadCommands implements MainThreadCommandsShape { } $unregisterCommand(id: string): void { - const command = this._disposables.get(id); + const command = this._commandRegistrations.get(id); if (command) { command.dispose(); - this._disposables.delete(id); + this._commandRegistrations.delete(id); } } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 76b910bcd1b9..31e547804710 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { ICodeEditor, isCodeEditor, isDiffEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import * as modes from 'vs/editor/common/modes'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -299,7 +299,8 @@ export class MainThreadCommentController { this._features = features; } - createCommentThread(commentThreadHandle: number, + createCommentThread(extensionId: string, + commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, @@ -307,7 +308,7 @@ export class MainThreadCommentController { let thread = new MainThreadCommentThread( commentThreadHandle, this.handle, - '', + extensionId, threadId, URI.revive(resource).toString(), range @@ -451,6 +452,10 @@ export class MainThreadCommentController { this._proxy.$createCommentThreadTemplate(this.handle, resource, range); } + async updateCommentThreadTemplate(threadHandle: number, range: IRange) { + await this._proxy.$updateCommentThreadTemplate(this.handle, threadHandle, range); + } + toJSON(): any { return { $mid: 6, @@ -461,16 +466,19 @@ export class MainThreadCommentController { @extHostNamedCustomer(MainContext.MainThreadComments) export class MainThreadComments extends Disposable implements MainThreadCommentsShape { - private _disposables: IDisposable[]; - private _activeCommentThreadDisposables: IDisposable[]; private readonly _proxy: ExtHostCommentsShape; private _documentProviders = new Map(); private _workspaceProviders = new Map(); private _handlers = new Map(); private _commentControllers = new Map(); + private _activeCommentThread?: MainThreadCommentThread; + private readonly _activeCommentThreadDisposables = this._register(new DisposableStore()); + private _input?: modes.CommentInput; + private _openPanelListener: IDisposable | null; + constructor( extHostContext: IExtHostContext, @IEditorService private readonly _editorService: IEditorService, @@ -480,9 +488,27 @@ export class MainThreadComments extends Disposable implements MainThreadComments @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); - this._disposables = []; - this._activeCommentThreadDisposables = []; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments); + + this._register(this._commentService.onDidChangeActiveCommentThread(async thread => { + let handle = (thread as MainThreadCommentThread).controllerHandle; + let controller = this._commentControllers.get(handle); + + if (!controller) { + return; + } + + this._activeCommentThreadDisposables.clear(); + this._activeCommentThread = thread as MainThreadCommentThread; + controller.activeCommentThread = this._activeCommentThread; + + this._activeCommentThreadDisposables.add(this._activeCommentThread.onDidChangeInput(input => { // todo, dispose + this._input = input; + this._proxy.$onCommentWidgetInputChange(handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread!.range, this._input ? this._input.value : undefined); + })); + + await this._proxy.$onCommentWidgetInputChange(controller.handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread.range, this._input ? this._input.value : undefined); + })); } $registerCommentController(handle: number, id: string, label: string): void { @@ -525,7 +551,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments commentThreadHandle: number, threadId: string, resource: UriComponents, - range: IRange + range: IRange, + extensionId: ExtensionIdentifier ): modes.CommentThread2 | undefined { let provider = this._commentControllers.get(handle); @@ -533,7 +560,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments return undefined; } - return provider.createCommentThread(commentThreadHandle, threadId, resource, range); + return provider.createCommentThread(extensionId.value, commentThreadHandle, threadId, resource, range); } $updateCommentThread(handle: number, @@ -753,8 +780,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments } dispose(): void { - this._disposables = dispose(this._disposables); - this._activeCommentThreadDisposables = dispose(this._activeCommentThreadDisposables); + super.dispose(); this._workspaceProviders.forEach(value => dispose(value)); this._workspaceProviders.clear(); this._documentProviders.forEach(value => dispose(value)); diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index db5ee1e41e93..923b3a3775f2 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -176,11 +176,11 @@ class MainThreadDocumentAndEditorStateComputer { } private _onDidAddEditor(e: ICodeEditor): void { - this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable([ + this._toDisposeOnEditorRemove.set(e.getId(), combinedDisposable( e.onDidChangeModel(() => this._updateState()), e.onDidFocusEditorText(() => this._updateState()), e.onDidFocusEditorWidget(() => this._updateState(e)) - ])); + )); this._updateState(); } diff --git a/src/vs/workbench/api/browser/mainThreadKeytar.ts b/src/vs/workbench/api/browser/mainThreadKeytar.ts index 6da34102ecf9..0cd602a1db78 100644 --- a/src/vs/workbench/api/browser/mainThreadKeytar.ts +++ b/src/vs/workbench/api/browser/mainThreadKeytar.ts @@ -5,27 +5,19 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadKeytarShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; - -interface IKeytarModule { - getPassword(service: string, account: string): Promise; - setPassword(service: string, account: string, password: string): Promise; - deletePassword(service: string, account: string): Promise; - findPassword(service: string): Promise; -} +import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { optional } from 'vs/platform/instantiation/common/instantiation'; @extHostNamedCustomer(MainContext.MainThreadKeytar) export class MainThreadKeytar implements MainThreadKeytarShape { - private _keytar: IKeytarModule | null; + private readonly _credentialsService?: ICredentialsService; constructor( - extHostContext: IExtHostContext + _extHostContext: IExtHostContext, + @optional(ICredentialsService) credentialsService: ICredentialsService, ) { - try { - this._keytar = require.__$__nodeRequire('keytar'); - } catch (e) { - this._keytar = null; - } + this._credentialsService = credentialsService; } dispose(): void { @@ -33,28 +25,28 @@ export class MainThreadKeytar implements MainThreadKeytarShape { } async $getPassword(service: string, account: string): Promise { - if (this._keytar) { - return this._keytar.getPassword(service, account); + if (this._credentialsService) { + return this._credentialsService.getPassword(service, account); } return null; } async $setPassword(service: string, account: string, password: string): Promise { - if (this._keytar) { - return this._keytar.setPassword(service, account, password); + if (this._credentialsService) { + return this._credentialsService.setPassword(service, account, password); } } async $deletePassword(service: string, account: string): Promise { - if (this._keytar) { - return this._keytar.deletePassword(service, account); + if (this._credentialsService) { + return this._credentialsService.deletePassword(service, account); } return false; } async $findPassword(service: string): Promise { - if (this._keytar) { - return this._keytar.findPassword(service); + if (this._credentialsService) { + return this._credentialsService.findPassword(service); } return null; } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 069778aed8b1..8170af79956c 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -11,34 +11,29 @@ import * as search from 'vs/workbench/contrib/search/common/search'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, CodeInsetDto, LinkDto, CallHierarchyDto, SuggestDataDto } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CallHierarchyDto, SuggestDataDto, CodeActionDto } from '../common/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IModeService } from 'vs/editor/common/services/modeService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; -import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; -import { IHeapService } from 'vs/workbench/services/heap/common/heap'; import { mixin } from 'vs/base/common/objects'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { private readonly _proxy: ExtHostLanguageFeaturesShape; - private readonly _heapService: IHeapService; private readonly _modeService: IModeService; private readonly _registrations: { [handle: number]: IDisposable; } = Object.create(null); constructor( extHostContext: IExtHostContext, - @IHeapService heapService: IHeapService, @IModeService modeService: IModeService, ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures); - this._heapService = heapService; this._modeService = modeService; } @@ -101,7 +96,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } - private static _reviveCodeActionDto(data: CodeActionDto[] | undefined): modes.CodeAction[] { + private static _reviveCodeActionDto(data: ReadonlyArray): modes.CodeAction[] { if (data) { data.forEach(code => reviveWorkspaceEditDto(code.edit)); } @@ -140,25 +135,19 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void { const provider = { - provideCodeLenses: (model: ITextModel, token: CancellationToken): modes.ICodeLensSymbol[] | Promise => { - return this._proxy.$provideCodeLenses(handle, model.uri, token).then(dto => { - if (dto) { - dto.forEach(obj => { - this._heapService.trackObject(obj); - this._heapService.trackObject(obj.command); - }); + provideCodeLenses: (model: ITextModel, token: CancellationToken): Promise => { + return this._proxy.$provideCodeLenses(handle, model.uri, token).then(listDto => { + if (!listDto) { + return undefined; } - return dto; + return { + lenses: listDto.lenses, + dispose: () => listDto.cacheId && this._proxy.$releaseCodeLenses(handle, listDto.cacheId) + }; }); }, - resolveCodeLens: (_model: ITextModel, codeLens: modes.ICodeLensSymbol, token: CancellationToken): Promise => { - return this._proxy.$resolveCodeLens(handle, codeLens, token).then(obj => { - if (obj) { - this._heapService.trackObject(obj); - this._heapService.trackObject(obj.command); - } - return obj; - }); + resolveCodeLens: (_model: ITextModel, codeLens: modes.CodeLens, token: CancellationToken): Promise => { + return this._proxy.$resolveCodeLens(handle, codeLens, token); } }; @@ -178,35 +167,6 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } - // -- code inset - - $registerCodeInsetSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number): void { - - const provider = { - provideCodeInsets: (model: ITextModel, token: CancellationToken): CodeInsetDto[] | Thenable => { - return this._proxy.$provideCodeInsets(handle, model.uri, token).then(dto => { - if (dto) { dto.forEach(obj => this._heapService.trackObject(obj)); } - return dto; - }); - }, - resolveCodeInset: (model: ITextModel, codeInset: CodeInsetDto, token: CancellationToken): CodeInsetDto | Thenable => { - return this._proxy.$resolveCodeInset(handle, model.uri, codeInset, token).then(obj => { - this._heapService.trackObject(obj); - return obj; - }); - } - }; - - if (typeof eventHandle === 'number') { - const emitter = new Emitter(); - this._registrations[eventHandle] = emitter; - provider.onDidChange = emitter.event; - } - - const langSelector = selector; - this._registrations[handle] = codeInset.CodeInsetProviderRegistry.register(langSelector, provider); - } - // --- declaration $registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void { @@ -275,13 +235,19 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], providedCodeActionKinds?: string[]): void { this._registrations[handle] = modes.CodeActionProviderRegistry.register(selector, { - provideCodeActions: (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise => { - return this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token).then(dto => { - if (dto) { - dto.forEach(obj => { this._heapService.trackObject(obj.command); }); + provideCodeActions: async (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise => { + const listDto = await this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token); + if (!listDto) { + return undefined; + } + return { + actions: MainThreadLanguageFeatures._reviveCodeActionDto(listDto.actions), + dispose: () => { + if (typeof listDto.cacheId === 'number') { + this._proxy.$releaseCodeActions(handle, listDto.cacheId); + } } - return MainThreadLanguageFeatures._reviveCodeActionDto(dto); - }); + }; }, providedCodeActionKinds }); diff --git a/src/vs/workbench/api/browser/mainThreadMessageService.ts b/src/vs/workbench/api/browser/mainThreadMessageService.ts index 9a7873560c42..414d1b21d6d7 100644 --- a/src/vs/workbench/api/browser/mainThreadMessageService.ts +++ b/src/vs/workbench/api/browser/mainThreadMessageService.ts @@ -90,7 +90,8 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { // if promise has not been resolved yet, now is the time to ensure a return value // otherwise if already resolved it means the user clicked one of the buttons Event.once(messageHandle.onDidClose)(() => { - dispose(...primaryActions, ...secondaryActions); + dispose(primaryActions); + dispose(secondaryActions); resolve(undefined); }); }); diff --git a/src/vs/workbench/api/browser/mainThreadProgress.ts b/src/vs/workbench/api/browser/mainThreadProgress.ts index 227ce760de77..f84e42ed8b60 100644 --- a/src/vs/workbench/api/browser/mainThreadProgress.ts +++ b/src/vs/workbench/api/browser/mainThreadProgress.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IProgress, IProgressService2, IProgressStep, ProgressLocation, IProgressOptions, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; +import { IProgress, IProgressService, IProgressStep, ProgressLocation, IProgressOptions, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; import { MainThreadProgressShape, MainContext, IExtHostContext, ExtHostProgressShape, ExtHostContext } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { Action } from 'vs/base/common/actions'; @@ -22,13 +22,13 @@ class ManageExtensionAction extends Action { @extHostNamedCustomer(MainContext.MainThreadProgress) export class MainThreadProgress implements MainThreadProgressShape { - private readonly _progressService: IProgressService2; + private readonly _progressService: IProgressService; private _progress = new Map void, progress: IProgress }>(); private readonly _proxy: ExtHostProgressShape; constructor( extHostContext: IExtHostContext, - @IProgressService2 progressService: IProgressService2, + @IProgressService progressService: IProgressService, @ICommandService private readonly _commandService: ICommandService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostProgress); diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 599e294b9f23..17522b6ad655 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -28,7 +28,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; // {{SQL CARBON EDIT}} @@ -299,10 +299,10 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { if (CodeActionKind.SourceFixAll.contains(b)) { return 0; } - return 1; + return -1; } if (CodeActionKind.SourceFixAll.contains(b)) { - return -1; + return 1; } return 0; }); @@ -334,6 +334,8 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { await this.applyCodeActions(actionsToRun.actions); } catch { // Failure to apply a code action should not block other on save actions + } finally { + actionsToRun.dispose(); } } } @@ -391,7 +393,7 @@ export class SaveParticipant implements ISaveParticipant { constructor( extHostContext: IExtHostContext, @IInstantiationService instantiationService: IInstantiationService, - @IProgressService2 private readonly _progressService: IProgressService2, + @IProgressService private readonly _progressService: IProgressService, @ILogService private readonly _logService: ILogService ) { this._saveParticipants = new IdleValue(() => [ diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index 167fb9e5dd4b..317b6777a05f 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -3,11 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; import { MainThreadStatusBarShape, MainContext, IExtHostContext } from '../common/extHost.protocol'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { dispose } from 'vs/base/common/lifecycle'; @extHostNamedCustomer(MainContext.MainThreadStatusBar) @@ -25,8 +24,8 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { this.entries.clear(); } - $setEntry(id: number, extensionId: ExtensionIdentifier, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void { - const entry = { text, tooltip, command, color, extensionId }; + $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void { + const entry: IStatusbarEntry = { text, tooltip, command, color }; // Reset existing entry if alignment or priority changed let existingEntry = this.entries.get(id); @@ -38,7 +37,7 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { // Create new entry if not existing if (!existingEntry) { - this.entries.set(id, { accessor: this.statusbarService.addEntry(entry, alignment, priority), alignment, priority }); + this.entries.set(id, { accessor: this.statusbarService.addEntry(entry, statusId, statusName, alignment, priority), alignment, priority }); } // Otherwise update diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index 67cbc1549c34..53460d3e2d54 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -62,7 +62,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape // when the extension host process goes down ? } - public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string }, waitOnExit?: boolean, strictEnv?: boolean): Promise<{ id: number, name: string }> { + public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string }, waitOnExit?: boolean, strictEnv?: boolean, runInBackground?: boolean): Promise<{ id: number, name: string }> { const shellLaunchConfig: IShellLaunchConfig = { name, executable: shellPath, @@ -71,7 +71,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape waitOnExit, ignoreConfigurationCwd: true, env, - strictEnv + strictEnv, + runInBackground }; const terminal = this.terminalService.createTerminal(shellLaunchConfig); return Promise.resolve({ diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts similarity index 75% rename from src/vs/workbench/api/electron-browser/mainThreadWebview.ts rename to src/vs/workbench/api/browser/mainThreadWebview.ts index 833aed5ecc98..52081d889942 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -2,30 +2,27 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as map from 'vs/base/common/map'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import product from 'vs/platform/product/node/product'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewInsetHandle, WebviewPanelHandle, WebviewPanelShowOptions } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions } from 'vs/workbench/api/common/extHost.protocol'; import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; -import { CodeInsetController } from 'vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; -import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { extHostNamedCustomer } from '../common/extHostCustomers'; +import { IProductService } from 'vs/platform/product/common/product'; @extHostNamedCustomer(MainContext.MainThreadWebviews) export class MainThreadWebviews extends Disposable implements MainThreadWebviewsShape { @@ -34,9 +31,8 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews 'http', 'https', 'mailto', - product.urlProtocol, 'vscode', - 'vscode-insiders' + 'vscode-insider', ]); private static revivalPool = 0; @@ -44,7 +40,6 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews private readonly _proxy: ExtHostWebviewsShape; private readonly _webviews = new Map(); - private readonly _webviewsElements = new Map(); private readonly _revivers = new Map(); private _activeWebview: WebviewPanelHandle | undefined = undefined; @@ -58,18 +53,17 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews @IWebviewEditorService private readonly _webviewService: IWebviewEditorService, @IOpenerService private readonly _openerService: IOpenerService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + @IProductService private readonly _productService: IProductService, ) { super(); this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews); - _editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._toDispose); - _editorService.onDidVisibleEditorsChange(this.onVisibleEditorsChanged, this, this._toDispose); + this._register(_editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this)); + this._register(_editorService.onDidVisibleEditorsChange(this.onVisibleEditorsChanged, this)); // This reviver's only job is to activate webview extensions // This should trigger the real reviver to be registered from the extension host side. - this._toDispose.push(_webviewService.registerReviver({ + this._register(_webviewService.registerReviver({ canRevive: (webview) => { const viewType = webview.state.viewType; if (viewType) { @@ -80,9 +74,9 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews reviveWebview: () => { throw new Error('not implemented'); } })); - lifecycleService.onBeforeShutdown(e => { + this._register(lifecycleService.onBeforeShutdown(e => { e.veto(this._onBeforeShutdown()); - }, this, this._toDispose); + }, this)); } public $createWebviewPanel( @@ -119,52 +113,6 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews this._telemetryService.publicLog('webviews:createWebviewPanel', { extensionId: extensionId.value }); } - $createWebviewCodeInset( - handle: WebviewInsetHandle, - symbolId: string, - options: modes.IWebviewOptions, - extensionId: ExtensionIdentifier, - extensionLocation: UriComponents - ): void { - // todo@joh main is for the lack of a code-inset service - // which we maybe wanna have... this is how it now works - // 1) create webview element - // 2) find the code inset controller that request it - // 3) let the controller adopt the widget - // 4) continue to forward messages to the webview - const webview = this._instantiationService.createInstance( - WebviewElement, - { - extension: { - location: URI.revive(extensionLocation), - id: extensionId - }, - enableFindWidget: false, - }, - { - allowScripts: options.enableScripts, - } - ); - - let found = false; - for (const editor of this._codeEditorService.listCodeEditors()) { - const ctrl = CodeInsetController.get(editor); - if (ctrl && ctrl.acceptWebview(symbolId, webview)) { - found = true; - break; - } - } - - if (!found) { - webview.dispose(); - return; - } - // this will leak... the adopted webview will be disposed by the - // code inset controller. we might need a dispose-event here so that - // we can clean up things. - this._webviewsElements.set(handle, webview); - } - public $disposeWebview(handle: WebviewPanelHandle): void { const webview = this.getWebview(handle); webview.dispose(); @@ -180,22 +128,14 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews webview.iconPath = reviveWebviewIcon(value); } - public $setHtml(handle: WebviewPanelHandle | WebviewInsetHandle, value: string): void { - if (typeof handle === 'number') { - this.getWebviewElement(handle).html = value; - } else { - const webview = this.getWebview(handle); - webview.html = value; - } + public $setHtml(handle: WebviewPanelHandle, value: string): void { + const webview = this.getWebview(handle); + webview.html = value; } - public $setOptions(handle: WebviewPanelHandle | WebviewInsetHandle, options: modes.IWebviewOptions): void { - if (typeof handle === 'number') { - this.getWebviewElement(handle).options = reviveWebviewOptions(options as any /*todo@mat */); - } else { - const webview = this.getWebview(handle); - webview.setOptions(reviveWebviewOptions(options as any /*todo@mat */)); - } + public $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void { + const webview = this.getWebview(handle); + webview.setOptions(reviveWebviewOptions(options as any /*todo@mat */)); } public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void { @@ -210,29 +150,24 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } } - public async $postMessage(handle: WebviewPanelHandle | WebviewInsetHandle, message: any): Promise { - if (typeof handle === 'number') { - this.getWebviewElement(handle).sendMessage(message); - return true; - } else { - const webview = this.getWebview(handle); - const editors = this._editorService.visibleControls - .filter(e => e instanceof WebviewEditor) - .map(e => e as WebviewEditor) - .filter(e => e.input!.matches(webview)); - - if (editors.length > 0) { - editors[0].sendMessage(message); - return true; - } + public async $postMessage(handle: WebviewPanelHandle, message: any): Promise { + const webview = this.getWebview(handle); + const editors = this._editorService.visibleControls + .filter(e => e instanceof WebviewEditor) + .map(e => e as WebviewEditor) + .filter(e => e.input!.matches(webview)); - if (webview.webview) { - webview.webview.sendMessage(message); - return true; - } + if (editors.length > 0) { + editors[0].sendMessage(message); + return true; + } - return false; + if (webview.webview) { + webview.webview.sendMessage(message); + return true; } + + return false; } public $registerSerializer(viewType: string): void { @@ -384,6 +319,9 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews if (MainThreadWebviews.standardSupportedLinkSchemes.has(link.scheme)) { return true; } + if (this._productService.urlProtocol === link.scheme) { + return true; + } return !!webview.options.enableCommandUris && link.scheme === 'command'; } @@ -395,14 +333,6 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return webview; } - private getWebviewElement(handle: number): WebviewElement { - const webview = this._webviewsElements.get(handle); - if (!webview) { - throw new Error('Unknown webview handle:' + handle); - } - return webview; - } - private static getDeserializationFailedContents(viewType: string) { return ` diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 4f4b7e64d014..e31cb5f668f8 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -13,7 +13,6 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -24,6 +23,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { isEqualOrParent } from 'vs/base/common/resources'; +import { INotificationService } from 'vs/platform/notification/common/notification'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -39,7 +39,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @ITextFileService private readonly _textFileService: ITextFileService, @IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService, - @IStatusbarService private readonly _statusbarService: IStatusbarService, + @INotificationService private readonly _notificationService: INotificationService, @IWindowService private readonly _windowService: IWindowService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILabelService private readonly _labelService: ILabelService, @@ -66,7 +66,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const workspaceFoldersToAdd = foldersToAdd.map(f => ({ uri: URI.revive(f.uri), name: f.name })); // Indicate in status message - this._statusbarService.setStatusMessage(this.getStatusMessage(extensionName, workspaceFoldersToAdd.length, deleteCount), 10 * 1000 /* 10s */); + this._notificationService.status(this.getStatusMessage(extensionName, workspaceFoldersToAdd.length, deleteCount), { hideAfter: 10 * 1000 /* 10s */ }); return this._workspaceEditingService.updateFolders(index, deleteCount, workspaceFoldersToAdd, true); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 91547b69827f..8ae0ad29db46 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -42,7 +42,6 @@ import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ResolvedAuthority, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { IRelativePattern } from 'vs/base/common/glob'; import { IRemoteConsoleLog } from 'vs/base/common/console'; @@ -140,7 +139,7 @@ export interface MainThreadCommentsShape extends IDisposable { $registerCommentController(handle: number, id: string, label: string): void; $unregisterCommentController(handle: number): void; $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; - $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange): modes.CommentThread2 | undefined; + $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, extensionId: ExtensionIdentifier): modes.CommentThread2 | undefined; $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string, contextValue: string | undefined, comments: modes.Comment[], acceptInputCommand: modes.Command | undefined, additionalCommands: modes.Command[], deleteCommand: modes.Command | undefined, collapseState: modes.CommentThreadCollapsibleState): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $setInputValue(handle: number, input: string): void; @@ -336,7 +335,6 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $unregister(handle: number): void; $registerDocumentSymbolProvider(handle: number, selector: ISerializedDocumentFilter[], label: string): void; $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void; - $registerCodeInsetSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void; $emitCodeLensEvent(eventHandle: number, event?: any): void; $registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void; $registerDeclarationSupport(handle: number, selector: ISerializedDocumentFilter[]): void; @@ -393,7 +391,7 @@ export interface MainThreadProgressShape extends IDisposable { } export interface MainThreadTerminalServiceShape extends IDisposable { - $createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string | null }, waitOnExit?: boolean, strictEnv?: boolean): Promise<{ id: number, name: string }>; + $createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string | null }, waitOnExit?: boolean, strictEnv?: boolean, runInBackground?: boolean): Promise<{ id: number, name: string }>; $createTerminalRenderer(name: string): Promise; $dispose(terminalId: number): void; $hide(terminalId: number): void; @@ -500,7 +498,7 @@ export interface MainThreadQuickOpenShape extends IDisposable { } export interface MainThreadStatusBarShape extends IDisposable { - $setEntry(id: number, extensionId: ExtensionIdentifier | undefined, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void; + $setEntry(id: number, statusId: string, statusName: string, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: statusbar.StatusbarAlignment, priority: number | undefined): void; $dispose(id: number): void; } @@ -513,9 +511,21 @@ export interface MainThreadTelemetryShape extends IDisposable { $publicLog(eventName: string, data?: any): void; } -export type WebviewPanelHandle = string; +export interface MainThreadEditorInsetsShape extends IDisposable { + $createEditorInset(handle: number, editorId: string, document: UriComponents, range: IRange, options: modes.IWebviewOptions): Promise; + $disposeEditorInset(handle: number): void; + + $setHtml(handle: number, value: string): void; + $setOptions(handle: number, options: modes.IWebviewOptions): void; + $postMessage(handle: number, value: any): Promise; +} -export type WebviewInsetHandle = number; +export interface ExtHostEditorInsetsShape { + $onDidDispose(handle: number): void; + $onDidReceiveMessage(handle: number, message: any): void; +} + +export type WebviewPanelHandle = string; export interface WebviewPanelShowOptions { readonly viewColumn?: EditorViewColumn; @@ -524,15 +534,14 @@ export interface WebviewPanelShowOptions { export interface MainThreadWebviewsShape extends IDisposable { $createWebviewPanel(handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void; - $createWebviewCodeInset(handle: WebviewInsetHandle, symbolId: string, options: modes.IWebviewOptions, extensionId: ExtensionIdentifier | undefined, extensionLocation: UriComponents | undefined): void; $disposeWebview(handle: WebviewPanelHandle): void; $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void; $setTitle(handle: WebviewPanelHandle, value: string): void; $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void; - $setHtml(handle: WebviewPanelHandle | WebviewInsetHandle, value: string): void; - $setOptions(handle: WebviewPanelHandle | WebviewInsetHandle, options: modes.IWebviewOptions): void; - $postMessage(handle: WebviewPanelHandle | WebviewInsetHandle, value: any): Promise; + $setHtml(handle: WebviewPanelHandle, value: string): void; + $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void; + $postMessage(handle: WebviewPanelHandle, value: any): Promise; $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; @@ -988,6 +997,11 @@ export interface CodeActionDto { isPreferred?: boolean; } +export interface CodeActionListDto { + cacheId: number; + actions: ReadonlyArray; +} + export type CacheId = number; export type ChainedCacheId = [CacheId, CacheId]; @@ -1000,16 +1014,20 @@ export interface LinkDto { cacheId?: ChainedCacheId; range: IRange; url?: string | UriComponents; + tooltip?: string; +} + +export interface CodeLensListDto { + cacheId?: number; + lenses: CodeLensDto[]; } -export interface CodeLensDto extends ObjectIdentifier { +export interface CodeLensDto { + cacheId?: ChainedCacheId; range: IRange; - id?: string; command?: CommandDto; } -export type CodeInsetDto = ObjectIdentifier & codeInset.ICodeInsetSymbol; - export interface CallHierarchyDto { _id: number; kind: modes.SymbolKind; @@ -1022,10 +1040,9 @@ export interface CallHierarchyDto { export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; $resolveCodeLens(handle: number, symbol: CodeLensDto, token: CancellationToken): Promise; - $provideCodeInsets(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $resolveCodeInset(handle: number, resource: UriComponents, symbol: CodeInsetDto, token: CancellationToken): Promise; + $releaseCodeLenses(handle: number, id: number): void; $provideDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDeclaration(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; @@ -1033,7 +1050,8 @@ export interface ExtHostLanguageFeaturesShape { $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; - $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; + $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; + $releaseCodeActions(handle: number, cacheId: number): void; $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions, token: CancellationToken): Promise; $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions, token: CancellationToken): Promise; $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise; @@ -1212,6 +1230,7 @@ export interface ExtHostCommentsShape { $provideDocumentComments(handle: number, document: UriComponents): Promise; $createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise; $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange): void; + $updateCommentThreadTemplate(commentControllerHandle: number, threadHandle: number, range: IRange): Promise; $onCommentWidgetInputChange(commentControllerHandle: number, document: UriComponents, range: IRange, input: string | undefined): Promise; $deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number): void; $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise; @@ -1249,6 +1268,7 @@ export const MainContext = { MainThreadDocuments: createMainId('MainThreadDocuments'), MainThreadDocumentContentProviders: createMainId('MainThreadDocumentContentProviders'), MainThreadTextEditors: createMainId('MainThreadTextEditors'), + MainThreadEditorInsets: createMainId('MainThreadEditorInsets'), MainThreadErrors: createMainId('MainThreadErrors'), MainThreadTreeViews: createMainId('MainThreadTreeViews'), MainThreadKeytar: createMainId('MainThreadKeytar'), @@ -1299,6 +1319,7 @@ export const ExtHostContext = { ExtHostWorkspace: createExtId('ExtHostWorkspace'), ExtHostWindow: createExtId('ExtHostWindow'), ExtHostWebviews: createExtId('ExtHostWebviews'), + ExtHostEditorInsets: createExtId('ExtHostEditorInsets'), ExtHostProgress: createMainId('ExtHostProgress'), ExtHostComments: createMainId('ExtHostComments'), ExtHostStorage: createMainId('ExtHostStorage'), diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 2c85b26e8646..14826c7f5d16 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -504,7 +504,7 @@ export class ExtHostApiCommands { private _executeCodeLensProvider(resource: URI, itemResolveCount: number): Promise { const args = { resource, itemResolveCount }; - return this._commands.executeCommand('_executeCodeLensProvider', args) + return this._commands.executeCommand('_executeCodeLensProvider', args) .then(tryMapWith(item => { return new types.CodeLens( typeConverters.Range.to(item.range), diff --git a/src/vs/workbench/api/common/extHostCodeInsets.ts b/src/vs/workbench/api/common/extHostCodeInsets.ts new file mode 100644 index 000000000000..7fdfdbe34884 --- /dev/null +++ b/src/vs/workbench/api/common/extHostCodeInsets.ts @@ -0,0 +1,130 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; +import * as vscode from 'vscode'; +import { MainThreadEditorInsetsShape } from './extHost.protocol'; +import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; + +export class ExtHostEditorInsets implements ExtHostEditorInsets { + + private _handlePool = 0; + private _disposables = new DisposableStore(); + private _insets = new Map }>(); + + constructor( + private readonly _proxy: MainThreadEditorInsetsShape, + private readonly _editors: ExtHostEditors + ) { + + // dispose editor inset whenever the hosting editor goes away + this._disposables.add(_editors.onDidChangeVisibleTextEditors(() => { + const visibleEditor = _editors.getVisibleTextEditors(); + this._insets.forEach(value => { + if (visibleEditor.indexOf(value.editor) < 0) { + value.inset.dispose(); // will remove from `this._insets` + } + }); + })); + } + + dispose(): void { + this._insets.forEach(value => value.inset.dispose()); + this._disposables.dispose(); + } + + createWebviewEditorInset(editor: vscode.TextEditor, range: vscode.Range, options?: vscode.WebviewOptions): vscode.WebviewEditorInset { + + let apiEditor: ExtHostTextEditor | undefined; + for (const candidate of this._editors.getVisibleTextEditors()) { + if (candidate === editor) { + apiEditor = candidate; + break; + } + } + if (!apiEditor) { + throw new Error('not a visible editor'); + } + + const that = this; + const handle = this._handlePool++; + const onDidReceiveMessage = new Emitter(); + const onDidDispose = new Emitter(); + + const webview = new class implements vscode.Webview { + + private _html: string = ''; + private _options: vscode.WebviewOptions; + + set options(value: vscode.WebviewOptions) { + this._options = value; + that._proxy.$setOptions(handle, value); + } + + get options(): vscode.WebviewOptions { + return this._options; + } + + set html(value: string) { + this._html = value; + that._proxy.$setHtml(handle, value); + } + + get html(): string { + return this._html; + } + + get onDidReceiveMessage(): vscode.Event { + return onDidReceiveMessage.event; + } + + postMessage(message: any): Thenable { + return that._proxy.$postMessage(handle, message); + } + }; + + const inset = new class implements vscode.WebviewEditorInset { + + readonly editor: vscode.TextEditor = editor; + readonly range: vscode.Range = range; + readonly webview: vscode.Webview = webview; + readonly onDidDispose: vscode.Event = onDidDispose.event; + + dispose(): void { + if (that._insets.has(handle)) { + that._insets.delete(handle); + that._proxy.$disposeEditorInset(handle); + onDidDispose.fire(); + + // final cleanup + onDidDispose.dispose(); + onDidReceiveMessage.dispose(); + } + } + }; + + this._proxy.$createEditorInset(handle, apiEditor.id, apiEditor.document.uri, typeConverters.Range.from(range), options || {}); + this._insets.set(handle, { editor, inset, onDidReceiveMessage }); + + return inset; + } + + $onDidDispose(handle: number): void { + const value = this._insets.get(handle); + if (value) { + value.inset.dispose(); + } + } + + $onDidReceiveMessage(handle: number, message: any): void { + const value = this._insets.get(handle); + if (value) { + value.onDidReceiveMessage.fire(message); + } + } +} diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index a5c2f0f73819..a7fbe73c74fb 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -18,6 +18,7 @@ import { revive } from 'vs/base/common/marshalling'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { URI } from 'vs/base/common/uri'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; interface CommandHandler { callback: Function; @@ -211,6 +212,35 @@ export class CommandsConverter { this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } + toInternal2(command: vscode.Command | undefined, disposables: DisposableStore): CommandDto | undefined { + + if (!command) { + return undefined; + } + + const result: CommandDto = { + $ident: undefined, + id: command.command, + title: command.title, + tooltip: command.tooltip + }; + + if (command.command && isNonEmptyArray(command.arguments)) { + // we have a contributed command with arguments. that + // means we don't want to send the arguments around + + const id = this._heap.keep(command); + disposables.add(toDisposable(() => this._heap.delete(id))); + result.$ident = id; + + result.id = this._delegatingCommandId; + result.arguments = [id]; + + } + + return result; + } + toInternal(command: vscode.Command): CommandDto; toInternal(command: undefined): undefined; toInternal(command: vscode.Command | undefined): CommandDto | undefined; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 67e6e3c4fdab..fcb82cc10c5c 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -162,6 +162,16 @@ export class ExtHostComments implements ExtHostCommentsShape { commentController.$createCommentThreadTemplate(uriComponents, range); } + async $updateCommentThreadTemplate(commentControllerHandle: number, threadHandle: number, range: IRange) { + const commentController = this._commentControllers.get(commentControllerHandle); + + if (!commentController) { + return; + } + + commentController.$updateCommentThreadTemplate(threadHandle, range); + } + $onCommentWidgetInputChange(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, input: string): Promise { const commentController = this._commentControllers.get(commentControllerHandle); @@ -582,7 +592,8 @@ export class ExtHostCommentThread implements vscode.CommentThread { private _id: string | undefined, private _uri: vscode.Uri, private _range: vscode.Range, - private _comments: vscode.Comment[] + private _comments: vscode.Comment[], + extensionId: ExtensionIdentifier ) { if (this._id === undefined) { this._id = `${_commentController.id}.${this.handle}`; @@ -593,7 +604,8 @@ export class ExtHostCommentThread implements vscode.CommentThread { this.handle, this._id, this._uri, - extHostTypeConverter.Range.from(this._range) + extHostTypeConverter.Range.from(this._range), + extensionId ); this._localDisposables = []; @@ -741,7 +753,7 @@ class ExtHostCommentController implements vscode.CommentController { } constructor( - _extension: IExtensionDescription, + private _extension: IExtensionDescription, private _handle: number, private readonly _commandsConverter: CommandsConverter, private _proxy: MainThreadCommentsShape, @@ -755,23 +767,30 @@ class ExtHostCommentController implements vscode.CommentController { createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): vscode.CommentThread; createCommentThread(arg0: vscode.Uri | string, arg1: vscode.Uri | vscode.Range, arg2: vscode.Range | vscode.Comment[], arg3?: vscode.Comment[]): vscode.CommentThread { if (typeof arg0 === 'string') { - const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, arg0, arg1 as vscode.Uri, arg2 as vscode.Range, arg3 as vscode.Comment[]); + const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, arg0, arg1 as vscode.Uri, arg2 as vscode.Range, arg3 as vscode.Comment[], this._extension.identifier); this._threads.set(commentThread.handle, commentThread); return commentThread; } else { - const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, undefined, arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[]); + const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, undefined, arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[], this._extension.identifier); this._threads.set(commentThread.handle, commentThread); return commentThread; } } $createCommentThreadTemplate(uriComponents: UriComponents, range: IRange) { - const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), []); + const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension.identifier); commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; this._threads.set(commentThread.handle, commentThread); return commentThread; } + $updateCommentThreadTemplate(threadHandle: number, range: IRange) { + let thread = this._threads.get(threadHandle); + if (thread) { + thread.range = extHostTypeConverter.Range.to(range); + } + } + $deleteCommentThread(threadHandle: number) { let thread = this._threads.get(threadHandle); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index a504b4624593..41d6359bcca1 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -15,7 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { asPromise } from 'vs/base/common/async'; -import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, MainThreadWebviewsShape, CodeInsetDto, SuggestDataDto, LinksListDto, ChainedCacheId } from './extHost.protocol'; +import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, SuggestDataDto, LinksListDto, ChainedCacheId, CodeLensListDto, CodeActionListDto } from './extHost.protocol'; import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range as EditorRange } from 'vs/editor/common/core/range'; @@ -25,11 +25,10 @@ import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ExtHostWebview } from 'vs/workbench/api/common/extHostWebview'; -import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; -import { generateUuid } from 'vs/base/common/uuid'; import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { LRUCache } from 'vs/base/common/map'; +import { IURITransformer } from 'vs/base/common/uriIpc'; +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; // --- adapter @@ -104,34 +103,48 @@ class CodeLensAdapter { private static _badCmd: vscode.Command = { command: 'missing', title: '!!MISSING: command!!' }; + private readonly _cache = new Cache(); + private readonly _disposables = new Map(); + constructor( private readonly _documents: ExtHostDocuments, private readonly _commands: CommandsConverter, - private readonly _heapService: ExtHostHeapService, private readonly _provider: vscode.CodeLensProvider ) { } - provideCodeLenses(resource: URI, token: CancellationToken): Promise { + provideCodeLenses(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideCodeLenses(doc, token)).then(lenses => { - const result: CodeLensDto[] = []; - if (isNonEmptyArray(lenses)) { - for (const lens of lenses) { - const id = this._heapService.keep(lens); - result.push(ObjectIdentifier.mixin({ - range: typeConvert.Range.from(lens.range), - command: this._commands.toInternal(lens.command) - }, id)); - } + + if (!lenses || token.isCancellationRequested) { + return undefined; } + + const cacheId = this._cache.add(lenses); + const disposables = new DisposableStore(); + this._disposables.set(cacheId, disposables); + + const result: CodeLensListDto = { + cacheId, + lenses: [], + }; + + for (let i = 0; i < lenses.length; i++) { + result.lenses.push({ + cacheId: [cacheId, i], + range: typeConvert.Range.from(lenses[i].range), + command: this._commands.toInternal2(lenses[i].command, disposables) + }); + } + return result; }); } resolveCodeLens(symbol: CodeLensDto, token: CancellationToken): Promise { - const lens = this._heapService.get(ObjectIdentifier.of(symbol)); + const lens = symbol.cacheId && this._cache.get(...symbol.cacheId); if (!lens) { return Promise.resolve(undefined); } @@ -144,52 +157,27 @@ class CodeLensAdapter { } return resolve.then(newLens => { - newLens = newLens || lens; - symbol.command = this._commands.toInternal(newLens.command || CodeLensAdapter._badCmd); - return symbol; - }); - } -} - -class CodeInsetAdapter { - - constructor( - private readonly _documents: ExtHostDocuments, - private readonly _heapService: ExtHostHeapService, - private readonly _provider: vscode.CodeInsetProvider - ) { } - - provideCodeInsets(resource: URI, token: CancellationToken): Promise { - const doc = this._documents.getDocument(resource); - return asPromise(() => this._provider.provideCodeInsets(doc, token)).then(insets => { - if (Array.isArray(insets)) { - return insets.map(inset => { - const $ident = this._heapService.keep(inset); - const id = generateUuid(); - return { - $ident, - id, - range: typeConvert.Range.from(inset.range), - height: inset.height - }; - }); + if (token.isCancellationRequested) { + return undefined; } - return undefined; - }); - } - resolveCodeInset(symbol: CodeInsetDto, webview: vscode.Webview, token: CancellationToken): Promise { - - const inset = this._heapService.get(ObjectIdentifier.of(symbol)); - if (!inset) { - return Promise.resolve(symbol); - } + const disposables = symbol.cacheId && this._disposables.get(symbol.cacheId[0]); + if (!disposables) { + // We've already been disposed of + return undefined; + } - return asPromise(() => this._provider.resolveCodeInset(inset, webview, token)).then(newInset => { - newInset = newInset || inset; + newLens = newLens || lens; + symbol.command = this._commands.toInternal2(newLens.command || CodeLensAdapter._badCmd, disposables); return symbol; }); } + + releaseCodeLenses(cachedId: number): void { + dispose(this._disposables.get(cachedId)); + this._disposables.delete(cachedId); + this._cache.delete(cachedId); + } } function convertToLocationLinks(value: vscode.Definition): modes.LocationLink[] { @@ -328,6 +316,9 @@ export interface CustomCodeAction extends CodeActionDto { class CodeActionAdapter { private static readonly _maxCodeActionsPerFile: number = 1000; + private readonly _cache = new Cache(); + private readonly _disposables = new Map(); + constructor( private readonly _documents: ExtHostDocuments, private readonly _commands: CommandsConverter, @@ -337,7 +328,7 @@ class CodeActionAdapter { private readonly _extensionId: ExtensionIdentifier ) { } - provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { + provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); const ran = Selection.isISelection(rangeOrSelection) @@ -360,34 +351,39 @@ class CodeActionAdapter { }; return asPromise(() => this._provider.provideCodeActions(doc, ran, codeActionContext, token)).then(commandsOrActions => { - if (!isNonEmptyArray(commandsOrActions)) { + if (!isNonEmptyArray(commandsOrActions) || token.isCancellationRequested) { return undefined; } - const result: CustomCodeAction[] = []; + + const cacheId = this._cache.add(commandsOrActions); + const disposables = new DisposableStore(); + this._disposables.set(cacheId, disposables); + + const actions: CustomCodeAction[] = []; for (const candidate of commandsOrActions) { if (!candidate) { continue; } if (CodeActionAdapter._isCommand(candidate)) { // old school: synthetic code action - result.push({ + actions.push({ _isSynthetic: true, title: candidate.title, - command: this._commands.toInternal(candidate), + command: this._commands.toInternal2(candidate, disposables), }); } else { if (codeActionContext.only) { if (!candidate.kind) { this._logService.warn(`${this._extensionId.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`); } else if (!codeActionContext.only.contains(candidate.kind)) { - this._logService.warn(`${this._extensionId.value} -Code actions of kind '${codeActionContext.only.value} 'requested but returned code action is of kind '${candidate.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`); + this._logService.warn(`${this._extensionId.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action is of kind '${candidate.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`); } } // new school: convert code action - result.push({ + actions.push({ title: candidate.title, - command: candidate.command && this._commands.toInternal(candidate.command), + command: candidate.command && this._commands.toInternal2(candidate.command, disposables), diagnostics: candidate.diagnostics && candidate.diagnostics.map(typeConvert.Diagnostic.from), edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit), kind: candidate.kind && candidate.kind.value, @@ -396,10 +392,16 @@ class CodeActionAdapter { } } - return result; + return { cacheId, actions }; }); } + public releaseCodeActions(cachedId: number): void { + dispose(this._disposables.get(cachedId)); + this._disposables.delete(cachedId); + this._cache.delete(cachedId); + } + private static _isCommand(thing: any): thing is vscode.Command { return typeof (thing).command === 'string' && typeof (thing).title === 'string'; } @@ -624,6 +626,7 @@ class SuggestAdapter { private _provider: vscode.CompletionItemProvider; private _cache = new Cache(); + private _disposables = new Map(); constructor(documents: ExtHostDocuments, commands: CommandsConverter, provider: vscode.CompletionItemProvider) { this._documents = documents; @@ -643,13 +646,18 @@ class SuggestAdapter { return undefined; } - let list = Array.isArray(value) ? new CompletionList(value) : value; - let pid: number | undefined; + if (token.isCancellationRequested) { + // cancelled -> return without further ado, esp no caching + // of results as they will leak + return undefined; + } + + const list = Array.isArray(value) ? new CompletionList(value) : value; // keep result for providers that support resolving - if (SuggestAdapter.supportsResolving(this._provider)) { - pid = this._cache.add(list.items); - } + const pid: number = SuggestAdapter.supportsResolving(this._provider) ? this._cache.add(list.items) : this._cache.add([]); + const disposables = new DisposableStore(); + this._disposables.set(pid, disposables); // the default text edit range const wordRangeBeforePos = (doc.getWordRangeAtPosition(pos) as Range || new Range(pos, pos)) @@ -663,7 +671,7 @@ class SuggestAdapter { }; for (let i = 0; i < list.items.length; i++) { - const suggestion = this._convertCompletionItem(list.items[i], pos, pid && [pid, i] || undefined); + const suggestion = this._convertCompletionItem(list.items[i], pos, [pid, i]); // check for bad completion item // for the converter did warn if (suggestion) { @@ -698,15 +706,22 @@ class SuggestAdapter { } releaseCompletionItems(id: number): any { + dispose(this._disposables.get(id)); + this._disposables.delete(id); this._cache.delete(id); } - private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: ChainedCacheId | undefined): SuggestDataDto | undefined { + private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: ChainedCacheId): SuggestDataDto | undefined { if (typeof item.label !== 'string' || item.label.length === 0) { console.warn('INVALID text edit -> must have at least a label'); return undefined; } + const disposables = this._disposables.get(id[0]); + if (!disposables) { + throw Error('DisposableStore is missing...'); + } + const result: SuggestDataDto = { // x: id, @@ -721,7 +736,7 @@ class SuggestAdapter { i: item.keepWhitespace ? modes.CompletionItemInsertTextRule.KeepWhitespace : 0, k: item.commitCharacters, l: item.additionalTextEdits && item.additionalTextEdits.map(typeConvert.TextEdit.from), - m: this._commands.toInternal(item.command), + m: this._commands.toInternal2(item.command, disposables), }; // 'insertText'-logic @@ -831,6 +846,12 @@ class LinkProviderAdapter { return undefined; } + if (token.isCancellationRequested) { + // cancelled -> return without further ado, esp no caching + // of results as they will leak + return undefined; + } + if (typeof this._provider.resolveDocumentLink !== 'function') { // no resolve -> no caching return { links: links.map(typeConvert.DocumentLink.from) }; @@ -1027,7 +1048,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter - | ColorProviderAdapter | FoldingProviderAdapter | CodeInsetAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter; + | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter | SelectionRangeAdapter | CallHierarchyAdapter; class AdapterData { constructor( @@ -1036,15 +1057,11 @@ class AdapterData { ) { } } -export interface ISchemeTransformer { - transformOutgoing(scheme: string): string; -} - export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { private static _handlePool: number = 0; - private readonly _schemeTransformer: ISchemeTransformer | null; + private readonly _uriTransformer: IURITransformer | null; private _proxy: MainThreadLanguageFeaturesShape; private _documents: ExtHostDocuments; private _commands: ExtHostCommands; @@ -1052,25 +1069,23 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { private _diagnostics: ExtHostDiagnostics; private _adapter = new Map(); private readonly _logService: ILogService; - private _webviewProxy: MainThreadWebviewsShape; constructor( mainContext: IMainContext, - schemeTransformer: ISchemeTransformer | null, + uriTransformer: IURITransformer | null, documents: ExtHostDocuments, commands: ExtHostCommands, heapMonitor: ExtHostHeapService, diagnostics: ExtHostDiagnostics, logService: ILogService ) { - this._schemeTransformer = schemeTransformer; + this._uriTransformer = uriTransformer; this._proxy = mainContext.getProxy(MainContext.MainThreadLanguageFeatures); this._documents = documents; this._commands = commands; this._heapService = heapMonitor; this._diagnostics = diagnostics; this._logService = logService; - this._webviewProxy = mainContext.getProxy(MainContext.MainThreadWebviews); } private _transformDocumentSelector(selector: vscode.DocumentSelector): Array { @@ -1099,8 +1114,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { } private _transformScheme(scheme: string | undefined): string | undefined { - if (this._schemeTransformer && typeof scheme === 'string') { - return this._schemeTransformer.transformOutgoing(scheme); + if (this._uriTransformer && typeof scheme === 'string') { + return this._uriTransformer.transformOutgoingScheme(scheme); } return scheme; } @@ -1173,7 +1188,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { const handle = this._nextHandle(); const eventHandle = typeof provider.onDidChangeCodeLenses === 'function' ? this._nextHandle() : undefined; - this._adapter.set(handle, new AdapterData(new CodeLensAdapter(this._documents, this._commands.converter, this._heapService, provider), extension)); + this._adapter.set(handle, new AdapterData(new CodeLensAdapter(this._documents, this._commands.converter, provider), extension)); this._proxy.$registerCodeLensSupport(handle, this._transformDocumentSelector(selector), eventHandle); let result = this._createDisposable(handle); @@ -1185,43 +1200,16 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return result; } - $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), []); + $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined); } - $resolveCodeLens(handle: number, symbol: modes.ICodeLensSymbol, token: CancellationToken): Promise { + $resolveCodeLens(handle: number, symbol: CodeLensDto, token: CancellationToken): Promise { return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined); } - // --- code insets - - registerCodeInsetProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CodeInsetProvider): vscode.Disposable { - const handle = this._nextHandle(); - const eventHandle = typeof provider.onDidChangeCodeInsets === 'function' ? this._nextHandle() : undefined; - - this._adapter.set(handle, new AdapterData(new CodeInsetAdapter(this._documents, this._heapService, provider), extension)); - this._proxy.$registerCodeInsetSupport(handle, this._transformDocumentSelector(selector), eventHandle); - let result = this._createDisposable(handle); - - if (eventHandle !== undefined && provider.onDidChangeCodeInsets) { - const subscription = provider.onDidChangeCodeInsets(_ => this._proxy.$emitCodeLensEvent(eventHandle)); - result = Disposable.from(result, subscription); - } - - return result; - } - - $provideCodeInsets(handle: number, resource: UriComponents, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeInsetAdapter, adapter => adapter.provideCodeInsets(URI.revive(resource), token), undefined); - } - - $resolveCodeInset(handle: number, _resource: UriComponents, symbol: codeInset.ICodeInsetSymbol, token: CancellationToken): Promise { - const webviewHandle = Math.random(); - const webview = new ExtHostWebview(webviewHandle, this._webviewProxy, { enableScripts: true }); - return this._withAdapter(handle, CodeInsetAdapter, async (adapter, extension) => { - await this._webviewProxy.$createWebviewCodeInset(webviewHandle, symbol.id, { enableCommandUris: true, enableScripts: true }, extension ? extension.identifier : undefined, extension ? extension.extensionLocation : undefined); - return adapter.resolveCodeInset(symbol, webview, token); - }, symbol); + $releaseCodeLenses(handle: number, cacheId: number): void { + this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined); } // --- declaration @@ -1311,10 +1299,14 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { } - $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { + $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), rangeOrSelection, context, token), undefined); } + $releaseCodeActions(handle: number, cacheId: number): void { + this._withAdapter(handle, CodeActionAdapter, adapter => Promise.resolve(adapter.releaseCodeActions(cacheId)), undefined); + } + // --- formatting registerDocumentFormattingEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable { diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index 547b6c088631..08632864fd42 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -7,7 +7,7 @@ import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/ import { StatusBarAlignment as ExtHostStatusBarAlignment, Disposable, ThemeColor } from './extHostTypes'; import { StatusBarItem, StatusBarAlignment } from 'vscode'; import { MainContext, MainThreadStatusBarShape, IMainContext } from './extHost.protocol'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { localize } from 'vs/nls'; export class ExtHostStatusBarEntry implements StatusBarItem { private static ID_GEN = 0; @@ -18,6 +18,9 @@ export class ExtHostStatusBarEntry implements StatusBarItem { private _disposed: boolean; private _visible: boolean; + private _statusId: string; + private _statusName: string; + private _text: string; private _tooltip: string; private _color: string | ThemeColor; @@ -26,14 +29,13 @@ export class ExtHostStatusBarEntry implements StatusBarItem { private _timeoutHandle: any; private _proxy: MainThreadStatusBarShape; - private _extensionId?: ExtensionIdentifier; - - constructor(proxy: MainThreadStatusBarShape, extensionId: ExtensionIdentifier | undefined, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) { + constructor(proxy: MainThreadStatusBarShape, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) { this._id = ExtHostStatusBarEntry.ID_GEN++; this._proxy = proxy; + this._statusId = id; + this._statusName = name; this._alignment = alignment; this._priority = priority; - this._extensionId = extensionId; } public get id(): number { @@ -107,7 +109,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem { this._timeoutHandle = undefined; // Set to status bar - this._proxy.$setEntry(this.id, this._extensionId, this.text, this.tooltip, this.command, this.color, + this._proxy.$setEntry(this.id, this._statusId, this._statusName, this.text, this.tooltip, this.command, this.color, this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT, this._priority); }, 0); @@ -125,7 +127,7 @@ class StatusBarMessage { private _messages: { message: string }[] = []; constructor(statusBar: ExtHostStatusBar) { - this._item = statusBar.createStatusBarEntry(undefined, ExtHostStatusBarAlignment.Left, Number.MIN_VALUE); + this._item = statusBar.createStatusBarEntry('status.extensionMessage', localize('status.extensionMessage', "Extension Status"), ExtHostStatusBarAlignment.Left, Number.MIN_VALUE); } dispose() { @@ -167,8 +169,8 @@ export class ExtHostStatusBar { this._statusMessage = new StatusBarMessage(this); } - createStatusBarEntry(extensionId: ExtensionIdentifier | undefined, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem { - return new ExtHostStatusBarEntry(this._proxy, extensionId, alignment, priority); + createStatusBarEntry(id: string, name: string, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem { + return new ExtHostStatusBarEntry(this._proxy, id, name, alignment, priority); } setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): Disposable { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 6e52f764b5ad..692a774815ad 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -808,7 +808,8 @@ export namespace DocumentLink { export function from(link: vscode.DocumentLink): modes.ILink { return { range: Range.from(link.range), - url: link.target + url: link.target, + tooltip: link.tooltip }; } @@ -858,10 +859,7 @@ export namespace Color { export namespace SelectionRange { export function from(obj: vscode.SelectionRange): modes.SelectionRange { - return { - kind: '', - range: Range.from(obj.range) - }; + return { range: Range.from(obj.range) }; } export function to(obj: modes.SelectionRange): vscode.SelectionRange { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 1b9d8bab4e96..e41d7d50f996 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1440,6 +1440,8 @@ export class DocumentLink { target?: URI; + tooltip?: string; + constructor(range: Range, target: URI | undefined) { if (target && !(target instanceof URI)) { throw illegalArgument('target'); diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 03d782deac91..21d51dc505df 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import * as vscode from 'vscode'; -import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState, WebviewInsetHandle } from './extHost.protocol'; +import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState } from './extHost.protocol'; import { Disposable } from './extHostTypes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as modes from 'vs/editor/common/modes'; @@ -16,7 +16,7 @@ import * as modes from 'vs/editor/common/modes'; type IconPath = URI | { light: URI, dark: URI }; export class ExtHostWebview implements vscode.Webview { - private readonly _handle: WebviewPanelHandle | WebviewInsetHandle; + private readonly _handle: WebviewPanelHandle; private readonly _proxy: MainThreadWebviewsShape; private _html: string; private _options: vscode.WebviewOptions; @@ -26,7 +26,7 @@ export class ExtHostWebview implements vscode.Webview { public readonly onDidReceiveMessage: Event = this._onMessageEmitter.event; constructor( - handle: WebviewPanelHandle | WebviewInsetHandle, + handle: WebviewPanelHandle, proxy: MainThreadWebviewsShape, options: vscode.WebviewOptions ) { diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 9e6d686543b0..80942178ae70 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -21,7 +21,6 @@ namespace schema { export interface IUserFriendlyMenuItem { command: string; alt?: string; - precondition?: string; when?: string; group?: string; } @@ -84,10 +83,6 @@ namespace schema { collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt')); return false; } - if (item.precondition && typeof item.precondition !== 'string') { - collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'precondition')); - return false; - } if (item.when && typeof item.when !== 'string') { collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when')); return false; @@ -112,10 +107,6 @@ namespace schema { description: localize('vscode.extension.contributes.menuItem.alt', 'Identifier of an alternative command to execute. The command must be declared in the \'commands\'-section'), type: 'string' }, - precondition: { - description: localize('vscode.extension.contributes.menuItem.precondition', 'Condition which must be true to enable this item'), - type: 'string' - }, when: { description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'), type: 'string' @@ -206,8 +197,8 @@ namespace schema { type: 'array', items: menuItem }, - 'comments/commentThread/actions': { - description: localize('commentThread.actions', "The contributed comment thread actions"), + 'comments/commentThread/context': { + description: localize('commentThread.actions', "The contributed comment thread context menu, rendered as buttons below the comment editor"), type: 'array', items: menuItem }, @@ -216,8 +207,8 @@ namespace schema { type: 'array', items: menuItem }, - 'comments/comment/actions': { - description: localize('comment.actions', "The contributed comment actions"), + 'comments/comment/context': { + description: localize('comment.actions', "The contributed comment context menu, rendered as buttons below the comment editor"), type: 'array', items: menuItem }, @@ -229,6 +220,7 @@ namespace schema { export interface IUserFriendlyCommand { command: string; title: string | ILocalizedString; + enablement?: string; category?: string | ILocalizedString; icon?: IUserFriendlyIcon; } @@ -247,6 +239,10 @@ namespace schema { if (!isValidLocalizedString(command.title, collector, 'title')) { return false; } + if (command.enablement && typeof command.enablement !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'precondition')); + return false; + } if (command.category && !isValidLocalizedString(command.category, collector, 'category')) { return false; } @@ -300,6 +296,10 @@ namespace schema { description: localize('vscode.extension.contributes.commandType.category', '(Optional) Category string by the command is grouped in the UI'), type: 'string' }, + enablement: { + description: localize('vscode.extension.contributes.commandType.precondition', '(Optional) Condition which must be true to enable the command'), + type: 'string' + }, icon: { description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path or a themable configuration'), anyOf: [{ @@ -336,10 +336,12 @@ namespace schema { let _commandRegistrations: IDisposable[] = []; -ExtensionsRegistry.registerExtensionPoint({ +export const commandsExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'commands', jsonSchema: schema.commandsContribution -}).setHandler(extensions => { +}); + +commandsExtensionPoint.setHandler(extensions => { function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser, disposables: IDisposable[]) { @@ -347,7 +349,7 @@ ExtensionsRegistry.registerExtensionPointposition, priority); + const id = extension.identifier.value; + const name = nls.localize('extensionLabel', "{0} (Extension)", extension.displayName || extension.name); + + return extHostStatusBar.createStatusBarEntry(id, name, position, priority); }, setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): vscode.Disposable { return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable); @@ -488,9 +495,14 @@ export function createApiFactory( createWebviewPanel(viewType: string, title: string, showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean }, options: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel { return extHostWebviews.createWebviewPanel(extension, viewType, title, showOptions, options); }, + createWebviewTextEditorInset(editor: vscode.TextEditor, range: vscode.Range, options: vscode.WebviewOptions): vscode.WebviewEditorInset { + checkProposedApiEnabled(extension); + return extHostEditorInsets.createWebviewEditorInset(editor, range, options); + }, createTerminal(nameOrOptions?: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal { if (typeof nameOrOptions === 'object') { - return extHostTerminalService.createTerminalFromOptions(nameOrOptions); + nameOrOptions.runInBackground = nameOrOptions.runInBackground && extension.enableProposedApi; + return extHostTerminalService.createTerminalFromOptions(nameOrOptions); } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); }, diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 56e16d3aeb65..84d4cc993fdc 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -33,10 +33,10 @@ import { IWorkspace } from 'vs/platform/workspace/common/workspace'; import { Schemas } from 'vs/base/common/network'; import { withNullAsUndefined } from 'vs/base/common/types'; import { VSBuffer } from 'vs/base/common/buffer'; -import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures'; import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/workbench/api/common/extHostTypes'; +import { IURITransformer } from 'vs/base/common/uriIpc'; interface ITestRunner { run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; @@ -86,8 +86,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { extHostConfiguration: ExtHostConfiguration, environment: IEnvironment, extHostLogService: ExtHostLogService, - schemeTransformer: ISchemeTransformer | null, - outputChannelName: string + uriTransformer: IURITransformer | null ) { this._hostUtils = hostUtils; this._initData = initData; @@ -137,8 +136,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this, this._extHostLogService, this._storage, - schemeTransformer, - outputChannelName + uriTransformer ); this._resolvers = Object.create(null); diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index d176cac5e849..0114cf10d982 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -51,7 +51,7 @@ export const LogOutputChannelFactory = new class implements IOutputChannelFactor try { const outputDirPath = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); const outputDir = await dirExists(outputDirPath).then(exists => exists ? exists : mkdirp(outputDirPath).then(() => true)).then(() => outputDirPath); - const fileName = `${this._namePool++}-${name}`; + const fileName = `${this._namePool++}-${name.replace(/[\\/:\*\?"<>\|]/g, '')}`; const file = URI.file(join(outputDir, `${fileName}.log`)); const appender = new OutputAppender(fileName, file.fsPath); return new ExtHostOutputChannelBackedByFile(name, appender, proxy); diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 223d432056e4..238a2c71f348 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -16,10 +16,7 @@ import { OutputChannel } from 'vs/workbench/services/search/node/ripgrepSearchUt import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import * as vscode from 'vscode'; import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from '../common/extHost.protocol'; - -export interface ISchemeTransformer { - transformOutgoing(scheme: string): string; -} +import { IURITransformer } from 'vs/base/common/uriIpc'; export class ExtHostSearch implements ExtHostSearchShape { @@ -35,14 +32,14 @@ export class ExtHostSearch implements ExtHostSearchShape { private _fileSearchManager: FileSearchManager; - constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer | null, private _logService: ILogService, private _pfs = pfs) { + constructor(mainContext: IMainContext, private _uriTransformer: IURITransformer | null, private _logService: ILogService, private _pfs = pfs) { this._proxy = mainContext.getProxy(MainContext.MainThreadSearch); this._fileSearchManager = new FileSearchManager(); } private _transformScheme(scheme: string): string { - if (this._schemeTransformer) { - return this._schemeTransformer.transformOutgoing(scheme); + if (this._uriTransformer) { + return this._uriTransformer.transformOutgoingScheme(scheme); } return scheme; } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index a39e90bfea99..5894a499ba4e 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -714,7 +714,7 @@ export class ExtHostTask implements ExtHostTaskShape { paths[i] = resolver.resolve(ws, paths[i]); } } - result.process = win32.findExecutable( + result.process = await win32.findExecutable( resolver.resolve(ws, toResolve.process.name), toResolve.process.cwd !== undefined ? resolver.resolve(ws, toResolve.process.cwd) : undefined, paths diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index cc2f1465f7ef..2deaf0fe8fa5 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -115,9 +115,10 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi cwd?: string | URI, env?: { [key: string]: string | null }, waitOnExit?: boolean, - strictEnv?: boolean + strictEnv?: boolean, + runInBackground?: boolean ): void { - this._proxy.$createTerminal(this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv).then(terminal => { + this._proxy.$createTerminal(this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, runInBackground).then(terminal => { this._name = terminal.name; this._runQueuedRequests(terminal.id); }); @@ -176,7 +177,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi this._pidPromiseComplete(processId); this._pidPromiseComplete = null; } else { - // Recreate the promise if this is the nth processId set (eg. reused task terminals) + // Recreate the promise if this is the nth processId set (e.g. reused task terminals) this._pidPromise.then(pid => { if (pid !== processId) { this._pidPromise = Promise.resolve(processId); @@ -309,7 +310,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options.name); - terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env, /*options.waitOnExit*/ undefined, options.strictEnv); + terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env, /*options.waitOnExit*/ undefined, options.strictEnv, options.runInBackground); this._terminals.push(terminal); return terminal; } diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index 31bbb9bb53c6..c0171828b407 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -24,7 +24,7 @@ export class ActionBarContributor { /** * Returns an array of primary actions in the given context. */ - getActions(context: unknown): IAction[] { + getActions(context: unknown): ReadonlyArray { return []; } } @@ -66,7 +66,7 @@ export class ContributableActionProvider implements IActionProvider { return false; } - getActions(tree: ITree, element: unknown): IAction[] { + getActions(tree: ITree, element: unknown): ReadonlyArray { const actions: IAction[] = []; const context = this.toContext(tree, element); @@ -155,7 +155,7 @@ export interface IActionBarRegistry { class ActionBarRegistry implements IActionBarRegistry { private readonly actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0; }[] = []; - private readonly actionBarContributorInstances: { [scope: string]: ActionBarContributor[] } = Object.create(null); + private readonly actionBarContributorInstances: Map = new Map(); private instantiationService: IInstantiationService; start(accessor: ServicesAccessor): void { @@ -169,15 +169,16 @@ class ActionBarRegistry implements IActionBarRegistry { private createActionBarContributor(scope: string, ctor: IConstructorSignature0): void { const instance = this.instantiationService.createInstance(ctor); - let target = this.actionBarContributorInstances[scope]; + let target = this.actionBarContributorInstances.get(scope); if (!target) { - target = this.actionBarContributorInstances[scope] = []; + target = []; + this.actionBarContributorInstances.set(scope, target); } target.push(instance); } private getContributors(scope: string): ActionBarContributor[] { - return this.actionBarContributorInstances[scope] || []; + return this.actionBarContributorInstances.get(scope) || []; } registerActionBarContributor(scope: string, ctor: IConstructorSignature0): void { diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index a6d1f5cede71..ac464da84a4d 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -21,8 +21,9 @@ import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { IsMacContext } from 'vs/workbench/browser/contextkeys'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { InEditorZenModeContext } from 'vs/workbench/common/editor'; +import { InEditorZenModeContext, IsCenteredLayoutContext } from 'vs/workbench/common/editor'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; const registry = Registry.as(Extensions.WorkbenchActions); const viewCategory = nls.localize('view', "View"); @@ -61,7 +62,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', command: { id: ToggleActivityBarVisibilityAction.ID, - title: nls.localize({ key: 'miToggleActivityBar', comment: ['&& denotes a mnemonic'] }, "Toggle &&Activity Bar") + title: nls.localize({ key: 'miShowActivityBar', comment: ['&& denotes a mnemonic'] }, "Show &&Activity Bar"), + toggled: ContextKeyExpr.equals('config.workbench.activityBar.visible', true) }, order: 4 }); @@ -95,7 +97,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '1_toggle_view', command: { id: ToggleCenteredLayout.ID, - title: nls.localize('miToggleCenteredLayout', "Toggle Centered Layout") + title: nls.localize('miToggleCenteredLayout', "Centered Layout"), + toggled: IsCenteredLayoutContext }, order: 3 }); @@ -203,11 +206,22 @@ export class ToggleSidebarPositionAction extends Action { registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSidebarPositionAction, ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.LABEL), 'View: Toggle Side Bar Position', viewCategory); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '2_workbench_layout', + group: '3_workbench_layout_move', command: { id: ToggleSidebarPositionAction.ID, - title: nls.localize({ key: 'miMoveSidebarLeftRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left/Right") + title: nls.localize({ key: 'miMoveSidebarRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Right") }, + when: ContextKeyExpr.notEquals('config.workbench.sideBar.location', 'right'), + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '3_workbench_layout_move', + command: { + id: ToggleSidebarPositionAction.ID, + title: nls.localize({ key: 'miMoveSidebarLeft', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left") + }, + when: ContextKeyExpr.equals('config.workbench.sideBar.location', 'right'), order: 2 }); @@ -266,14 +280,15 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', command: { id: ToggleSidebarVisibilityAction.ID, - title: nls.localize({ key: 'miToggleSidebar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Side Bar") + title: nls.localize({ key: 'miShowSidebar', comment: ['&& denotes a mnemonic'] }, "Show &&Side Bar"), + toggled: SideBarVisibleContext }, order: 1 }); // --- Toggle Statusbar Visibility -class ToggleStatusbarVisibilityAction extends Action { +export class ToggleStatusbarVisibilityAction extends Action { static readonly ID = 'workbench.action.toggleStatusbarVisibility'; static readonly LABEL = nls.localize('toggleStatusbar', "Toggle Status Bar Visibility"); @@ -305,7 +320,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', command: { id: ToggleStatusbarVisibilityAction.ID, - title: nls.localize({ key: 'miToggleStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Status Bar") + title: nls.localize({ key: 'miShowStatusbar', comment: ['&& denotes a mnemonic'] }, "Show S&&tatus Bar"), + toggled: ContextKeyExpr.equals('config.workbench.statusBar.visible', true) }, order: 3 }); @@ -370,7 +386,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '1_toggle_view', command: { id: ToggleZenMode.ID, - title: nls.localize('miToggleZenMode', "Toggle Zen Mode") + title: nls.localize('miToggleZenMode', "Zen Mode"), + toggled: InEditorZenModeContext }, order: 2 }); @@ -427,13 +444,13 @@ if (isWindows || isLinux) { } MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '1_toggle_view', + group: '2_workbench_layout', command: { id: ToggleMenuBarAction.ID, - title: nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar") + title: nls.localize({ key: 'miShowMenuBar', comment: ['&& denotes a mnemonic'] }, "Show Menu &&Bar"), + toggled: ContextKeyExpr.and(IsMacContext.toNegated(), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'hidden'), ContextKeyExpr.notEquals('config.window.menuBarVisibility', 'toggle')) }, - when: IsMacContext.toNegated(), - order: 4 + order: 0 }); // --- Resize View diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index 8a08fb658283..fac3d53815f0 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -150,7 +150,7 @@ export abstract class Composite extends Component implements IComposite { /** * Returns an array of actions to show in the action bar of the composite. */ - getActions(): IAction[] { + getActions(): ReadonlyArray { return []; } @@ -158,14 +158,14 @@ export abstract class Composite extends Component implements IComposite { * Returns an array of actions to show in the action bar of the composite * in a less prominent way then action from getActions. */ - getSecondaryActions(): IAction[] { + getSecondaryActions(): ReadonlyArray { return []; } /** * Returns an array of actions to show in the context menu of the composite */ - getContextMenuActions(): IAction[] { + getContextMenuActions(): ReadonlyArray { return []; } diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 4d11c2605e35..62b64596c914 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; -import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -16,9 +16,10 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; +import { PanelPositionContext } from 'vs/workbench/common/panel'; export const IsMacContext = new RawContextKey('isMac', isMacintosh); export const IsLinuxContext = new RawContextKey('isLinux', isLinux); @@ -38,6 +39,8 @@ export const WorkspaceFolderCountContext = new RawContextKey('workspaceF export const RemoteFileDialogContext = new RawContextKey('remoteFileDialogVisible', false); +export const IsFullscreenContext = new RawContextKey('isFullscreen', false); + export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; @@ -54,8 +57,10 @@ export class WorkbenchContextKeysHandler extends Disposable { private inZenModeContext: IContextKey; - + private isFullscreenContext: IContextKey; + private isCenteredLayoutContext: IContextKey; private sideBarVisibleContext: IContextKey; + private panelPositionContext: IContextKey; constructor( @IContextKeyService private contextKeyService: IContextKeyService, @@ -93,6 +98,9 @@ export class WorkbenchContextKeysHandler extends Disposable { })); this._register(this.layoutService.onZenModeChange(enabled => this.inZenModeContext.set(enabled))); + this._register(this.layoutService.onFullscreenChange(fullscreen => this.isFullscreenContext.set(fullscreen))); + this._register(this.layoutService.onCenteredLayoutChange(centered => this.isCenteredLayoutContext.set(centered))); + this._register(this.layoutService.onPanelPositionChange(position => this.panelPositionContext.set(position))); this._register(this.viewletService.onDidViewletClose(() => this.updateSideBarContextKeys())); this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys())); @@ -140,11 +148,21 @@ export class WorkbenchContextKeysHandler extends Disposable { this.splitEditorsVerticallyContext = SplitEditorsVertically.bindTo(this.contextKeyService); this.updateSplitEditorsVerticallyContext(); + // Fullscreen + this.isFullscreenContext = IsFullscreenContext.bindTo(this.contextKeyService); + // Zen Mode this.inZenModeContext = InEditorZenModeContext.bindTo(this.contextKeyService); + // Centered Layout + this.isCenteredLayoutContext = IsCenteredLayoutContext.bindTo(this.contextKeyService); + // Sidebar this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService); + + // Panel Position + this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService); + this.panelPositionContext.set(this.layoutService.getPanelPosition() === Position.RIGHT ? 'right' : 'bottom'); } private updateEditorContextKeys(): void { diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index bd043e6d9769..56dd165d5c30 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -94,7 +94,8 @@ export class ResourceLabels extends Disposable { @IModelService private readonly modelService: IModelService, @IDecorationsService private readonly decorationsService: IDecorationsService, @IThemeService private readonly themeService: IThemeService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @ILabelService private readonly labelService: ILabelService ) { super(); @@ -145,6 +146,10 @@ export class ResourceLabels extends Disposable { this._widgets.forEach(widget => widget.notifyFileAssociationsChange()); } })); + + this._register(this.labelService.onDidChangeFormatters(() => { + this._widgets.forEach(widget => widget.notifyFormattersChange()); + })); } get(index: number): IResourceLabel { @@ -212,9 +217,10 @@ export class ResourceLabel extends ResourceLabels { @IModelService modelService: IModelService, @IDecorationsService decorationsService: IDecorationsService, @IThemeService themeService: IThemeService, - @IFileService fileService: IFileService + @IFileService fileService: IFileService, + @ILabelService labelService: ILabelService ) { - super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, fileService); + super(DEFAULT_LABELS_CONTAINER, instantiationService, extensionService, configurationService, modelService, decorationsService, themeService, fileService, labelService); this._label = this._register(this.create(container, options)); } @@ -309,6 +315,10 @@ class ResourceLabelWidget extends IconLabel { this.render(true); } + notifyFormattersChange(): void { + this.render(false); + } + setResource(label: IResourceLabelProps, options?: IResourceLabelOptions): void { const hasResourceChanged = this.hasResourceChanged(label, options); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index a4c0b5b4de83..b17487feaaae 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,11 +5,11 @@ import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size, EventHelper } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen, getZoomFactor } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; -import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; +import { isWindows, isLinux, isMacintosh, isWeb } from 'vs/base/common/platform'; import { pathsToEditors } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; @@ -43,7 +43,10 @@ enum Settings { SIDEBAR_POSITION = 'workbench.sideBar.location', PANEL_POSITION = 'workbench.panel.defaultLocation', - ZEN_MODE_RESTORE = 'zenMode.restore' + ZEN_MODE_RESTORE = 'zenMode.restore', + + // TODO @misolori remove before shipping stable + ICON_EXPLORATION_ENABLED = 'workbench.iconExploration.enabled' } enum Storage { @@ -63,8 +66,17 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly _onTitleBarVisibilityChange: Emitter = this._register(new Emitter()); get onTitleBarVisibilityChange(): Event { return this._onTitleBarVisibilityChange.event; } - private readonly _onZenMode: Emitter = this._register(new Emitter()); - get onZenModeChange(): Event { return this._onZenMode.event; } + private readonly _onZenModeChange: Emitter = this._register(new Emitter()); + get onZenModeChange(): Event { return this._onZenModeChange.event; } + + private readonly _onFullscreenChange: Emitter = this._register(new Emitter()); + get onFullscreenChange(): Event { return this._onFullscreenChange.event; } + + private readonly _onCenteredLayoutChange: Emitter = this._register(new Emitter()); + get onCenteredLayoutChange(): Event { return this._onCenteredLayoutChange.event; } + + private readonly _onPanelPositionChange: Emitter = this._register(new Emitter()); + get onPanelPositionChange(): Event { return this._onPanelPositionChange.event; } private readonly _onLayout = this._register(new Emitter()); get onLayout(): Event { return this._onLayout.event; } @@ -148,6 +160,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi wasSideBarVisible: false, wasPanelVisible: false, transitionDisposeables: [] as IDisposable[] + }, + + // TODO @misolori remove before shipping stable + iconExploration: { + enabled: false } }; @@ -206,6 +223,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Prevent workbench from scrolling #55456 this._register(addDisposableListener(this.container, EventType.SCROLL, () => this.container.scrollTop = 0)); + // Prevent native context menus in web #73781 + if (isWeb) { + this._register(addDisposableListener(this.container, EventType.CONTEXT_MENU, (e) => EventHelper.stop(e, true))); + } + // Menubar visibility changes if ((isWindows || isLinux) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); @@ -242,6 +264,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onTitleBarVisibilityChange.fire(); this.layout(); // handle title bar when fullscreen changes } + + this._onFullscreenChange.fire(this.state.fullscreen); } private doUpdateLayoutConfiguration(skipLayout?: boolean): void { @@ -274,6 +298,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Menubar visibility const newMenubarVisibility = this.configurationService.getValue(Settings.MENUBAR_VISIBLE); this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); + + // TODO @misolori remove before shipping stable + // Icon exploration on setting change + const newIconExplorationEnabled = this.configurationService.getValue(Settings.ICON_EXPLORATION_ENABLED); + this.setIconExploration(newIconExplorationEnabled); + } private setSideBarPosition(position: Position): void { @@ -386,6 +416,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Zen mode enablement this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE); + + // TODO @misolori remove before shipping stable + // Icon exploration + this.state.iconExploration.enabled = this.configurationService.getValue(Settings.ICON_EXPLORATION_ENABLED); + this.setIconExploration(this.state.iconExploration.enabled); } private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditor[] { @@ -631,7 +666,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Event - this._onZenMode.fire(this.state.zenMode.active); + this._onZenModeChange.fire(this.state.zenMode.active); } private setStatusBarHidden(hidden: boolean, skipLayout?: boolean): void { @@ -654,6 +689,19 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + // TODO @misolori remove before shipping stable + private setIconExploration(enabled: boolean): void { + this.state.iconExploration.enabled = enabled; + + // Update DOM + if (enabled) { + document.body.dataset.exploration = 'icon-exploration'; + } else { + document.body.dataset.exploration = ''; + } + + } + protected createWorkbenchLayout(instantiationService: IInstantiationService): void { const titleBar = this.getPart(Parts.TITLEBAR_PART); const editorPart = this.getPart(Parts.EDITOR_PART); @@ -828,6 +876,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.layout(); } } + + this._onCenteredLayoutChange.fire(this.state.editor.centered); } resizePart(part: Parts, sizeChange: number): void { @@ -1069,6 +1119,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } else { this.workbenchGrid.layout(); } + + this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); } private savePanelDimension(): void { diff --git a/src/vs/workbench/browser/legacyLayout.ts b/src/vs/workbench/browser/legacyLayout.ts index ea2a2ed200bb..f1e6c8c1eaee 100644 --- a/src/vs/workbench/browser/legacyLayout.ts +++ b/src/vs/workbench/browser/legacyLayout.ts @@ -10,14 +10,14 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { memoize } from 'vs/base/common/decorators'; import { Dimension, getClientArea, size, position, hide, show } from 'vs/base/browser/dom'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { getZoomFactor } from 'vs/base/browser/browser'; import { Part } from 'vs/workbench/browser/part'; -const TITLE_BAR_HEIGHT = isMacintosh ? 22 : 30; +const TITLE_BAR_HEIGHT = isMacintosh && !isWeb ? 22 : 30; const STATUS_BAR_HEIGHT = 22; const ACTIVITY_BAR_WIDTH = 50; diff --git a/src/vs/workbench/browser/media/icons.css b/src/vs/workbench/browser/media/icons.css new file mode 100644 index 000000000000..a71f316bc93a --- /dev/null +++ b/src/vs/workbench/browser/media/icons.css @@ -0,0 +1,1221 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/**************** + Colors +****************/ +:root { + --blue: #00539c; + --gray: #424242; + --grayLight: #848484; + --green: #388a34; + --greenLight: #9cce9c; + --orange: #c27d1a; + --orangeLight: #ff8e00; + --purple: #652d90; + --red: #a31515; + --redLight: #e51400; + --yellow: #fc0; +} + +:root .vs-dark { + --blue: #75beff; + --gray: #c5c5c5; + --grayLight: #848484; + --green: #89d185; + --greenLight: #9cce9c; + --orange: #e8ab53; + --orangeLight: #ff8e00; + --purple: #b180d7; + --red: #f48771; + --redLight: #e51400; + --yellow: #fc0; +} + + +/**************** + Base +****************/ +body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header[aria-label="Open Editors Section"] > .actions .action-label.icon, +body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.explorer-action.icon, +body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.toolbar-toggle-more.icon, +body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label^="Explorer"] .icon, +body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label="Search actions"] .icon, +body[data-exploration^="icon-exploration"] .monaco-findInput > .controls .monaco-custom-checkbox::before, +body[data-exploration^="icon-exploration"] .monaco-workbench .search-view .query-details .file-types .controls > .monaco-custom-checkbox.useExcludesAndIgnoreFiles::before, +body[data-exploration^="icon-exploration"] .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude::before, +body[data-exploration^="icon-exploration"] .search-view .search-widget .replace-container .monaco-action-bar .action-item .icon, +body[data-exploration^="icon-exploration"] .search-view a[class^="action-"], +body[data-exploration^="icon-exploration"] .monaco-workbench .search-view .query-details .more, +body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label="Source Control: Git actions"] .icon[data-title="git.commit"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label="Source Control: Git actions"] .icon[data-title="git.refresh"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label="Debug actions"] .icon, +body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .actions-container[aria-label^="Extensions"] .icon, +body[data-exploration^="icon-exploration"] .scm-viewlet .monaco-list-row > .resource-group > .actions .action-label[data-title^="git."], +body[data-exploration^="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label, +body[data-exploration^="icon-exploration"] .monaco-workbench .part > .content > .debug-viewlet .actions .action-label.icon, +body[data-exploration^="icon-exploration"] .monaco-workbench .debug-toolbar .drag-area, +body[data-exploration^="icon-exploration"] .monaco-workbench .debug-toolbar .action-label, +body[data-exploration^="icon-exploration"] .debug-breakpoint, +body[data-exploration^="icon-exploration"] .debug-viewlet .debug-breakpoints .breakpoint > .icon, +body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar .action-item .action-label.extension-action.manage, +body[data-exploration^="icon-exploration"] .extension-ratings > .star, +body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon, +body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle .octicon, +body[data-exploration^="icon-exploration"] .monaco-toolbar .action-label.toolbar-toggle-more, +body[data-exploration^="icon-exploration"] .monaco-workbench .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label, +body[data-exploration^="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon::before, +body[data-exploration^="icon-exploration"] .monaco-workbench .symbol-icon::before, +body[data-exploration^="icon-exploration"] .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .close, +body[data-exploration^="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .readMore, +body[data-exploration^="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .icon.error, +body[data-exploration^="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .icon.warning, +body[data-exploration^="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item-icon, +body[data-exploration^="icon-exploration"] .monaco-workbench > .notifications-center > .notifications-center-header .clear-all-notifications-action, +body[data-exploration^="icon-exploration"] .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action, +body[data-exploration^="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action, +body[data-exploration^="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .expand-notification-action, +body[data-exploration^="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .collapse-notification-action, +body[data-exploration^="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-label, +body[data-exploration^="icon-exploration"] .markers-panel .marker-icon, +body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix, +body[data-exploration^="icon-exploration"] .monaco-tl-twistie.collapsible:not(.loading), +body[data-exploration^="icon-exploration"] .file-icon-themable-tree .monaco-tree-row.has-children .content::before, +body[data-exploration^="icon-exploration"] .file-icon-themable-tree .monaco-tree-row.has-children.expanded .content::before, +body[data-exploration^="icon-exploration"] .monaco-breadcrumbs .monaco-breadcrumb-item:not(:nth-child(2))::before, +body[data-exploration^="icon-exploration"] .monaco-tl-twistie.collapsible.collapsed:not(.loading), +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action, +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .close-editor-action, +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.closeActiveEditor"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.splitEditor"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="git.openChange"], +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace-all::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .previous::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .next::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .close-fw::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .expand::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .collapse::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .monaco-checkbox .label::before, +body[data-exploration^="icon-exploration"] .monaco-workbench .quick-open-sidebyside-vertical, +body[data-exploration^="icon-exploration"] .monaco-tree-action.collapse-all, +body[data-exploration^="icon-exploration"] .monaco-editor .debug-breakpoint, +body[data-exploration^="icon-exploration"] .monaco-editor .debug-focused-stack-frame, +body[data-exploration^="icon-exploration"] .monaco-editor .debug-top-stack-frame, +body[data-exploration^="icon-exploration"] .monaco-editor .debug-breakpoint-hint, +body[data-exploration^="icon-exploration"] .monaco-editor .debug-breakpoint-conditional, +body[data-exploration^="icon-exploration"] .monaco-editor .debug-breakpoint-log, +body[data-exploration^="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .severity-icon, +body[data-exploration^="icon-exploration"] .monaco-editor .margin-view-overlays .folding, +body[data-exploration^="icon-exploration"] .monaco-workbench .explorer-action.save-all, +body[data-exploration^="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="git.init"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="_workbench.openUserSettingsEditor"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.compareEditor.previousChange"], +body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.icon[data-title="git.commit"], +body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.icon[data-title="git.refresh"], +body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-up, +body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-down, +body[data-exploration^="icon-exploration"] .monaco-workbench .explorer-viewlet .action-close-all-files, +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group, +body[data-exploration^="icon-exploration"] .monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more, +body[data-exploration^="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .clear-input, +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.openGlobalKeybindingsFile"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="settings.switchToJSON"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="markdown.showSource"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label.file-icon[title^="Preview"]::before, +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="markdown.showPreviewToSide"], +body[data-exploration^="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label, +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.compareEditor.nextChange"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.compareEditor.previousChange"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.compareEditor.nextChange"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="git.openFile"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="git.openFile"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.openGlobalKeybindings"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="toggle.diff.ignoreTrimWhitespace"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="toggle.diff.ignoreTrimWhitespace"], +body[data-exploration^="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions .action-label.icon[data-title="peekview.close"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.splitEditor"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="git.openChange"], +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover, +body[data-exploration^="icon-exploration"] .monaco-workbench .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .close-editor-action, +body[data-exploration^="icon-exploration"] .monaco-workbench .explorer-viewlet .explorer-open-editors .close-editor-action { + background-image: none; + background: var(--gray); + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: 16px; +} + +/* Center via Flexbox + Search expand */ +body[data-exploration^="icon-exploration"] .search-view .search-widget .toggle-replace-button.collapse, +body[data-exploration^="icon-exploration"] .search-view .search-widget .toggle-replace-button.expand, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace-all, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .previous, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .next, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .close-fw, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .expand, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .collapse, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .monaco-checkbox .label { + display: flex; + align-items: center; + justify-content: center; + background-image: none !important; +} + +/* Before elements */ +body[data-exploration^="icon-exploration"] .search-view .search-widget .toggle-replace-button.collapse::before, +body[data-exploration^="icon-exploration"] .search-view .search-widget .toggle-replace-button.expand::before, +body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace-all::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .replace::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .previous::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .next::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .close-fw::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .expand::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .collapse::before, +body[data-exploration^="icon-exploration"] .monaco-editor .find-widget .monaco-checkbox .label::before, +body[data-exploration^="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .monaco-custom-checkbox::before { + content: ""; + width: 16px; + height: 16px; + background-color: var(--gray); + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: 16px; +} + +/* For icons that are a custom checkbox and use focus */ +body[data-exploration^="icon-exploration"] .monaco-findInput > .controls .monaco-custom-checkbox, +body[data-exploration^="icon-exploration"] .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude, +body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header, +body[data-exploration^="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .monaco-custom-checkbox { + background-image: none !important; +} + +body[data-exploration^="icon-exploration"] .monaco-findInput > .controls .monaco-custom-checkbox::before, +body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-up::before, +body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-down::before, +body[data-exploration^="icon-exploration"] .monaco-workbench .search-view .query-details .file-types .controls > .monaco-custom-checkbox.useExcludesAndIgnoreFiles::before, +body[data-exploration^="icon-exploration"] .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude::before { + content: ""; + width: 16px; + height: 16px; + display: block; +} + +/******************** + ACTIVITY BAR +********************/ + +body[data-exploration^="icon-exploration"] .monaco-workbench .activitybar > .content .monaco-action-bar .badge .badge-content { + font-size: 9px; + font-weight: 600; + height: 16px; + line-height: 16px; + padding: 0 4px; +} + +body[data-exploration^="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label { + -webkit-mask-size: 24px !important; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.explore { + -webkit-mask-image: url("images/activitybar/files-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.search { + -webkit-mask-image: url("images/activitybar/search-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.scm { + -webkit-mask-image: url("images/activitybar/git-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.debug { + -webkit-mask-image: url("images/activitybar/debug-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.extensions { + -webkit-mask-image: url("images/activitybar/extensions-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .activitybar .monaco-action-bar .action-label.update-activity { + -webkit-mask-image: url("images/activitybar/settings-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-label.toggle-more { + -webkit-mask-image: url("images/activitybar/more-alt1.svg"); +} + + +/**************** + Explorer +****************/ + +body[data-exploration="icon-exploration"] .monaco-workbench .flip-editor-layout { + -webkit-mask-image: url("images/explorer/layout-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .explorer-action.save-all, +body[data-exploration="icon-exploration"] .monaco-workbench .save-all { + -webkit-mask-image: url("images/explorer/save-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .explorer-viewlet .action-close-all-files { + -webkit-mask-image: url("images/explorer/close-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .explorer-action.new-file { + -webkit-mask-image: url("images/explorer/add-file-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .explorer-action.new-folder { + -webkit-mask-image: url("images/explorer/add-folder-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .explorer-action.refresh-explorer { + -webkit-mask-image: url("images/explorer/refresh-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .explorer-action.collapse-explorer { + -webkit-mask-image: url("images/explorer/collapse-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-toolbar .action-label.toolbar-toggle-more { + -webkit-mask-image: url("images/explorer/more-alt1.svg"); +} + + +/**************** + Search +****************/ + +body[data-exploration="icon-exploration"] .monaco-workbench .search-action.refresh { + -webkit-mask-image: url("images/search/refresh-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .search-action.clear-search-results { + -webkit-mask-image: url("images/search/clear-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .search-action.collapse { + -webkit-mask-image: url("images/search/collapse-all-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .search-action.cancel-search { + -webkit-mask-image: url("images/search/stop-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .search-view .search-widget .replace-container .monaco-action-bar .action-item .icon { + -webkit-mask-image: url("images/search/replace-all-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .search-view .query-details .file-types .controls > .monaco-custom-checkbox.useExcludesAndIgnoreFiles::before, +body[data-exploration="icon-exploration"] .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude::before { + -webkit-mask-image: url("images/search/exclude-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-custom-checkbox.monaco-case-sensitive::before { + -webkit-mask-image: url("images/search/case-sensitive-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-custom-checkbox.monaco-whole-word::before { + -webkit-mask-image: url("images/search/whole-word-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-custom-checkbox.monaco-regex::before { + -webkit-mask-image: url("images/search/regex-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .search-view .action-replace { + -webkit-mask-image: url("images/search/replace-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .search-view .action-replace-all { + -webkit-mask-image: url("images/search/replace-all-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .search-view .action-remove { + -webkit-mask-image: url("images/search/remove-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .search-view .query-details .more { + -webkit-mask-image: url("images/search/more-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .search-view .search-widget .toggle-replace-button.collapse::before { + -webkit-mask-image: url("images/search/collapse-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .search-view .search-widget .toggle-replace-button.expand::before { + -webkit-mask-image: url("images/search/expand-alt1.svg"); +} + + +/**************** + Git +****************/ + +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="git.init"] { + -webkit-mask-image: url("images/git/initialze.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="git.commit"], +body[data-exploration="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.icon[data-title="git.commit"] { + -webkit-mask-image: url("images/git/check-alt1.svg"); +} +/* Refresh */ +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="git.refresh"], +body[data-exploration="icon-exploration"] .monaco-panel-view .panel > .panel-header > .actions .action-label.icon[data-title="git.refresh"] { + -webkit-mask-image: url("images/git/refresh-alt1.svg"); +} +/* Stage */ +body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="git.stageChange"], +body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource-group > .actions .action-label[data-title="git.stageAll"], +body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label[data-title="git.stage"] { + -webkit-mask-image: url("images/git/stage-alt1.svg"); +} +/* Unstage */ +body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource-group > .actions .action-label[data-title="git.unstageAll"], +body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label[data-title="git.unstage"] { + -webkit-mask-image: url("images/git/unstage-alt1.svg"); +} +/* Discard */ +body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="git.revertChange"], +body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource-group > .actions .action-label[data-title="git.cleanAll"], +body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label[data-title="git.clean"] { + -webkit-mask-image: url("images/git/clean-alt1.svg"); +} +/* Open File */ +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="_workbench.openUserSettingsEditor"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="git.openFile"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="git.openFile"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.openGlobalKeybindings"], +body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label[data-title="git.openFile2"] { + -webkit-mask-image: url("images/git/gotofile-alt1.svg"); +} +/* Chevrons */ +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.compareEditor.nextChange"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.compareEditor.nextChange"], +body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="editor.action.marker.next"], +body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="editor.action.dirtydiff.next"] { + -webkit-mask-image: url("images/git/next-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.compareEditor.previousChange"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.compareEditor.previousChange"], +body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="editor.action.marker.prev"], +body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions > .monaco-action-bar .action-label[data-title="editor.action.dirtydiff.previous"] { + -webkit-mask-image: url("images/git/previous-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="toggle.diff.ignoreTrimWhitespace"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="toggle.diff.ignoreTrimWhitespace"]{ + -webkit-mask-image: url("images/git/whitespace-alt1.svg"); +} + + +/**************** + Debug +****************/ + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-action.start, +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.continue"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon { + -webkit-mask-image: url("images/debug/start-alt1.svg"); + background: var(--green) !important; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-action.configure { + -webkit-mask-image: url("images/debug/gear-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-action.toggle-repl { + -webkit-mask-image: url("images/debug/repl-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .debug-viewlet .debug-action.add-watch-expression, +body[data-exploration="icon-exploration"] .debug-viewlet .debug-action.add-function-breakpoint { + -webkit-mask-image: url("images/debug/add-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .debug-viewlet .debug-action.remove-all { + -webkit-mask-image: url("images/debug/close-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .debug-viewlet .debug-action.breakpoints-activate { + -webkit-mask-image: url("images/debug/breakpoint-activate-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .drag-area { + -webkit-mask-image: url("images/debug/drag-alt1.svg"); + background: var(--grayLight); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.stepOver"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.stepOver"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.stepBack"], +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.stepBack"] { + -webkit-mask-image: url("images/debug/step-over-alt1.svg"); + background: var(--blue) !important; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.stepInto"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.stepInto"] { + -webkit-mask-image: url("images/debug/step-into-alt1.svg"); + background: var(--blue) !important; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.stepOut"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.stepOut"] { + -webkit-mask-image: url("images/debug/step-out-alt1.svg"); + background: var(--blue) !important; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.continue"], +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.rever"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.continue"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.rever"] { + -webkit-mask-image: url("images/debug/continue-alt1.svg"); + background: var(--blue) !important; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.restart"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.restart"] { + -webkit-mask-image: url("images/debug/restart-alt1.svg"); + background: var(--green) !important; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.pause"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.pause"] { + -webkit-mask-image: url("images/debug/pause-alt1.svg"); + background: var(--green) !important; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.stop"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.stop"] { + -webkit-mask-image: url("images/debug/stop-alt1.svg"); + background: var(--red) !important; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .debug-toolbar .action-label[data-title="workbench.action.debug.disconnect"], +body[data-exploration="icon-exploration"] .monaco-workbench .part > .title > .title-actions .action-label[data-title="workbench.action.debug.disconnect"] { + -webkit-mask-image: url("images/debug/disconnect-alt1.svg"); + background: var(--red) !important; +} + +body[data-exploration="icon-exploration"] .debug-breakpoint, +body[data-exploration="icon-exploration"] .debug-breakpoint-hint, +body[data-exploration="icon-exploration"] .debug-breakpoint.icon, +body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-column::before { + -webkit-mask-image: url("images/debug/breakpoint-alt1.svg"); + background: var(--redLight) !important; +} + +body[data-exploration="icon-exploration"] .debug-breakpoint-hint:not(.debug-breakpoint):not(.debug-breakpoint-conditional):not(.debug-top-stack-frame):not(.debug-focused-stack-frame):not(.debug-breakpoint-log) { + opacity: .5 !important; +} + +body[data-exploration="icon-exploration"] .debug-breakpoint-disabled.icon { + -webkit-mask-image: url("images/debug/breakpoint-unverified-alt1.svg"); + background: var(--grayLight); +} + +body[data-exploration="icon-exploration"] .debug-breakpoint-unverified, +body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-column.debug-breakpoint-disabled-column::before { + -webkit-mask-image: url("images/debug/breakpoint-alt1.svg"); + background: var(--grayLight); +} + +body[data-exploration="icon-exploration"] .monaco-editor .debug-top-stack-frame-column::before, +body[data-exploration="icon-exploration"] .monaco-editor .debug-top-stack-frame { + -webkit-mask-image: url("images/debug/current-arrow-alt1.svg"); + background: var(--yellow) !important; +} + +body[data-exploration="icon-exploration"] .monaco-editor .debug-top-stack-frame.debug-breakpoint, +body[data-exploration="icon-exploration"] .monaco-editor .debug-top-stack-frame.debug-breakpoint-conditional, +body[data-exploration="icon-exploration"] .monaco-editor .debug-top-stack-frame.debug-breakpoint-log, +body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-column.debug-breakpoint-column.debug-top-stack-frame-column::before { + -webkit-mask-image: url("images/debug/current-and-breakpoint-alt1.svg"); + background: var(--yellow) !important; +} + +body[data-exploration="icon-exploration"] .monaco-editor .debug-focused-stack-frame.debug-breakpoint, +body[data-exploration="icon-exploration"] .monaco-editor .debug-focused-stack-frame.debug-breakpoint-conditional, +body[data-exploration="icon-exploration"] .monaco-editor .debug-focused-stack-frame.debug-breakpoint-log { + -webkit-mask-image: url("images/debug/stackframe-and-breakpoint-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .debug-breakpoint-log, +body[data-exploration="icon-exploration"] .debug-breakpoint-log.icon, +body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-column.debug-breakpoint-log-column::before { + -webkit-mask-image: url("images/debug/breakpoint-log-alt1.svg"); + background: var(--redLight) !important; +} + +body[data-exploration="icon-exploration"] .debug-breakpoint-log-disabled, +body[data-exploration="icon-exploration"] .debug-breakpoint-log-disabled.icon, +body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-log-disabled-column::before { + -webkit-mask-image: url("images/debug/breakpoint-log-alt1.svg"); + background: var(--grayLight); +} + +body[data-exploration="icon-exploration"] .debug-breakpoint-log-unverified, +body[data-exploration="icon-exploration"] .debug-breakpoint-log-unverified.icon, +body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-log-unverified-column::before { + -webkit-mask-image: url("images/debug/breakpoint-log-unverified-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .debug-breakpoint-conditional, +body[data-exploration="icon-exploration"] .debug-breakpoint-conditional.icon, +body[data-exploration="icon-exploration"] .monaco-editor .debug-breakpoint-column.debug-breakpoint-conditional-column::before { + -webkit-mask-image: url("images/debug/breakpoint-conditional-alt1.svg"); + background: var(--redLight) !important; +} + +body[data-exploration="icon-exploration"] .monaco-editor .debug-focused-stack-frame { + -webkit-mask-image: url("images/debug/stackframe-arrow-alt1.svg"); + background: var(--green) !important; +} + +body[data-exploration="icon-exploration"] .debug-function-breakpoint, +body[data-exploration="icon-exploration"] .debug-function-breakpoint.icon { + -webkit-mask-image: url("images/debug/breakpoint-function-alt1.svg"); + background: var(--redLight) !important; +} + +body[data-exploration="icon-exploration"] .debug-function-breakpoint-unverified { + -webkit-mask-image: url("images/debug/breakpoint-function-unverified-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .debug-function-breakpoint-disabled { + -webkit-mask-image: url("images/debug/breakpoint-function-disabled-alt1.svg"); +} + +/**************** + Extensions +****************/ + +body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header { + align-items: center; +} + +body[data-exploration="icon-exploration"] .monaco-action-bar .action-item .action-label.clear-extensions { + -webkit-mask-image: url("images/extensions/clear-alt1.svg"); +} + +body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count:not(:empty), +body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .ratings { + display: flex; + align-items: center; + justify-content: center; +} + +body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon::before, +body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle .octicon::before { + content: "" !important; +} + +body[data-exploration^="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon, +body[data-exploration^="icon-exploration"] .extension-ratings.small > .full { + width: 12px; + height: 12px; + -webkit-mask-size: 12px; +} + +body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle .octicon { + width: 16px; + height: 16px; + -webkit-mask-size: 16px; +} + +body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle > span:not(:first-child):not(:empty) { + position: relative; +} + +body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle > .install > .count { + margin-left: 22px; +} + +body[data-exploration^="icon-exploration"] .extension-editor > .header > .details > .subtitle .octicon { + position: absolute; + left: 14px; + top: 0; +} + +body[data-exploration="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon, +body[data-exploration="icon-exploration"] .extension-editor > .header > .details > .subtitle .octicon { + -webkit-mask-image: url("images/extensions/download-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .extension-ratings > .full { + -webkit-mask-image: url("images/extensions/star-full-alt1.svg"); + background: var(--orangeLight); +} + +body[data-exploration="icon-exploration"] .extension-ratings > .half { + -webkit-mask-image: url("images/extensions/star-half-alt1.svg"); + background: var(--orangeLight); +} + +body[data-exploration="icon-exploration"] .extension-ratings > .empty { + -webkit-mask-image: url("images/extensions/star-empty-alt1.svg"); + background: var(--gray); +} + +body[data-exploration="icon-exploration"] .extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar .action-item .action-label.extension-action.manage { + -webkit-mask-image: url("images/extensions/gear-alt1.svg"); +} + + +/**************** + Panels +****************/ + +body[data-exploration="icon-exploration"] .monaco-workbench .hide-panel-action, +.hc-black .monaco-workbench .hide-panel-action { + -webkit-mask-image: url("images/panel/close-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .maximize-panel-action { + -webkit-mask-image: url("images/panel/up-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .minimize-panel-action { + -webkit-mask-image: url("images/panel/down-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-tree-action.collapse-all { + -webkit-mask-image: url("images/panel/collapse-all-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .output-action.open-log-file { + -webkit-mask-image: url("images/panel/gotofile-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .output-action.open-log-file { + -webkit-mask-image: url("images/panel/gotofile-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .output-action.output-scroll-unlock { + -webkit-mask-image: url("images/panel/output-unlock-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .output-action.output-scroll-lock { + -webkit-mask-image: url("images/panel/output-lock-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .output-action.clear-output { + -webkit-mask-image: url("images/panel/clear-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .debug-action.clear-repl { + -webkit-mask-image: url("images/panel/clear-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .terminal-action.kill { + -webkit-mask-image: url("images/panel/kill-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .terminal-action.split, +body[data-exploration="icon-exploration"] .monaco-workbench .quick-open-sidebyside-vertical { + -webkit-mask-image: url("images/panel/split-horizontal-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .panel.right .terminal-action.split { + -webkit-mask-image: url("images/panel/split-vertical-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .terminal-action.new { + -webkit-mask-image: url("images/panel/add-alt1.svg"); +} + + +/**************** + IntelliSense +****************/ + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .close { + -webkit-mask-image: url("images/intellisense/close-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .readMore { + -webkit-mask-image: url("images/intellisense/info-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon::before { + background-image: none !important; +} +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.method::before, +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.function::before, +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.constructor::before { + -webkit-mask-image: url("images/intellisense/method-alt1.svg"); + background-color: var(--purple); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.field::before { + -webkit-mask-image: url("images/intellisense/field-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.event::before { + -webkit-mask-image: url("images/intellisense/event-alt1.svg"); + background-color: var(--orange); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.operator::before { + -webkit-mask-image: url("images/intellisense/operator-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.variable::before { + -webkit-mask-image: url("images/intellisense/variable-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.class::before { + -webkit-mask-image: url("images/intellisense/class-alt1.svg"); + background-color: var(--orange); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.interface::before { + -webkit-mask-image: url("images/intellisense/interface-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.struct::before { + -webkit-mask-image: url("images/intellisense/structure-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.type-parameter::before { + -webkit-mask-image: url("images/intellisense/parameter-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.module::before { + -webkit-mask-image: url("images/intellisense/namespace-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.property::before { + -webkit-mask-image: url("images/intellisense/property-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.unit::before { + -webkit-mask-image: url("images/intellisense/ruler-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.constant::before { + -webkit-mask-image: url("images/intellisense/constant-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.value::before, +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.enum::before { + -webkit-mask-image: url("images/intellisense/enumerator-alt1.svg"); + background-color: var(--orange); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.enum-member::before { + -webkit-mask-image: url("images/intellisense/enum-member-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.keyword::before { + -webkit-mask-image: url("images/intellisense/keyword-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.text::before { + -webkit-mask-image: url("images/intellisense/string-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.color::before { + -webkit-mask-image: url("images/intellisense/color-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.file::before { + -webkit-mask-image: url("images/intellisense/file-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.reference::before { + -webkit-mask-image: url("images/intellisense/reference-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.snippet::before { + -webkit-mask-image: url("images/intellisense/snippet-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .suggest-widget .monaco-list .monaco-list-row .suggest-icon.folder::before { + -webkit-mask-image: url("images/intellisense/folder-alt1.svg"); +} + +/**************** + Breadcrumbs +****************/ + +body[data-exploration^="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before { + -webkit-mask-image: none; + background: transparent; +} + +body[data-exploration^="icon-exploration"] .monaco-quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon::before { + left: 0; + top: -3px; +} + +body[data-exploration^="icon-exploration"] .monaco-workbench .breadcrumbs-control .symbol-icon::before, +body[data-exploration^="icon-exploration"] .monaco-workbench .monaco-breadcrumb-item .symbol-icon { + top: -2px; +} + +body[data-exploration^="icon-exploration"] .monaco-workbench .symbol-icon { + position: relative; + background-image: none !important; + -webkit-mask-position: left center; +} + +body[data-exploration^="icon-exploration"] .monaco-workbench .symbol-icon::before { + content: ""; + position: absolute; + left: -3px; + width: 16px; + height: 22px; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon::before { + -webkit-mask-image: url("images/intellisense/field-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.constant::before { + -webkit-mask-image: url("images/intellisense/constant-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.enum::before { + -webkit-mask-image: url("images/intellisense/enumerator-alt1.svg"); + background-color: var(--orange); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.enum-member::before { + -webkit-mask-image: url("images/intellisense/enum-member-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.struct::before { + -webkit-mask-image: url("images/intellisense/structure-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.event::before { + -webkit-mask-image: url("images/intellisense/event-alt1.svg"); + background-color: var(--orange); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.operator::before { + -webkit-mask-image: url("images/intellisense/operator-alt1.svg"); + background-color: var(--blue); +} + +ody[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.type-parameter::before { + -webkit-mask-image: url("images/intellisense/parameter-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.boolean::before, +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.null::before { + -webkit-mask-image: url("images/intellisense/boolean-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.class::before { + -webkit-mask-image: url("images/intellisense/class-alt1.svg"); + background-color: var(--orange); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.constructor::before, +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.method::before, +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.function::before { + -webkit-mask-image: url("images/intellisense/method-alt1.svg"); + background-color: var(--purple); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.file::before { + -webkit-mask-image: url("images/intellisense/file-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.field::before { + -webkit-mask-image: url("images/intellisense/field-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.variable::before { + -webkit-mask-image: url("images/intellisense/variable-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.array::before { + -webkit-mask-image: url("images/intellisense/array-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.keyword::before { + -webkit-mask-image: url("images/intellisense/keyword-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.interface::before { + -webkit-mask-image: url("images/intellisense/interface-alt1.svg"); + background-color: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.object::before, +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.namespace::before, +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.package::before, +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.module::before { + -webkit-mask-image: url("images/intellisense/namespace-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.number::before { + -webkit-mask-image: url("images/intellisense/numeric-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.property::before { + -webkit-mask-image: url("images/intellisense/property-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.string::before { + -webkit-mask-image: url("images/intellisense/key-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .symbol-icon.key::before { + -webkit-mask-image: url("images/intellisense/key-alt1.svg"); +} + +body[data-exploration^="icon-exploration"] .monaco-editor .lightbulb-glyph { + background-image: none !important; + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center center; + -webkit-mask-size: 16px; +} + +body[data-exploration="icon-exploration"] .monaco-editor .lightbulb-glyph, +body[data-exploration="icon-exploration"] .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { + -webkit-mask-image: url("images/intellisense/lightbulb-alt1.svg"); + background-color: var(--yellow); +} + +body[data-exploration="icon-exploration"] .monaco-editor .lightbulb-glyph.autofixable, +body[data-exploration="icon-exploration"] .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix.autofixable { + -webkit-mask-image: url("images/intellisense/lightbulb-autofix-alt1.svg"); + background-color: var(--blue); +} + + +/**************** + Notifications +****************/ + +body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info, +body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .severity-icon.severity-info, +body[data-exploration="icon-exploration"] .markers-panel .marker-icon.severity-info { + -webkit-mask-image: url("images/notifications/info-alt1.svg"); + background: var(--blue); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning, +body[data-exploration="icon-exploration"] .markers-panel .marker-icon.severity-warning, +body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .severity-icon.severity-warning { + -webkit-mask-image: url("images/notifications/warning-alt1.svg"); + background: var(--yellow); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error, +body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-title .severity-icon.severity-error, +body[data-exploration="icon-exploration"] .markers-panel .marker-icon.severity-error { + -webkit-mask-image: url("images/notifications/error-alt1.svg"); + background: var(--redLight); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action { + -webkit-mask-image: url("images/notifications/close-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .expand-notification-action { + -webkit-mask-image: url("images/notifications/up-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .collapse-notification-action { + -webkit-mask-image: url("images/notifications/down-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .configure-notification-action { + -webkit-mask-image: url("images/notifications/configure-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench > .notifications-center > .notifications-center-header .clear-all-notifications-action { + -webkit-mask-image: url("images/notifications/closeall-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action { + -webkit-mask-image: url("images/notifications/down-alt1.svg"); +} + +body[data-exploration^="icon-exploration"] .markers-panel .monaco-tl-contents .marker-icon { + min-width: 16px; +} + + +/**************** + Tree +****************/ + +body[data-exploration^="icon-exploration"] .monaco-panel-view .panel > .panel-header::before { + position: absolute; + left: 2px; + top: 2px; +} + +body[data-exploration="icon-exploration"] .file-icon-themable-tree .monaco-tree-row.has-children.expanded .content::before, +body[data-exploration="icon-exploration"] .monaco-panel-view .panel > .panel-header.expanded::before, +body[data-exploration="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-up, +body[data-exploration="icon-exploration"] .monaco-tl-twistie.collapsible:not(.loading) { + -webkit-mask-image: url("images/tree/expand-alt1.svg"); + background-image: url("images/tree/expand-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .file-icon-themable-tree .monaco-tree-row.has-children .content::before, +body[data-exploration="icon-exploration"] .monaco-breadcrumbs .monaco-breadcrumb-item:not(:nth-child(2))::before, +body[data-exploration="icon-exploration"] .monaco-panel-view .panel > .panel-header::before, +body[data-exploration="icon-exploration"] .markers-panel .monaco-tl-contents .multiline-actions .action-label.octicon-chevron-down, +body[data-exploration="icon-exploration"] .monaco-tl-twistie.collapsible.collapsed:not(.loading) { + -webkit-mask-image: url("images/tree/collapse-alt1.svg"); + background-image: url("images/tree/collapse-alt1.svg"); + +} + +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.closeActiveEditor"], +body[data-exploration="icon-exploration"] .monaco-editor .peekview-widget .head .peekview-actions .action-label.icon[data-title="peekview.close"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .close-editor-action, +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover, +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group, +body[data-exploration="icon-exploration"] .monaco-workbench .explorer-viewlet .explorer-open-editors .close-editor-action { + -webkit-mask-image: url("images/tree/close-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action, +body[data-exploration="icon-exploration"] .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .close-editor-action { + -webkit-mask-image: url("images/tree/dirty-alt1.svg"); +} + + +/**************** + Tree +****************/ + +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="workbench.action.splitEditor"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.splitEditor"] { + -webkit-mask-image: url("images/editor/split-horizontal-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[title="Split Editor Down"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[title="Split Editor Down"] { + -webkit-mask-image: url("images/editor/split-vertical-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label[data-title="git.openChange"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .title-actions .action-label[data-title="git.openChange"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="git.openChange"] { + -webkit-mask-image: url("images/editor/open-change-alt1.svg"); +} + + +/**************** + Find +****************/ + +body[data-exploration="icon-exploration"] .monaco-editor .find-widget .previous::before { + -webkit-mask-image: url("images/find/previous-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .find-widget .next::before { + -webkit-mask-image: url("images/find/next-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .find-widget .close-fw::before { + -webkit-mask-image: url("images/find/close-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .find-widget .monaco-checkbox .label::before { + -webkit-mask-image: url("images/find/selection-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .find-widget .replace::before { + -webkit-mask-image: url("images/find/replace-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .find-widget .replace-all::before { + -webkit-mask-image: url("images/find/replace-all-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .find-widget .expand::before { + -webkit-mask-image: url("images/find/expand-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .find-widget .collapse::before { + -webkit-mask-image: url("images/find/collapse-alt1.svg"); +} + +/**************** + Misc +****************/ + +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="markdown.showPreviewToSide"]{ + -webkit-mask-image: url("images/misc/preview-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label.file-icon[title^="Preview"]::before{ + -webkit-mask-image: url("images/misc/preview-icon-alt1.svg"); + -webkit-mask-position: left center; +} + +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="markdown.showSource"]{ + -webkit-mask-image: url("images/misc/gotofile-alt1.svg") +} + +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="workbench.action.openGlobalKeybindingsFile"], +body[data-exploration="icon-exploration"] .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-label[data-title="settings.switchToJSON"]{ + -webkit-mask-image: url("images/misc/json-alt1.svg") +} + +body[data-exploration^="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .monaco-custom-checkbox::before { + display: block; +} + +body[data-exploration="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .record-keys::before { + -webkit-mask-image: url("images/misc/keyboard-alt1.svg") +} + +body[data-exploration="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .clear-input { + -webkit-mask-image: url("images/misc/clear-alt1.svg") +} + +body[data-exploration="icon-exploration"] .keybindings-editor .monaco-action-bar .action-item > .sort-by-precedence::before { + -webkit-mask-image: url("images/misc/precedence-alt1.svg") +} + +body[data-exploration="icon-exploration"] .monaco-workbench .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more { + -webkit-mask-image: url("images/misc/more-alt1.svg"); + background: var(--gray) !important; +} + +body[data-exploration^="icon-exploration"] .monaco-editor .margin-view-overlays .folding, +body[data-exploration^="icon-exploration"] .monaco-editor .margin-view-overlays .folding.collapsed { + -webkit-mask-position: 75% 50%; +} + +body[data-exploration="icon-exploration"] .monaco-editor .margin-view-overlays .folding { + -webkit-mask-image: url("images/misc/fold-alt1.svg"); +} + +body[data-exploration="icon-exploration"] .monaco-editor .margin-view-overlays .folding.collapsed { + -webkit-mask-image: url("images/misc/unfold-alt1.svg"); +} \ No newline at end of file diff --git a/src/vs/workbench/browser/media/images/activitybar/debug-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/debug-alt1.svg new file mode 100644 index 000000000000..dfe350a283f4 --- /dev/null +++ b/src/vs/workbench/browser/media/images/activitybar/debug-alt1.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/activitybar/extensions-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/extensions-alt1.svg new file mode 100644 index 000000000000..3fd6c5b9ca1d --- /dev/null +++ b/src/vs/workbench/browser/media/images/activitybar/extensions-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/activitybar/files-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/files-alt1.svg new file mode 100644 index 000000000000..f2ddfd97b798 --- /dev/null +++ b/src/vs/workbench/browser/media/images/activitybar/files-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/activitybar/git-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/git-alt1.svg new file mode 100644 index 000000000000..6ce8a7c1c519 --- /dev/null +++ b/src/vs/workbench/browser/media/images/activitybar/git-alt1.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/activitybar/more-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/more-alt1.svg new file mode 100644 index 000000000000..6729ca3c90d1 --- /dev/null +++ b/src/vs/workbench/browser/media/images/activitybar/more-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/activitybar/search-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/search-alt1.svg new file mode 100644 index 000000000000..fb1a6e7bd59c --- /dev/null +++ b/src/vs/workbench/browser/media/images/activitybar/search-alt1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/activitybar/settings-alt1.svg b/src/vs/workbench/browser/media/images/activitybar/settings-alt1.svg new file mode 100644 index 000000000000..b2a61102112a --- /dev/null +++ b/src/vs/workbench/browser/media/images/activitybar/settings-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/add-alt1.svg b/src/vs/workbench/browser/media/images/debug/add-alt1.svg new file mode 100644 index 000000000000..fb50c6c28499 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/add-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-activate-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-activate-alt1.svg new file mode 100644 index 000000000000..3e6b0a4c4df8 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/breakpoint-activate-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-alt1.svg new file mode 100644 index 000000000000..e391c3b08527 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/breakpoint-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-conditional-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-conditional-alt1.svg new file mode 100644 index 000000000000..9794d6b55225 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/breakpoint-conditional-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-function-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-function-alt1.svg new file mode 100644 index 000000000000..76d1f05da2cc --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/breakpoint-function-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-function-disabled-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-function-disabled-alt1.svg new file mode 100644 index 000000000000..76d1f05da2cc --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/breakpoint-function-disabled-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-function-unverified-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-function-unverified-alt1.svg new file mode 100644 index 000000000000..ece98d0f3561 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/breakpoint-function-unverified-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-log-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-log-alt1.svg new file mode 100644 index 000000000000..21ada7b29285 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/breakpoint-log-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-log-unverified-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-log-unverified-alt1.svg new file mode 100644 index 000000000000..f540bf94efb7 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/breakpoint-log-unverified-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/breakpoint-unverified-alt1.svg b/src/vs/workbench/browser/media/images/debug/breakpoint-unverified-alt1.svg new file mode 100644 index 000000000000..6bfa8fd2f5a5 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/breakpoint-unverified-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/close-alt1.svg b/src/vs/workbench/browser/media/images/debug/close-alt1.svg new file mode 100644 index 000000000000..8af24a5b1e85 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/close-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/debug/continue-alt1.svg b/src/vs/workbench/browser/media/images/debug/continue-alt1.svg new file mode 100644 index 000000000000..d9e1bcfa25dd --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/continue-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/debug/current-and-breakpoint-alt1.svg b/src/vs/workbench/browser/media/images/debug/current-and-breakpoint-alt1.svg new file mode 100644 index 000000000000..df3c2501a533 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/current-and-breakpoint-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/debug/current-arrow-alt1.svg b/src/vs/workbench/browser/media/images/debug/current-arrow-alt1.svg new file mode 100644 index 000000000000..f62a9fb6db4f --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/current-arrow-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/disconnect-alt1.svg b/src/vs/workbench/browser/media/images/debug/disconnect-alt1.svg new file mode 100644 index 000000000000..71aae0dd887f --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/disconnect-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/drag-alt1.svg b/src/vs/workbench/browser/media/images/debug/drag-alt1.svg new file mode 100644 index 000000000000..b6b93f31fdff --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/drag-alt1.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/debug/gear-alt1.svg b/src/vs/workbench/browser/media/images/debug/gear-alt1.svg new file mode 100644 index 000000000000..7db1664af786 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/gear-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/debug/pause-alt1.svg b/src/vs/workbench/browser/media/images/debug/pause-alt1.svg new file mode 100644 index 000000000000..1050e04f2d9f --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/pause-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/repl-alt1.svg b/src/vs/workbench/browser/media/images/debug/repl-alt1.svg new file mode 100644 index 000000000000..f13e57b89ac2 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/repl-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/debug/restart-alt1.svg b/src/vs/workbench/browser/media/images/debug/restart-alt1.svg new file mode 100644 index 000000000000..28c63aae4d93 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/restart-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/debug/stackframe-and-breakpoint-alt1.svg b/src/vs/workbench/browser/media/images/debug/stackframe-and-breakpoint-alt1.svg new file mode 100644 index 000000000000..df3c2501a533 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/stackframe-and-breakpoint-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/debug/stackframe-arrow-alt1.svg b/src/vs/workbench/browser/media/images/debug/stackframe-arrow-alt1.svg new file mode 100644 index 000000000000..f62a9fb6db4f --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/stackframe-arrow-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/start-alt1.svg b/src/vs/workbench/browser/media/images/debug/start-alt1.svg new file mode 100644 index 000000000000..30196ede1685 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/start-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/debug/step-into-alt1.svg b/src/vs/workbench/browser/media/images/debug/step-into-alt1.svg new file mode 100644 index 000000000000..2113d7b1987a --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/step-into-alt1.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/debug/step-out-alt1.svg b/src/vs/workbench/browser/media/images/debug/step-out-alt1.svg new file mode 100644 index 000000000000..6724964aea15 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/step-out-alt1.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/debug/step-over-alt1.svg b/src/vs/workbench/browser/media/images/debug/step-over-alt1.svg new file mode 100644 index 000000000000..f2454843ae53 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/step-over-alt1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/debug/stop-alt1.svg b/src/vs/workbench/browser/media/images/debug/stop-alt1.svg new file mode 100644 index 000000000000..4e348228b619 --- /dev/null +++ b/src/vs/workbench/browser/media/images/debug/stop-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/editor/open-change-alt1.svg b/src/vs/workbench/browser/media/images/editor/open-change-alt1.svg new file mode 100644 index 000000000000..0c8297f940b0 --- /dev/null +++ b/src/vs/workbench/browser/media/images/editor/open-change-alt1.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/editor/split-horizontal-alt1.svg b/src/vs/workbench/browser/media/images/editor/split-horizontal-alt1.svg new file mode 100644 index 000000000000..c92025ddd92f --- /dev/null +++ b/src/vs/workbench/browser/media/images/editor/split-horizontal-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/editor/split-vertical-alt1.svg b/src/vs/workbench/browser/media/images/editor/split-vertical-alt1.svg new file mode 100644 index 000000000000..f407c08fa341 --- /dev/null +++ b/src/vs/workbench/browser/media/images/editor/split-vertical-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/explorer/add-file-alt1.svg b/src/vs/workbench/browser/media/images/explorer/add-file-alt1.svg new file mode 100644 index 000000000000..138351f66b2e --- /dev/null +++ b/src/vs/workbench/browser/media/images/explorer/add-file-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/explorer/add-folder-alt1.svg b/src/vs/workbench/browser/media/images/explorer/add-folder-alt1.svg new file mode 100644 index 000000000000..0f1a2bfb9ba9 --- /dev/null +++ b/src/vs/workbench/browser/media/images/explorer/add-folder-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/explorer/close-alt1.svg b/src/vs/workbench/browser/media/images/explorer/close-alt1.svg new file mode 100644 index 000000000000..8af24a5b1e85 --- /dev/null +++ b/src/vs/workbench/browser/media/images/explorer/close-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/explorer/collapse-alt1.svg b/src/vs/workbench/browser/media/images/explorer/collapse-alt1.svg new file mode 100644 index 000000000000..f2e0e5dd5f6e --- /dev/null +++ b/src/vs/workbench/browser/media/images/explorer/collapse-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/explorer/layout-alt1.svg b/src/vs/workbench/browser/media/images/explorer/layout-alt1.svg new file mode 100644 index 000000000000..40c1b46b197f --- /dev/null +++ b/src/vs/workbench/browser/media/images/explorer/layout-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/explorer/more-alt1.svg b/src/vs/workbench/browser/media/images/explorer/more-alt1.svg new file mode 100644 index 000000000000..3d7068f6b4cd --- /dev/null +++ b/src/vs/workbench/browser/media/images/explorer/more-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/explorer/refresh-alt1.svg b/src/vs/workbench/browser/media/images/explorer/refresh-alt1.svg new file mode 100644 index 000000000000..a940b8ef4a62 --- /dev/null +++ b/src/vs/workbench/browser/media/images/explorer/refresh-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/explorer/save-alt1.svg b/src/vs/workbench/browser/media/images/explorer/save-alt1.svg new file mode 100644 index 000000000000..5756795cb42f --- /dev/null +++ b/src/vs/workbench/browser/media/images/explorer/save-alt1.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/vs/workbench/browser/media/images/extensions/clear-alt1.svg b/src/vs/workbench/browser/media/images/extensions/clear-alt1.svg new file mode 100644 index 000000000000..63be0fae2151 --- /dev/null +++ b/src/vs/workbench/browser/media/images/extensions/clear-alt1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/vs/workbench/browser/media/images/extensions/download-alt1.svg b/src/vs/workbench/browser/media/images/extensions/download-alt1.svg new file mode 100644 index 000000000000..211864d2c8b9 --- /dev/null +++ b/src/vs/workbench/browser/media/images/extensions/download-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/extensions/gear-alt1.svg b/src/vs/workbench/browser/media/images/extensions/gear-alt1.svg new file mode 100644 index 000000000000..7db1664af786 --- /dev/null +++ b/src/vs/workbench/browser/media/images/extensions/gear-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/extensions/star-empty-alt1.svg b/src/vs/workbench/browser/media/images/extensions/star-empty-alt1.svg new file mode 100644 index 000000000000..a35ded971f76 --- /dev/null +++ b/src/vs/workbench/browser/media/images/extensions/star-empty-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/extensions/star-full-alt1.svg b/src/vs/workbench/browser/media/images/extensions/star-full-alt1.svg new file mode 100644 index 000000000000..2413e6ecb1f1 --- /dev/null +++ b/src/vs/workbench/browser/media/images/extensions/star-full-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/extensions/star-half-alt1.svg b/src/vs/workbench/browser/media/images/extensions/star-half-alt1.svg new file mode 100644 index 000000000000..4e8dcd71f3bf --- /dev/null +++ b/src/vs/workbench/browser/media/images/extensions/star-half-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/find/close-alt1.svg b/src/vs/workbench/browser/media/images/find/close-alt1.svg new file mode 100644 index 000000000000..2512e9d61aac --- /dev/null +++ b/src/vs/workbench/browser/media/images/find/close-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/find/collapse-alt1.svg b/src/vs/workbench/browser/media/images/find/collapse-alt1.svg new file mode 100644 index 000000000000..6d01adc07ea1 --- /dev/null +++ b/src/vs/workbench/browser/media/images/find/collapse-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/find/expand-alt1.svg b/src/vs/workbench/browser/media/images/find/expand-alt1.svg new file mode 100644 index 000000000000..a1a96a60926e --- /dev/null +++ b/src/vs/workbench/browser/media/images/find/expand-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/find/next-alt1.svg b/src/vs/workbench/browser/media/images/find/next-alt1.svg new file mode 100644 index 000000000000..d2660e6e90f9 --- /dev/null +++ b/src/vs/workbench/browser/media/images/find/next-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/find/previous-alt1.svg b/src/vs/workbench/browser/media/images/find/previous-alt1.svg new file mode 100644 index 000000000000..6f8d0cbcb6df --- /dev/null +++ b/src/vs/workbench/browser/media/images/find/previous-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/find/replace-all-alt1.svg b/src/vs/workbench/browser/media/images/find/replace-all-alt1.svg new file mode 100644 index 000000000000..66437cf4410e --- /dev/null +++ b/src/vs/workbench/browser/media/images/find/replace-all-alt1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/find/replace-alt1.svg b/src/vs/workbench/browser/media/images/find/replace-alt1.svg new file mode 100644 index 000000000000..b599b6e5432e --- /dev/null +++ b/src/vs/workbench/browser/media/images/find/replace-alt1.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/vs/workbench/browser/media/images/find/selection-alt1.svg b/src/vs/workbench/browser/media/images/find/selection-alt1.svg new file mode 100644 index 000000000000..e0503a93a515 --- /dev/null +++ b/src/vs/workbench/browser/media/images/find/selection-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/git/check-alt1.svg b/src/vs/workbench/browser/media/images/git/check-alt1.svg new file mode 100644 index 000000000000..a5597d7c4049 --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/check-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/git/clean-alt1.svg b/src/vs/workbench/browser/media/images/git/clean-alt1.svg new file mode 100644 index 000000000000..2f3dd525c0d7 --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/clean-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/git/close-alt1.svg b/src/vs/workbench/browser/media/images/git/close-alt1.svg new file mode 100644 index 000000000000..64618b61760c --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/close-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/git/gotofile-alt1.svg b/src/vs/workbench/browser/media/images/git/gotofile-alt1.svg new file mode 100644 index 000000000000..aabfc34c3e4a --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/gotofile-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/git/initialze.svg b/src/vs/workbench/browser/media/images/git/initialze.svg new file mode 100644 index 000000000000..fb50c6c28499 --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/initialze.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/git/next-alt1.svg b/src/vs/workbench/browser/media/images/git/next-alt1.svg new file mode 100644 index 000000000000..d2660e6e90f9 --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/next-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/git/previous-alt1.svg b/src/vs/workbench/browser/media/images/git/previous-alt1.svg new file mode 100644 index 000000000000..6f8d0cbcb6df --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/previous-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/git/refresh-alt1.svg b/src/vs/workbench/browser/media/images/git/refresh-alt1.svg new file mode 100644 index 000000000000..a940b8ef4a62 --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/refresh-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/git/stage-alt1.svg b/src/vs/workbench/browser/media/images/git/stage-alt1.svg new file mode 100644 index 000000000000..fb50c6c28499 --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/stage-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/git/unstage-alt1.svg b/src/vs/workbench/browser/media/images/git/unstage-alt1.svg new file mode 100644 index 000000000000..ae942eb67483 --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/unstage-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/git/whitespace-alt1.svg b/src/vs/workbench/browser/media/images/git/whitespace-alt1.svg new file mode 100644 index 000000000000..b17a38ab079d --- /dev/null +++ b/src/vs/workbench/browser/media/images/git/whitespace-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/array-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/array-alt1.svg new file mode 100644 index 000000000000..b058a7ca9cc8 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/array-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/boolean-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/boolean-alt1.svg new file mode 100644 index 000000000000..8fbcf89f2fdf --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/boolean-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/class-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/class-alt1.svg new file mode 100644 index 000000000000..c939df47de56 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/class-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/close-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/close-alt1.svg new file mode 100644 index 000000000000..1d8840d92763 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/close-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/color-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/color-alt1.svg new file mode 100644 index 000000000000..914bb6f48d50 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/color-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/constant-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/constant-alt1.svg new file mode 100644 index 000000000000..bbb9e6dcbf85 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/constant-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/enum-member-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/enum-member-alt1.svg new file mode 100644 index 000000000000..e182868e71e5 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/enum-member-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/enumerator-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/enumerator-alt1.svg new file mode 100644 index 000000000000..7b87fce32023 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/enumerator-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/event-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/event-alt1.svg new file mode 100644 index 000000000000..9c7c2504c249 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/event-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/field-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/field-alt1.svg new file mode 100644 index 000000000000..e547fb51a2d8 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/field-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/file-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/file-alt1.svg new file mode 100644 index 000000000000..9a0f5212ac70 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/file-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/folder-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/folder-alt1.svg new file mode 100644 index 000000000000..8d3f68206e00 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/folder-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/info-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/info-alt1.svg new file mode 100644 index 000000000000..ef9fe8777a3f --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/info-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/interface-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/interface-alt1.svg new file mode 100644 index 000000000000..9114ce3f3e86 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/interface-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/key-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/key-alt1.svg new file mode 100644 index 000000000000..58c6cf4ca115 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/key-alt1.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/keyword-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/keyword-alt1.svg new file mode 100644 index 000000000000..f21364efec0b --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/keyword-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/lightbulb-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/lightbulb-alt1.svg new file mode 100644 index 000000000000..1583a31632c9 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/lightbulb-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/lightbulb-autofix-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/lightbulb-autofix-alt1.svg new file mode 100644 index 000000000000..d5ef68f9b83f --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/lightbulb-autofix-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/method-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/method-alt1.svg new file mode 100644 index 000000000000..392febfaa6b9 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/method-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/namespace-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/namespace-alt1.svg new file mode 100644 index 000000000000..bb4c103c752b --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/namespace-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/numeric-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/numeric-alt1.svg new file mode 100644 index 000000000000..90cf05e747ae --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/numeric-alt1.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/operator-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/operator-alt1.svg new file mode 100644 index 000000000000..20f50387a69a --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/operator-alt1.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/parameter-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/parameter-alt1.svg new file mode 100644 index 000000000000..00198f67f4e5 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/parameter-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/property-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/property-alt1.svg new file mode 100644 index 000000000000..828113202a98 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/property-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/reference-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/reference-alt1.svg new file mode 100644 index 000000000000..aabfc34c3e4a --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/reference-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/ruler-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/ruler-alt1.svg new file mode 100644 index 000000000000..eec16f41fbf6 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/ruler-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/snippet-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/snippet-alt1.svg new file mode 100644 index 000000000000..ee82045f2ac2 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/snippet-alt1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/string-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/string-alt1.svg new file mode 100644 index 000000000000..1aa09c086cbc --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/string-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/structure-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/structure-alt1.svg new file mode 100644 index 000000000000..f0e857ed7ca4 --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/structure-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/intellisense/variable-alt1.svg b/src/vs/workbench/browser/media/images/intellisense/variable-alt1.svg new file mode 100644 index 000000000000..8cf0dbb7116d --- /dev/null +++ b/src/vs/workbench/browser/media/images/intellisense/variable-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/misc/clear-alt1.svg b/src/vs/workbench/browser/media/images/misc/clear-alt1.svg new file mode 100644 index 000000000000..0e624a231916 --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/clear-alt1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/vs/workbench/browser/media/images/misc/fold-alt1.svg b/src/vs/workbench/browser/media/images/misc/fold-alt1.svg new file mode 100644 index 000000000000..69efb3e69578 --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/fold-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/misc/gotofile-alt1.svg b/src/vs/workbench/browser/media/images/misc/gotofile-alt1.svg new file mode 100644 index 000000000000..aabfc34c3e4a --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/gotofile-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/misc/json-alt1.svg b/src/vs/workbench/browser/media/images/misc/json-alt1.svg new file mode 100644 index 000000000000..9e8f99141fc6 --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/json-alt1.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/misc/keyboard-alt1.svg b/src/vs/workbench/browser/media/images/misc/keyboard-alt1.svg new file mode 100644 index 000000000000..6181acae7ef0 --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/keyboard-alt1.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/misc/more-alt1.svg b/src/vs/workbench/browser/media/images/misc/more-alt1.svg new file mode 100644 index 000000000000..3d7068f6b4cd --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/more-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/misc/precedence-alt1.svg b/src/vs/workbench/browser/media/images/misc/precedence-alt1.svg new file mode 100644 index 000000000000..2609f1f40f90 --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/precedence-alt1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/vs/workbench/browser/media/images/misc/preview-alt1.svg b/src/vs/workbench/browser/media/images/misc/preview-alt1.svg new file mode 100644 index 000000000000..25bf0b58bb9f --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/preview-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/misc/preview-icon-alt1.svg b/src/vs/workbench/browser/media/images/misc/preview-icon-alt1.svg new file mode 100644 index 000000000000..df907d5dd141 --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/preview-icon-alt1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/vs/workbench/browser/media/images/misc/split-alt1.svg b/src/vs/workbench/browser/media/images/misc/split-alt1.svg new file mode 100644 index 000000000000..c92025ddd92f --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/split-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/misc/unfold-alt1.svg b/src/vs/workbench/browser/media/images/misc/unfold-alt1.svg new file mode 100644 index 000000000000..bb57558f6087 --- /dev/null +++ b/src/vs/workbench/browser/media/images/misc/unfold-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/notifications/close-alt1.svg b/src/vs/workbench/browser/media/images/notifications/close-alt1.svg new file mode 100644 index 000000000000..64618b61760c --- /dev/null +++ b/src/vs/workbench/browser/media/images/notifications/close-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/notifications/closeall-alt1.svg b/src/vs/workbench/browser/media/images/notifications/closeall-alt1.svg new file mode 100644 index 000000000000..72bf0a8b54a8 --- /dev/null +++ b/src/vs/workbench/browser/media/images/notifications/closeall-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/notifications/configure-alt1.svg b/src/vs/workbench/browser/media/images/notifications/configure-alt1.svg new file mode 100644 index 000000000000..7db1664af786 --- /dev/null +++ b/src/vs/workbench/browser/media/images/notifications/configure-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/notifications/down-alt1.svg b/src/vs/workbench/browser/media/images/notifications/down-alt1.svg new file mode 100644 index 000000000000..7042b08fddf2 --- /dev/null +++ b/src/vs/workbench/browser/media/images/notifications/down-alt1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/notifications/error-alt1.svg b/src/vs/workbench/browser/media/images/notifications/error-alt1.svg new file mode 100644 index 000000000000..e39bf5e0e4a4 --- /dev/null +++ b/src/vs/workbench/browser/media/images/notifications/error-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/notifications/info-alt1.svg b/src/vs/workbench/browser/media/images/notifications/info-alt1.svg new file mode 100644 index 000000000000..4a597c7b804e --- /dev/null +++ b/src/vs/workbench/browser/media/images/notifications/info-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/notifications/up-alt1.svg b/src/vs/workbench/browser/media/images/notifications/up-alt1.svg new file mode 100644 index 000000000000..d5edcc4c3057 --- /dev/null +++ b/src/vs/workbench/browser/media/images/notifications/up-alt1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/notifications/warning-alt1.svg b/src/vs/workbench/browser/media/images/notifications/warning-alt1.svg new file mode 100644 index 000000000000..948c0b4decf7 --- /dev/null +++ b/src/vs/workbench/browser/media/images/notifications/warning-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/panel/add-alt1.svg b/src/vs/workbench/browser/media/images/panel/add-alt1.svg new file mode 100644 index 000000000000..fb50c6c28499 --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/add-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/panel/clear-alt1.svg b/src/vs/workbench/browser/media/images/panel/clear-alt1.svg new file mode 100644 index 000000000000..63be0fae2151 --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/clear-alt1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/vs/workbench/browser/media/images/panel/close-all-alt1.svg b/src/vs/workbench/browser/media/images/panel/close-all-alt1.svg new file mode 100644 index 000000000000..8af24a5b1e85 --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/close-all-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/panel/close-alt1.svg b/src/vs/workbench/browser/media/images/panel/close-alt1.svg new file mode 100644 index 000000000000..3818f7d53506 --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/close-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/panel/collapse-all-alt1.svg b/src/vs/workbench/browser/media/images/panel/collapse-all-alt1.svg new file mode 100644 index 000000000000..f2e0e5dd5f6e --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/collapse-all-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/panel/down-alt1.svg b/src/vs/workbench/browser/media/images/panel/down-alt1.svg new file mode 100644 index 000000000000..de3313624a40 --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/down-alt1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/panel/gear-alt1.svg b/src/vs/workbench/browser/media/images/panel/gear-alt1.svg new file mode 100644 index 000000000000..7db1664af786 --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/gear-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/panel/gotofile-alt1.svg b/src/vs/workbench/browser/media/images/panel/gotofile-alt1.svg new file mode 100644 index 000000000000..aabfc34c3e4a --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/gotofile-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/panel/kill-alt1.svg b/src/vs/workbench/browser/media/images/panel/kill-alt1.svg new file mode 100644 index 000000000000..bc448290732b --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/kill-alt1.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/panel/output-lock-alt1.svg b/src/vs/workbench/browser/media/images/panel/output-lock-alt1.svg new file mode 100644 index 000000000000..70d268965a0d --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/output-lock-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/panel/output-unlock-alt1.svg b/src/vs/workbench/browser/media/images/panel/output-unlock-alt1.svg new file mode 100644 index 000000000000..cf8268d37d26 --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/output-unlock-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/panel/split-horizontal-alt1.svg b/src/vs/workbench/browser/media/images/panel/split-horizontal-alt1.svg new file mode 100644 index 000000000000..c92025ddd92f --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/split-horizontal-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/panel/split-vertical-alt1.svg b/src/vs/workbench/browser/media/images/panel/split-vertical-alt1.svg new file mode 100644 index 000000000000..f407c08fa341 --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/split-vertical-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/panel/up-alt1.svg b/src/vs/workbench/browser/media/images/panel/up-alt1.svg new file mode 100644 index 000000000000..e7ac370945fe --- /dev/null +++ b/src/vs/workbench/browser/media/images/panel/up-alt1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/search/case-sensitive-alt1.svg b/src/vs/workbench/browser/media/images/search/case-sensitive-alt1.svg new file mode 100644 index 000000000000..418172b68660 --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/case-sensitive-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/search/clear-alt1.svg b/src/vs/workbench/browser/media/images/search/clear-alt1.svg new file mode 100644 index 000000000000..890a6cddaa1f --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/clear-alt1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/vs/workbench/browser/media/images/search/collapse-all-alt1.svg b/src/vs/workbench/browser/media/images/search/collapse-all-alt1.svg new file mode 100644 index 000000000000..f2e0e5dd5f6e --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/collapse-all-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/search/collapse-alt1.svg b/src/vs/workbench/browser/media/images/search/collapse-alt1.svg new file mode 100644 index 000000000000..829e53760f26 --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/collapse-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/search/exclude-alt1.svg b/src/vs/workbench/browser/media/images/search/exclude-alt1.svg new file mode 100644 index 000000000000..2257ec6dae9a --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/exclude-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/search/expand-alt1.svg b/src/vs/workbench/browser/media/images/search/expand-alt1.svg new file mode 100644 index 000000000000..a1085f2ad07c --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/expand-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/search/more-alt1.svg b/src/vs/workbench/browser/media/images/search/more-alt1.svg new file mode 100644 index 000000000000..a83faaa6ffb5 --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/more-alt1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/vs/workbench/browser/media/images/search/refresh-alt1.svg b/src/vs/workbench/browser/media/images/search/refresh-alt1.svg new file mode 100644 index 000000000000..a940b8ef4a62 --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/refresh-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/search/regex-alt1.svg b/src/vs/workbench/browser/media/images/search/regex-alt1.svg new file mode 100644 index 000000000000..bffb311a5be8 --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/regex-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/search/remove-alt1.svg b/src/vs/workbench/browser/media/images/search/remove-alt1.svg new file mode 100644 index 000000000000..64618b61760c --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/remove-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/search/replace-all-alt1.svg b/src/vs/workbench/browser/media/images/search/replace-all-alt1.svg new file mode 100644 index 000000000000..1ef9b63cd606 --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/replace-all-alt1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/vs/workbench/browser/media/images/search/replace-alt1.svg b/src/vs/workbench/browser/media/images/search/replace-alt1.svg new file mode 100644 index 000000000000..0979845e8369 --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/replace-alt1.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/vs/workbench/browser/media/images/search/stop-alt1.svg b/src/vs/workbench/browser/media/images/search/stop-alt1.svg new file mode 100644 index 000000000000..52f3aa26ce28 --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/stop-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/search/whole-word-alt1.svg b/src/vs/workbench/browser/media/images/search/whole-word-alt1.svg new file mode 100644 index 000000000000..8a18eed8deb8 --- /dev/null +++ b/src/vs/workbench/browser/media/images/search/whole-word-alt1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/vs/workbench/browser/media/images/tree/close-alt1.svg b/src/vs/workbench/browser/media/images/tree/close-alt1.svg new file mode 100644 index 000000000000..3818f7d53506 --- /dev/null +++ b/src/vs/workbench/browser/media/images/tree/close-alt1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/browser/media/images/tree/collapse-alt1.svg b/src/vs/workbench/browser/media/images/tree/collapse-alt1.svg new file mode 100644 index 000000000000..f6e6553774e7 --- /dev/null +++ b/src/vs/workbench/browser/media/images/tree/collapse-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/tree/dirty-alt1.svg b/src/vs/workbench/browser/media/images/tree/dirty-alt1.svg new file mode 100644 index 000000000000..e391c3b08527 --- /dev/null +++ b/src/vs/workbench/browser/media/images/tree/dirty-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/media/images/tree/expand-alt1.svg b/src/vs/workbench/browser/media/images/tree/expand-alt1.svg new file mode 100644 index 000000000000..a98a85b340ed --- /dev/null +++ b/src/vs/workbench/browser/media/images/tree/expand-alt1.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index cc98b6a6102a..cbd07b403929 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -61,11 +61,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { //#endregion private globalActionBar: ActionBar; - private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; } = Object.create(null); + private globalActivityIdToActions: Map = new Map(); private cachedViewlets: ICachedViewlet[] = []; + private compositeBar: CompositeBar; - private compositeActions: { [compositeId: string]: { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null); + private compositeActions: Map = new Map(); + private readonly viewletDisposables: Map = new Map(); constructor( @@ -169,7 +171,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { throw illegalArgument('badge'); } - const action = this.globalActivityIdToActions[globalActivityId]; + const action = this.globalActivityIdToActions.get(globalActivityId); if (!action) { throw illegalArgument('globalActivityId'); } @@ -181,14 +183,15 @@ export class ActivitybarPart extends Part implements IActivityBarService { createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; + const content = document.createElement('div'); addClass(content, 'content'); parent.appendChild(content); - // Top Actionbar with action items for each viewlet action + // Viewlets action bar this.compositeBar.create(content); - // Top Actionbar with action items for each viewlet action + // Global action bar const globalActivities = document.createElement('div'); addClass(globalActivities, 'global-activity'); content.appendChild(globalActivities); @@ -208,7 +211,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder); const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT; - container.style.boxSizing = borderColor && isPositionLeft ? 'border-box' : null; + container.style.boxSizing = borderColor && isPositionLeft ? 'border-box' : ''; container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : null; container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : null; container.style.borderRightColor = isPositionLeft ? borderColor : null; @@ -243,13 +246,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { })); actions.forEach(a => { - this.globalActivityIdToActions[a.id] = a; + this.globalActivityIdToActions.set(a.id, a); this.globalActionBar.push(a); }); } private getCompositeActions(compositeId: string): { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } { - let compositeActions = this.compositeActions[compositeId]; + let compositeActions = this.compositeActions.get(compositeId); if (!compositeActions) { const viewlet = this.viewletService.getViewlet(compositeId); if (viewlet) { @@ -265,7 +268,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { }; } - this.compositeActions[compositeId] = compositeActions; + this.compositeActions.set(compositeId, compositeActions); } return compositeActions; @@ -341,11 +344,11 @@ export class ActivitybarPart extends Part implements IActivityBarService { private hideComposite(compositeId: string): void { this.compositeBar.hideComposite(compositeId); - const compositeActions = this.compositeActions[compositeId]; + const compositeActions = this.compositeActions.get(compositeId); if (compositeActions) { compositeActions.activityAction.dispose(); compositeActions.pinnedAction.dispose(); - delete this.compositeActions[compositeId]; + this.compositeActions.delete(compositeId); } } diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 7f21c71b8ef6..223fb6f42a36 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -427,7 +427,7 @@ export class CompositeBar extends Widget implements ICompositeBar { }); } - private getContextMenuActions(): IAction[] { + private getContextMenuActions(): ReadonlyArray { const actions: IAction[] = this.model.visibleItems .map(({ id, name, activityAction }) => ({ id, diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index e77d9557f9ae..ba7313c62eb4 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -549,11 +549,11 @@ export class CompositeActionViewItem extends ActivityActionViewItem { })); // Activate on drag over to reveal targets - [this.badge, this.label].forEach(b => new DelayedDragHandler(b, () => { + [this.badge, this.label].forEach(b => this._register(new DelayedDragHandler(b, () => { if (!this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) && !this.getAction().checked) { this.getAction().run(); } - })); + }))); this.updateStyles(); } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 6bc5648cdcff..8333c7422794 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/compositepart'; import * as nls from 'vs/nls'; import { defaultGenerator } from 'vs/base/common/idGenerator'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; @@ -18,13 +18,13 @@ import { IAction } from 'vs/base/common/actions'; import { Part, IPartOptions } from 'vs/workbench/browser/part'; import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; import { IComposite } from 'vs/workbench/common/composite'; -import { ScopedProgressService } from 'vs/workbench/services/progress/browser/progressService'; +import { ScopedProgressService } from 'vs/workbench/services/progress/browser/localProgressService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -50,7 +50,7 @@ export interface ICompositeTitleLabel { interface CompositeItem { composite: Composite; disposable: IDisposable; - progressService: IProgressService; + localProgressService: ILocalProgressService; } export abstract class CompositePart extends Part { @@ -60,8 +60,8 @@ export abstract class CompositePart extends Part { protected toolBar: ToolBar; - private mapCompositeToCompositeContainer: { [compositeId: string]: HTMLElement; }; - private mapActionsBindingToComposite: { [compositeId: string]: () => void; }; + private mapCompositeToCompositeContainer = new Map(); + private mapActionsBindingToComposite = new Map void>(); private activeComposite: Composite | null; private lastActiveCompositeId: string; private instantiatedCompositeItems: Map; @@ -91,8 +91,6 @@ export abstract class CompositePart extends Part { ) { super(id, options, themeService, storageService, layoutService); - this.mapCompositeToCompositeContainer = {}; - this.mapActionsBindingToComposite = {}; this.activeComposite = null; this.instantiatedCompositeItems = new Map(); this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId); @@ -171,17 +169,17 @@ export abstract class CompositePart extends Part { // Instantiate composite from registry otherwise const compositeDescriptor = this.registry.getComposite(id); if (compositeDescriptor) { - const progressService = this.instantiationService.createInstance(ScopedProgressService, this.progressBar, compositeDescriptor.id, isActive); - const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([IProgressService, progressService])); + const localProgressService = this.instantiationService.createInstance(ScopedProgressService, this.progressBar, compositeDescriptor.id, isActive); + const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([ILocalProgressService, localProgressService])); const composite = compositeDescriptor.instantiate(compositeInstantiationService); - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); // Remember as Instantiated - this.instantiatedCompositeItems.set(id, { composite, disposable: toDisposable(() => dispose(disposables)), progressService }); + this.instantiatedCompositeItems.set(id, { composite, disposable: disposables, localProgressService }); // Register to title area update events from the composite - composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId()), this, disposables); + disposables.add(composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId()), this)); return composite; } @@ -206,7 +204,7 @@ export abstract class CompositePart extends Part { this.lastActiveCompositeId = this.activeComposite.getId(); // Composites created for the first time - let compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()]; + let compositeContainer = this.mapCompositeToCompositeContainer.get(composite.getId()); if (!compositeContainer) { // Build Container off-DOM @@ -218,13 +216,7 @@ export abstract class CompositePart extends Part { composite.updateStyles(); // Remember composite container - this.mapCompositeToCompositeContainer[composite.getId()] = compositeContainer; - } - - // Report progress for slow loading composites (but only if we did not create the composites before already) - const compositeItem = this.instantiatedCompositeItems.get(composite.getId()); - if (compositeItem && !compositeContainer) { - compositeItem.progressService.showWhile(Promise.resolve(), this.layoutService.isRestored() ? 800 : 3200 /* less ugly initial startup */); + this.mapCompositeToCompositeContainer.set(composite.getId(), compositeContainer); } // Fill Content and Actions @@ -250,10 +242,10 @@ export abstract class CompositePart extends Part { } // Handle Composite Actions - let actionsBinding = this.mapActionsBindingToComposite[composite.getId()]; + let actionsBinding = this.mapActionsBindingToComposite.get(composite.getId()); if (!actionsBinding) { actionsBinding = this.collectCompositeActions(composite); - this.mapActionsBindingToComposite[composite.getId()] = actionsBinding; + this.mapActionsBindingToComposite.set(composite.getId(), actionsBinding); } actionsBinding(); @@ -306,13 +298,13 @@ export abstract class CompositePart extends Part { // Actions const actionsBinding = this.collectCompositeActions(this.activeComposite); - this.mapActionsBindingToComposite[this.activeComposite.getId()] = actionsBinding; + this.mapActionsBindingToComposite.set(this.activeComposite.getId(), actionsBinding); actionsBinding(); } // Otherwise invalidate actions binding for next time when the composite becomes visible else { - delete this.mapActionsBindingToComposite[compositeId]; + this.mapActionsBindingToComposite.delete(compositeId); } } @@ -366,14 +358,16 @@ export abstract class CompositePart extends Part { const composite = this.activeComposite; this.activeComposite = null; - const compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()]; + const compositeContainer = this.mapCompositeToCompositeContainer.get(composite.getId()); // Indicate to Composite composite.setVisible(false); // Take Container Off-DOM and hide - compositeContainer.remove(); - hide(compositeContainer); + if (compositeContainer) { + compositeContainer.remove(); + hide(compositeContainer); + } // Clear any running Progress this.progressBar.stop().hide(); @@ -462,17 +456,17 @@ export abstract class CompositePart extends Part { return contentContainer; } - getProgressIndicator(id: string): IProgressService | null { + getProgressIndicator(id: string): ILocalProgressService | null { const compositeItem = this.instantiatedCompositeItems.get(id); - return compositeItem ? compositeItem.progressService : null; + return compositeItem ? compositeItem.localProgressService : null; } - protected getActions(): IAction[] { + protected getActions(): ReadonlyArray { return []; } - protected getSecondaryActions(): IAction[] { + protected getSecondaryActions(): ReadonlyArray { return []; } @@ -496,8 +490,8 @@ export abstract class CompositePart extends Part { return false; // do not remove active composite } - delete this.mapCompositeToCompositeContainer[compositeId]; - delete this.mapActionsBindingToComposite[compositeId]; + this.mapCompositeToCompositeContainer.delete(compositeId); + this.mapActionsBindingToComposite.delete(compositeId); const compositeItem = this.instantiatedCompositeItems.get(compositeId); if (compositeItem) { compositeItem.composite.dispose(); @@ -509,8 +503,8 @@ export abstract class CompositePart extends Part { } dispose(): void { - this.mapCompositeToCompositeContainer = null!; // StrictNullOverride: nulling out ok in dispose - this.mapActionsBindingToComposite = null!; // StrictNullOverride: nulling out ok in dispose + this.mapCompositeToCompositeContainer.clear(); + this.mapActionsBindingToComposite.clear(); this.instantiatedCompositeItems.forEach(compositeItem => { compositeItem.composite.dispose(); diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 582e82cd22bf..5df7d489e905 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { isEmptyObject } from 'vs/base/common/types'; import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; +import { MementoObject } from 'vs/workbench/common/memento'; /** * The base class of editors in the workbench. Editors register themselves for specific editor inputs. @@ -177,7 +178,7 @@ export class EditorMemento implements IEditorMemento { constructor( private _id: string, private key: string, - private memento: object, + private memento: MementoObject, private limit: number, private editorGroupService: IEditorGroupsService ) { } diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index 74b9153fd896..6210dabe227c 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -15,11 +15,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ResourceViewerContext, ResourceViewer } from 'vs/workbench/browser/parts/editor/resourceViewer'; import { URI } from 'vs/base/common/uri'; import { Dimension, size, clearNode } from 'vs/base/browser/dom'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { CancellationToken } from 'vs/base/common/cancellation'; import { dispose } from 'vs/base/common/lifecycle'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IFileService } from 'vs/platform/files/common/files'; export interface IOpenCallbacks { openInternal: (input: EditorInput, options: EditorOptions) => Promise; @@ -48,7 +48,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { callbacks: IOpenCallbacks, telemetryService: ITelemetryService, themeService: IThemeService, - @ITextFileService private readonly textFileService: ITextFileService, + @IFileService private readonly fileService: IFileService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IStorageService storageService: IStorageService ) { @@ -89,7 +89,11 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { } // Render Input - this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, this.textFileService, this.binaryContainer, this.scrollbar, { + if (this.resourceViewerContext) { + this.resourceViewerContext.dispose(); + } + + this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, this.fileService, this.binaryContainer, this.scrollbar, { openInternalClb: () => this.handleOpenInternalCallback(input, options), openExternalClb: this.environmentService.configuration.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource), metadataClb: meta => this.handleMetadataChanged(meta) diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index 0bb6a8cf6cfa..54c3a6c30a9d 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -122,7 +122,7 @@ Registry.as(Extensions.Configuration).registerConfigurat 'breadcrumbs.enabled': { description: localize('enabled', "Enable/disable navigation breadcrumbs."), type: 'boolean', - default: false + default: true }, // 'breadcrumbs.useQuickPick': { // description: localize('useQuickPick', "Use quick pick instead of breadcrumb-pickers."), diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index d7ce23e96ad0..425a18d82306 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -384,14 +384,14 @@ export class BreadcrumbsControl { this._breadcrumbsPickerShowing = true; this._updateCkBreadcrumbsActive(); - return combinedDisposable([ + return combinedDisposable( picker, selectListener, focusListener, zoomListener, focusTracker, blurListener - ]); + ); }, getAnchor: () => { let maxInnerWidth = window.innerWidth - 8 /*a little less the full widget*/; @@ -494,17 +494,17 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { category: localize('cmd.category', "View") } }); -// {{SQL CARBON EDIT}} - Disable unused menu item -// MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { -// group: '5_editor', -// order: 99, -// command: { -// id: 'breadcrumbs.toggle', -// title: localize('miToggleBreadcrumbs', "Toggle &&Breadcrumbs"), -// toggled: ContextKeyExpr.equals('config.breadcrumbs.enabled', true) -// } -// }); -// {{SQL CARBON EDIT}} - End +/* {{SQL CARBON EDIT}} - Disable unused menu item +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '5_editor', + order: 3, + command: { + id: 'breadcrumbs.toggle', + title: localize('miShowBreadcrumbs', "Show &&Breadcrumbs"), + toggled: ContextKeyExpr.equals('config.breadcrumbs.enabled', true) + } +}); +*/ CommandsRegistry.registerCommand('breadcrumbs.toggle', accessor => { let config = accessor.get(IConfigurationService); let value = BreadcrumbsConfig.IsEnabled.bindTo(config).getValue(); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index fd4961f880b9..0ff5cd2c7882 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -11,7 +11,7 @@ import { IEditorQuickOpenEntry, IQuickOpenRegistry, Extensions as QuickOpenExten import { StatusbarItemDescriptor, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; -import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext } from 'vs/workbench/common/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'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -52,6 +52,8 @@ import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/edit import { ZoomStatusbarItem } from 'vs/workbench/browser/parts/editor/resourceViewer'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { toLocalResource } from 'vs/base/common/resources'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; // Register String Editor Registry.as(EditorExtensions.Editors).registerEditor( @@ -225,11 +227,17 @@ Registry.as(EditorInputExtensions.EditorInputFactor registerEditorContribution(OpenWorkspaceButtonContribution); // Register Editor Status -const statusBar = Registry.as(StatusExtensions.Statusbar); -statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* towards the left of the right hand side */)); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatus, LifecyclePhase.Ready); // Register Zoom Status -statusBar.registerStatusbarItem(new StatusbarItemDescriptor(ZoomStatusbarItem, StatusbarAlignment.RIGHT, 101 /* to the left of editor status (100) */)); +const statusBar = Registry.as(StatusExtensions.Statusbar); +statusBar.registerStatusbarItem(new StatusbarItemDescriptor( + ZoomStatusbarItem, + 'status.imageZoom', + nls.localize('status.imageZoom', "Image Zoom"), + StatusbarAlignment.RIGHT, + 101 /* to the left of editor status (100) */) +); // Register Status Actions const registry = Registry.as(ActionExtensions.WorkbenchActions); @@ -250,7 +258,7 @@ export class QuickOpenActionContributor extends ActionBarContributor { return !!entry; } - getActions(context: any): IAction[] { + getActions(context: any): ReadonlyArray { const actions: Action[] = []; const entry = this.getEntry(context); @@ -439,11 +447,11 @@ MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: edi // Editor Title Context Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close") }, group: '1_close', order: 10 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOthers', "Close Others") }, group: '1_close', order: 20 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRight', "Close to the Right") }, group: '1_close', order: 30, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOthers', "Close Others"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRight', "Close to the Right"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 30, when: ContextKeyExpr.has('config.workbench.editor.showTabs') }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '1_close', order: 50 }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open") }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); +MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open"), precondition: EditorPinnedContext.toNegated() }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_UP, title: nls.localize('splitUp', "Split Up") }, group: '5_split', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_DOWN, title: nls.localize('splitDown', "Split Down") }, group: '5_split', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_LEFT, title: nls.localize('splitLeft', "Split Left") }, group: '5_split', order: 30 }); diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 2cd9cebdea59..2b3cf2b1c686 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -11,7 +11,7 @@ import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } fr import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; @@ -47,11 +47,11 @@ export class EditorControl extends Disposable { private groupView: IEditorGroupView, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IProgressService progressService: IProgressService + @ILocalProgressService localProgressService: ILocalProgressService ) { super(); - this.editorOperation = this._register(new LongRunningOperation(progressService)); + this.editorOperation = this._register(new LongRunningOperation(localProgressService)); } get activeControl(): IVisibleEditor | null { diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 87a52a54eaf2..7fde637ffc3d 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -88,10 +88,10 @@ class DropOverlay extends Themable { // Overlay contrast border (if any) const activeContrastBorderColor = this.getColor(activeContrastBorder); - this.overlay.style.outlineColor = activeContrastBorderColor; - this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : null; - this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : null; - this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : null; + this.overlay.style.outlineColor = activeContrastBorderColor || ''; + this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : ''; + this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : ''; + this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : ''; } private registerListeners(): void { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 05277917f666..9f9a9f8c552c 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -20,8 +20,8 @@ import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BAC import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; -import { IProgressService } from 'vs/platform/progress/common/progress'; -import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; +import { LocalProgressService } from 'vs/workbench/services/progress/browser/localProgressService'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; @@ -184,7 +184,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const scopedContextKeyService = this._register(this.contextKeyService.createScoped(this.element)); this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( [IContextKeyService, scopedContextKeyService], - [IProgressService, new ProgressService(this.progressBar)] + [ILocalProgressService, new LocalProgressService(this.progressBar)] )); // Context keys @@ -220,6 +220,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private handleGroupContextKeys(contextKeyServcie: IContextKeyService): void { const groupActiveEditorDirtyContextKey = EditorGroupActiveEditorDirtyContext.bindTo(contextKeyServcie); + const groupEditorsCountContext = EditorGroupEditorsCountContext.bindTo(contextKeyServcie); let activeEditorListener: IDisposable; @@ -235,12 +236,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } }; - // Track the active editor and update context key that reflects - // the dirty state of this editor + // Update group contexts based on group changes this._register(this.onDidGroupChange(e => { + + // Track the active editor and update context key that reflects + // the dirty state of this editor if (e.kind === GroupChangeKind.EDITOR_ACTIVE) { observeActiveEditor(); } + + // Group editors count context + groupEditorsCountContext.set(this.count); })); observeActiveEditor(); @@ -285,7 +291,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { }); // Toolbar actions - const removeGroupAction = this._register(new Action(CLOSE_EDITOR_GROUP_COMMAND_ID, localize('closeGroupAction', "Close"), 'close-editor-group', true, () => { this.accessor.removeGroup(this); return Promise.resolve(true); })); + const removeGroupAction = this._register(new Action( + CLOSE_EDITOR_GROUP_COMMAND_ID, + localize('closeGroupAction', "Close"), + 'close-editor-group', + true, + () => { + this.accessor.removeGroup(this); + + return Promise.resolve(true); + })); + const keybinding = this.keybindingService.lookupKeybinding(removeGroupAction.id); containerToolbar.push(removeGroupAction, { icon: true, label: false, keybinding: keybinding ? keybinding.getLabel() : undefined }); } @@ -408,7 +424,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private async restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): Promise { if (this._group.count === 0) { - return Promise.resolve(); // nothing to show + return; // nothing to show } // Determine editor options @@ -421,7 +437,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const activeEditor = this._group.activeEditor; if (!activeEditor) { - return Promise.resolve(); + return; } options.pinned = this._group.isPinned(activeEditor); // preserve pinned state @@ -823,30 +839,34 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private async doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): Promise { // Show in editor control if the active editor changed - let openEditor: IEditor | null = null; + let openEditorPromise: Promise; if (active) { - try { - const result = await this.editorControl.openEditor(editor, options); + openEditorPromise = (async () => { + try { + const result = await this.editorControl.openEditor(editor, options); - // Editor change event - if (result.editorChanged) { - this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_ACTIVE, editor }); - } + // Editor change event + if (result.editorChanged) { + this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_ACTIVE, editor }); + } + + return result.control; + } catch (error) { - openEditor = result.control; - } catch (error) { + // Handle errors but do not bubble them up + this.doHandleOpenEditorError(error, editor, options); - // Handle errors but do not bubble them up - this.doHandleOpenEditorError(error, editor, options); - } + return null; // error: return NULL as result to signal this + } + })(); } else { - openEditor = null; // inactive: return NULL as result to signal this + openEditorPromise = Promise.resolve(null); // inactive: return NULL as result to signal this } // Show in title control after editor control because some actions depend on it this.titleAreaControl.openEditor(editor); - return openEditor; + return openEditorPromise; } private doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): void { @@ -892,7 +912,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Use the first editor as active editor const { editor, options } = editors.shift()!; - let firstEditor = await this.openEditor(editor, options); + let firstOpenedEditor = await this.openEditor(editor, options); // Open the other ones inactive const startingIndex = this.getIndexOfEditor(editor) + 1; @@ -903,12 +923,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { adjustedEditorOptions.index = startingIndex + index; const openedEditor = await this.openEditor(editor, adjustedEditorOptions); - if (!firstEditor) { - firstEditor = openedEditor; // only take if the first editor opening failed + if (!firstOpenedEditor) { + firstOpenedEditor = openedEditor; // only take if the first editor opening failed } })); - return firstEditor; + return firstOpenedEditor; } //#endregion @@ -1108,7 +1128,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private async handleDirty(editors: EditorInput[]): Promise { if (!editors.length) { - return Promise.resolve(false); // no veto + return false; // no veto } const editor = editors.shift()!; @@ -1141,7 +1161,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.accessor.groups.some(groupView => groupView !== this && groupView.group.contains(editor, true /* support side by side */)) || // editor is opened in other group editor instanceof SideBySideEditorInput && this.isOpened(editor.master) // side by side editor master is still opened ) { - return Promise.resolve(false); + return false; } // Switch to editor that we want to handle and confirm to save/revert diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 740f98ead27e..577687e09b70 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -31,6 +31,7 @@ import { IView, orthogonal, LayoutPriority } from 'vs/base/browser/ui/grid/gridv import { onUnexpectedError } from 'vs/base/common/errors'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { MementoObject } from 'vs/workbench/common/memento'; interface IEditorPartUIState { serializedGrid: ISerializedGrid; @@ -118,8 +119,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private _preferredSize: Dimension | undefined; - private workspaceMemento: object; - private globalMemento: object; + private readonly workspaceMemento: MementoObject; + private readonly globalMemento: MementoObject; private _partOptions: IEditorPartOptions; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 32f4cced0177..b39490673e62 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -5,17 +5,16 @@ import 'vs/css!./media/editorstatus'; import * as nls from 'vs/nls'; -import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { format } from 'vs/base/common/strings'; import { extname, basename } from 'vs/base/common/resources'; -import { areFunctions, withNullAsUndefined } from 'vs/base/common/types'; +import { areFunctions, withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; -import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence } from 'vs/editor/common/model'; @@ -25,7 +24,6 @@ import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpa import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor'; import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; @@ -33,7 +31,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; @@ -50,6 +48,8 @@ import { timeout } from 'vs/base/common/async'; import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Event } from 'vs/base/common/event'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; // {{SQL CARBON EDIT}} import { QueryEditorService } from 'sql/workbench/services/queryEditor/browser/queryEditorService'; @@ -277,219 +277,304 @@ const nlsMultiSelectionRange = nls.localize('multiSelectionRange', "{0} selectio const nlsMultiSelection = nls.localize('multiSelection', "{0} selections"); const nlsEOLLF = nls.localize('endOfLineLineFeed', "LF"); const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF"); -const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab Moves Focus"); -const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Optimized"); -const nlsScreenReaderDetectedTitle = nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\"."); -class StatusBarItem { - private _showing = true; +export class EditorStatus extends Disposable implements IWorkbenchContribution { + private tabFocusModeElement: IStatusbarEntryAccessor | null = null; + private screenRedearModeElement: IStatusbarEntryAccessor | null = null; + private indentationElement: IStatusbarEntryAccessor | null = null; + private selectionElement: IStatusbarEntryAccessor | null = null; + private encodingElement: IStatusbarEntryAccessor | null = null; + private eolElement: IStatusbarEntryAccessor | null = null; + private modeElement: IStatusbarEntryAccessor | null = null; + private metadataElement: IStatusbarEntryAccessor | null = null; + + private readonly state = new State(); + private readonly activeEditorListeners: IDisposable[] = []; + private delayedRender: IDisposable | null = null; + private toRender: StateChange | null = null; + private screenReaderNotification: INotificationHandle | null = null; + private promptedScreenReader: boolean = false; constructor( - private readonly element: HTMLElement, - title: string, + @IEditorService private readonly editorService: IEditorService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @IModeService private readonly modeService: IModeService, + @ITextFileService private readonly textFileService: ITextFileService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @INotificationService private readonly notificationService: INotificationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @IStatusbarService private readonly statusbarService: IStatusbarService ) { - this.setVisible(false); - this.element.title = title; + super(); + + this.registerCommands(); + this.registerListeners(); } - set textContent(value: string) { - this.element.textContent = value; + private registerListeners(): void { + this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); + this._register(this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); + this._register(this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange((e.resource)))); + this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange())); } - set onclick(value: () => void) { - this.element.onclick = value; + private registerCommands(): void { + CommandsRegistry.registerCommand({ id: 'showEditorScreenReaderNotification', handler: () => this.showScreenReaderNotification() }); + CommandsRegistry.registerCommand({ id: 'changeEditorIndentation', handler: () => this.showIndentationPicker() }); } - setVisible(shouldShow: boolean): void { - if (shouldShow !== this._showing) { - this._showing = shouldShow; - this.element.style.display = shouldShow ? '' : 'none'; + private showScreenReaderNotification(): void { + if (!this.screenReaderNotification) { + this.screenReaderNotification = this.notificationService.prompt( + Severity.Info, + nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate Azure Data Studio? (Certain features like folding, minimap or word wrap are disabled when using a screen reader)"), + [{ + label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"), + run: () => { + this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER); + } + }, { + label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"), + run: () => { + this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER); + } + }], + { sticky: true } + ); + + Event.once(this.screenReaderNotification.onDidClose)(() => this.screenReaderNotification = null); } } -} -export class EditorStatus implements IStatusbarItem { - private state: State; - private element: HTMLElement; - private tabFocusModeElement: StatusBarItem; - private screenRedearModeElement: StatusBarItem; - private indentationElement: StatusBarItem; - private selectionElement: StatusBarItem; - private encodingElement: StatusBarItem; - private eolElement: StatusBarItem; - private modeElement: StatusBarItem; - private metadataElement: StatusBarItem; - private toDispose: IDisposable[]; - private activeEditorListeners: IDisposable[]; - private delayedRender: IDisposable | null; - private toRender: StateChange | null; - private screenReaderNotification: INotificationHandle | null; + private async showIndentationPicker(): Promise { + const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); + if (!activeTextEditorWidget) { + return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + } - constructor( - @IEditorService private readonly editorService: IEditorService, - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, - @IModeService private readonly modeService: IModeService, - @ITextFileService private readonly textFileService: ITextFileService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @INotificationService private readonly notificationService: INotificationService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService - ) { - this.toDispose = []; - this.activeEditorListeners = []; - this.state = new State(); - } + if (!isWritableCodeEditor(activeTextEditorWidget)) { + return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); + } - render(container: HTMLElement): IDisposable { - this.element = append(container, $('.editor-statusbar-item')); - - this.tabFocusModeElement = new StatusBarItem( - append(this.element, $('a.editor-status-tabfocusmode.status-bar-info')), - nls.localize('disableTabMode', "Disable Accessibility Mode")); - this.tabFocusModeElement.onclick = () => this.onTabFocusModeClick(); - this.tabFocusModeElement.textContent = nlsTabFocusMode; - - this.screenRedearModeElement = new StatusBarItem( - append(this.element, $('a.editor-status-screenreadermode.status-bar-info')), - nlsScreenReaderDetectedTitle); - this.screenRedearModeElement.textContent = nlsScreenReaderDetected; - this.screenRedearModeElement.onclick = () => this.onScreenReaderModeClick(); - - this.selectionElement = new StatusBarItem( - append(this.element, $('a.editor-status-selection')), - nls.localize('gotoLine', "Go to Line")); - this.selectionElement.onclick = () => this.onSelectionClick(); - - this.indentationElement = new StatusBarItem( - append(this.element, $('a.editor-status-indentation')), - nls.localize('selectIndentation', "Select Indentation")); - this.indentationElement.onclick = () => this.onIndentationClick(); - - this.encodingElement = new StatusBarItem( - append(this.element, $('a.editor-status-encoding')), - nls.localize('selectEncoding', "Select Encoding")); - this.encodingElement.onclick = () => this.onEncodingClick(); - - this.eolElement = new StatusBarItem( - append(this.element, $('a.editor-status-eol')), - nls.localize('selectEOL', "Select End of Line Sequence")); - this.eolElement.onclick = () => this.onEOLClick(); - - this.modeElement = new StatusBarItem( - append(this.element, $('a.editor-status-mode')), - nls.localize('selectLanguageMode', "Select Language Mode")); - this.modeElement.onclick = () => this.onModeClick(); - - this.metadataElement = new StatusBarItem( - append(this.element, $('span.editor-status-metadata')), - nls.localize('fileInfo', "File Information")); - - this.delayedRender = null; - this.toRender = null; - - this.toDispose.push( - toDisposable(() => { - if (this.delayedRender) { - this.delayedRender.dispose(); - this.delayedRender = null; + const picks: QuickPickInput[] = [ + activeTextEditorWidget.getAction(IndentUsingSpaces.ID), + activeTextEditorWidget.getAction(IndentUsingTabs.ID), + activeTextEditorWidget.getAction(DetectIndentation.ID), + activeTextEditorWidget.getAction(IndentationToSpacesAction.ID), + activeTextEditorWidget.getAction(IndentationToTabsAction.ID), + activeTextEditorWidget.getAction(TrimTrailingWhitespaceAction.ID) + ].map((a: IEditorAction) => { + return { + id: a.id, + label: a.label, + detail: Language.isDefaultVariant() ? undefined : a.alias, + run: () => { + activeTextEditorWidget.focus(); + a.run(); } - }), - this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()), - this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)), - this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)), - TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()), - ); - - return combinedDisposable(this.toDispose); + }; + }); + + picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") }); + picks.unshift({ type: 'separator', label: nls.localize('indentView', "change view") }); + + const action = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); + return action && action.run(); } - private updateState(update: StateDelta): void { - const changed = this.state.update(update); - if (!changed.hasChanges()) { - // Nothing really changed - return; + private updateTabFocusModeElement(visible: boolean): void { + if (visible) { + if (!this.tabFocusModeElement) { + this.tabFocusModeElement = this.statusbarService.addEntry({ + text: nls.localize('tabFocusModeEnabled', "Tab Moves Focus"), + tooltip: nls.localize('disableTabMode', "Disable Accessibility Mode"), + command: 'editor.action.toggleTabFocusMode' + }, 'status.editor.tabFocusMode', nls.localize('status.editor.tabFocusMode', "Accessibility Mode"), StatusbarAlignment.RIGHT, 100.7); + } + } else { + if (this.tabFocusModeElement) { + this.tabFocusModeElement.dispose(); + this.tabFocusModeElement = null; + } } + } - if (!this.toRender) { - this.toRender = changed; - this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => { - this.delayedRender = null; - const toRender = this.toRender; - this.toRender = null; - if (toRender) { - this._renderNow(toRender); - } - }); + private updateScreenReaderModeElement(visible: boolean): void { + if (visible) { + if (!this.screenRedearModeElement) { + this.screenRedearModeElement = this.statusbarService.addEntry({ + text: nls.localize('screenReaderDetected', "Screen Reader Optimized"), + tooltip: nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\"."), + command: 'showEditorScreenReaderNotification' + }, 'status.editor.screenReaderMode', nls.localize('status.editor.screenReaderMode', "Screen Reader Mode"), StatusbarAlignment.RIGHT, 100.6); + } } else { - this.toRender.combine(changed); + if (this.screenRedearModeElement) { + this.screenRedearModeElement.dispose(); + this.screenRedearModeElement = null; + } } } - private _renderNow(changed: StateChange): void { - if (changed.tabFocusMode) { - this.tabFocusModeElement.setVisible(!!this.state.tabFocusMode); - } + private updateSelectionElement(text: string | undefined): void { + if (!text) { + if (this.selectionElement) { + dispose(this.selectionElement); + this.selectionElement = null; + } - if (changed.screenReaderMode) { - this.screenRedearModeElement.setVisible(!!this.state.screenReaderMode); + return; } - if (changed.indentation) { - if (this.state.indentation) { - this.indentationElement.textContent = this.state.indentation; - this.indentationElement.setVisible(true); - } else { - this.indentationElement.setVisible(false); + const props = { + text, + tooltip: nls.localize('gotoLine', "Go to Line"), + command: 'workbench.action.gotoLine' + }; + + this.selectionElement = this.updateElement(this.selectionElement, props, 'status.editor.selection', nls.localize('status.editor.selection', "Editor Selection"), StatusbarAlignment.RIGHT, 100.5); + } + + private updateIndentationElement(text: string | undefined): void { + if (!text) { + if (this.indentationElement) { + dispose(this.indentationElement); + this.indentationElement = null; } + + return; } - if (changed.selectionStatus) { - if (this.state.selectionStatus && !this.state.screenReaderMode) { - this.selectionElement.textContent = this.state.selectionStatus; - this.selectionElement.setVisible(true); - } else { - this.selectionElement.setVisible(false); + const props = { + text, + tooltip: nls.localize('selectIndentation', "Select Indentation"), + command: 'changeEditorIndentation' + }; + + this.indentationElement = this.updateElement(this.indentationElement, props, 'status.editor.indentation', nls.localize('status.editor.indentation', "Editor Indentation"), StatusbarAlignment.RIGHT, 100.4); + } + + private updateEncodingElement(text: string | undefined): void { + if (!text) { + if (this.encodingElement) { + dispose(this.encodingElement); + this.encodingElement = null; } + + return; } - if (changed.encoding) { - if (this.state.encoding) { - this.encodingElement.textContent = this.state.encoding; - this.encodingElement.setVisible(true); - } else { - this.encodingElement.setVisible(false); + const props = { + text, + tooltip: nls.localize('selectEncoding', "Select Encoding"), + command: 'workbench.action.editor.changeEncoding' + }; + + this.encodingElement = this.updateElement(this.encodingElement, props, 'status.editor.encoding', nls.localize('status.editor.encoding', "Editor Encoding"), StatusbarAlignment.RIGHT, 100.3); + } + + private updateEOLElement(text: string | undefined): void { + if (!text) { + if (this.eolElement) { + dispose(this.eolElement); + this.eolElement = null; } + + return; } - if (changed.EOL) { - if (this.state.EOL) { - this.eolElement.textContent = this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF; - this.eolElement.setVisible(true); - } else { - this.eolElement.setVisible(false); + const props = { + text, + tooltip: nls.localize('selectEOL', "Select End of Line Sequence"), + command: 'workbench.action.editor.changeEOL' + }; + + this.eolElement = this.updateElement(this.eolElement, props, 'status.editor.eol', nls.localize('status.editor.eol', "Editor End of Line"), StatusbarAlignment.RIGHT, 100.2); + } + + private updateModeElement(text: string | undefined): void { + if (!text) { + if (this.modeElement) { + dispose(this.modeElement); + this.modeElement = null; } + + return; } - if (changed.mode) { - if (this.state.mode) { - this.modeElement.textContent = this.state.mode; - this.modeElement.setVisible(true); - } else { - this.modeElement.setVisible(false); + const props = { + text, + tooltip: nls.localize('selectLanguageMode', "Select Language Mode"), + command: 'workbench.action.editor.changeLanguageMode' + }; + + this.modeElement = this.updateElement(this.modeElement, props, 'status.editor.mode', nls.localize('status.editor.mode', "Editor Language"), StatusbarAlignment.RIGHT, 100.1); + + } + + private updateMetadataElement(text: string | undefined): void { + if (!text) { + if (this.metadataElement) { + dispose(this.metadataElement); + this.metadataElement = null; } + + return; } - if (changed.metadata) { - if (this.state.metadata) { - this.metadataElement.textContent = this.state.metadata; - this.metadataElement.setVisible(true); - } else { - this.metadataElement.setVisible(false); - } + const props = { + text, + tooltip: nls.localize('fileInfo', "File Information") + }; + + this.metadataElement = this.updateElement(this.metadataElement, props, 'status.editor.info', nls.localize('status.editor.info', "File Information"), StatusbarAlignment.RIGHT, 100); + + } + + private updateElement(element: IStatusbarEntryAccessor | null, props: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor | null { + if (!element) { + element = this.statusbarService.addEntry(props, id, name, alignment, priority); + } else { + element.update(props); + } + + return element; + } + + private updateState(update: StateDelta): void { + const changed = this.state.update(update); + if (!changed.hasChanges()) { + return; // Nothing really changed + } + + if (!this.toRender) { + this.toRender = changed; + + this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => { + this.delayedRender = null; + const toRender = this.toRender; + this.toRender = null; + if (toRender) { + this.doRenderNow(toRender); + } + }); + } else { + this.toRender.combine(changed); } } + private doRenderNow(changed: StateChange): void { + this.updateTabFocusModeElement(!!this.state.tabFocusMode); + this.updateScreenReaderModeElement(!!this.state.screenReaderMode); + this.updateIndentationElement(this.state.indentation); + this.updateSelectionElement(this.state.selectionStatus && !this.state.screenReaderMode ? this.state.selectionStatus : undefined); + this.updateEncodingElement(this.state.encoding); + this.updateEOLElement(this.state.EOL ? this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF : undefined); + this.updateModeElement(this.state.mode); + this.updateMetadataElement(this.state.metadata); + } + private getSelectionLabel(info: IEditorSelectionStatus): string | undefined { if (!info || !info.selections) { return undefined; @@ -514,66 +599,6 @@ export class EditorStatus implements IStatusbarItem { return undefined; } - private onModeClick(): void { - const action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL); - - action.run(); - action.dispose(); - } - - private onIndentationClick(): void { - const action = this.instantiationService.createInstance(ChangeIndentationAction, ChangeIndentationAction.ID, ChangeIndentationAction.LABEL); - action.run(); - action.dispose(); - } - - private onScreenReaderModeClick(): void { - if (!this.screenReaderNotification) { - this.screenReaderNotification = this.notificationService.prompt( - Severity.Info, - nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate Azure Data Studio? (Certain features like folding, minimap or word wrap are disabled when using a screen reader)"), - [{ - label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"), - run: () => { - this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER); - } - }, { - label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"), - run: () => { - this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER); - } - }], - { sticky: true } - ); - - Event.once(this.screenReaderNotification.onDidClose)(() => { - this.screenReaderNotification = null; - }); - } - } - - private onSelectionClick(): void { - this.quickOpenService.show(':'); // "Go to line" - } - - private onEOLClick(): void { - const action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL); - - action.run(); - action.dispose(); - } - - private onEncodingClick(): void { - const action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL); - - action.run(); - action.dispose(); - } - - private onTabFocusModeClick(): void { - TabFocus.setTabFocusMode(false); - } - private updateStatusBar(): void { const activeControl = this.editorService.activeControl; const activeCodeEditor = activeControl ? withNullAsUndefined(getCodeEditor(activeControl.getControl())) : undefined; @@ -667,7 +692,6 @@ export class EditorStatus implements IStatusbarItem { if (editorWidget) { const textModel = editorWidget.getModel(); if (textModel) { - // Compute mode const modeId = textModel.getLanguageIdentifier().language; info = { mode: this.modeService.getLanguageName(modeId) || undefined }; } @@ -704,8 +728,6 @@ export class EditorStatus implements IStatusbarItem { this.updateState(update); } - private promptedScreenReader: boolean = false; - private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void { let screenReaderMode = false; @@ -715,12 +737,9 @@ export class EditorStatus implements IStatusbarItem { if (screenReaderDetected) { const screenReaderConfiguration = this.configurationService.getValue('editor').accessibilitySupport; if (screenReaderConfiguration === 'auto') { - // show explanation if (!this.promptedScreenReader) { this.promptedScreenReader = true; - setTimeout(() => { - this.onScreenReaderModeClick(); - }, 100); + setTimeout(() => this.showScreenReaderNotification(), 100); } } } @@ -736,7 +755,7 @@ export class EditorStatus implements IStatusbarItem { } private onSelectionChange(editorWidget: ICodeEditor | undefined): void { - const info: IEditorSelectionStatus = {}; + const info: IEditorSelectionStatus = Object.create(null); // We only support text based editors if (editorWidget) { @@ -830,6 +849,15 @@ export class EditorStatus implements IStatusbarItem { return !!activeControl && activeControl === control; } + + dispose(): void { + super.dispose(); + + if (this.delayedRender) { + this.delayedRender.dispose(); + this.delayedRender = null; + } + } } function isWritableCodeEditor(codeEditor: ICodeEditor | undefined): boolean { @@ -899,7 +927,7 @@ export class ChangeModeAction extends Action { // Compute mode let currentModeId: string | undefined; - let modeId: string; + let modeId: string | undefined; if (textModel) { modeId = textModel.getLanguageIdentifier().language; currentModeId = this.modeService.getLanguageName(modeId) || undefined; @@ -939,9 +967,9 @@ export class ChangeModeAction extends Action { } // Offer action to configure via settings - let configureModeAssociations: IQuickPickItem; - let configureModeSettings: IQuickPickItem; - let galleryAction: Action; + let configureModeAssociations: IQuickPickItem | undefined; + let configureModeSettings: IQuickPickItem | undefined; + let galleryAction: Action | undefined; if (hasLanguageSupport && resource) { const ext = extname(resource) || basename(resource); @@ -965,61 +993,60 @@ export class ChangeModeAction extends Action { picks.unshift(autoDetectMode); } - return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => { - if (!pick) { - return; - } + const pick = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }); + if (!pick) { + return; + } - if (pick === galleryAction) { - galleryAction.run(); - return; - } + if (pick === galleryAction) { + galleryAction.run(); + return; + } - // User decided to permanently configure associations, return right after - if (pick === configureModeAssociations) { - if (resource) { - this.configureFileAssociation(resource); - } - return; + // User decided to permanently configure associations, return right after + if (pick === configureModeAssociations) { + if (resource) { + this.configureFileAssociation(resource); } + return; + } - // User decided to configure settings for current language - if (pick === configureModeSettings) { - this.preferencesService.configureSettingsForLanguage(modeId); - return; - } + // User decided to configure settings for current language + if (pick === configureModeSettings) { + this.preferencesService.configureSettingsForLanguage(withUndefinedAsNull(modeId)); + return; + } - // Change mode for active editor - const activeEditor = this.editorService.activeControl; // {{SQL CARBON EDIT}} @anthonydresser change to activeControl from active editor - if (activeEditor) { - const modeSupport = toEditorWithModeSupport(activeEditor.input); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly - if (modeSupport) { - - // Find mode - let languageSelection: ILanguageSelection | undefined; - if (pick === autoDetectMode) { - if (textModel) { - const resource = toResource(activeEditor.input, { supportSideBySide: SideBySideEditor.MASTER }); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly - if (resource) { - languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); - } + // Change mode for active editor + const activeEditor = this.editorService.activeControl; // {{SQL CARBON EDIT}} @anthonydresser change to activeControl from active editor + if (activeEditor) { + const modeSupport = toEditorWithModeSupport(activeEditor.input); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly + if (modeSupport) { + + // Find mode + let languageSelection: ILanguageSelection | undefined; + if (pick === autoDetectMode) { + if (textModel) { + const resource = toResource(activeEditor.input, { supportSideBySide: SideBySideEditor.MASTER }); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly + if (resource) { + languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); } - } else { - languageSelection = this.modeService.createByLanguageName(pick.label); } + } else { + languageSelection = this.modeService.createByLanguageName(pick.label); + } - // {{SQL CARBON EDIT}} @anthonydresser preform a check before we actuall set the mode - // Change mode - if (typeof languageSelection !== 'undefined') { - QueryEditorService.sqlLanguageModeCheck(textModel, languageSelection, activeEditor).then(newTextModel => { - if (newTextModel) { - modeSupport.setMode(languageSelection.languageIdentifier.language); - } - }); - } + // {{SQL CARBON EDIT}} @anthonydresser preform a check before we actuall set the mode + // Change mode + if (typeof languageSelection !== 'undefined') { + QueryEditorService.sqlLanguageModeCheck(textModel, languageSelection, activeEditor).then(newTextModel => { + if (newTextModel) { + modeSupport.setMode(languageSelection.languageIdentifier.language); + } + }); } } - }); + } } private configureFileAssociation(resource: URI): void { @@ -1070,57 +1097,6 @@ export interface IChangeEOLEntry extends IQuickPickItem { eol: EndOfLineSequence; } -class ChangeIndentationAction extends Action { - - static readonly ID = 'workbench.action.editor.changeIndentation'; - static readonly LABEL = nls.localize('changeIndentation', "Change Indentation"); - - constructor( - actionId: string, - actionLabel: string, - @IEditorService private readonly editorService: IEditorService, - @IQuickInputService private readonly quickInputService: IQuickInputService - ) { - super(actionId, actionLabel); - } - - async run(): Promise { - const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); - if (!activeTextEditorWidget) { - return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); - } - - if (!isWritableCodeEditor(activeTextEditorWidget)) { - return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); - } - - const picks: QuickPickInput[] = [ - activeTextEditorWidget.getAction(IndentUsingSpaces.ID), - activeTextEditorWidget.getAction(IndentUsingTabs.ID), - activeTextEditorWidget.getAction(DetectIndentation.ID), - activeTextEditorWidget.getAction(IndentationToSpacesAction.ID), - activeTextEditorWidget.getAction(IndentationToTabsAction.ID), - activeTextEditorWidget.getAction(TrimTrailingWhitespaceAction.ID) - ].map((a: IEditorAction) => { - return { - id: a.id, - label: a.label, - detail: Language.isDefaultVariant() ? undefined : a.alias, - run: () => { - activeTextEditorWidget.focus(); - a.run(); - } - }; - }); - - picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") }); - picks.unshift({ type: 'separator', label: nls.localize('indentView', "change view") }); - - const action = await this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); - return action && action.run(); - } -} - export class ChangeEOLAction extends Action { static readonly ID = 'workbench.action.editor.changeEOL'; @@ -1182,7 +1158,7 @@ export class ChangeEncodingAction extends Action { super(actionId, actionLabel); } - run(): Promise { + async run(): Promise { if (!getCodeEditor(this.editorService.activeTextEditorWidget)) { return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } @@ -1207,93 +1183,88 @@ export class ChangeEncodingAction extends Action { reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding"), detail: 'Reopen with Encoding' }; } - let pickActionPromise: Promise; + let action: IQuickPickItem; if (encodingSupport instanceof UntitledEditorInput) { - pickActionPromise = Promise.resolve(saveWithEncodingPick); + action = saveWithEncodingPick; } else if (!isWritableBaseEditor(activeControl)) { - pickActionPromise = Promise.resolve(reopenWithEncodingPick); + action = reopenWithEncodingPick; } else { - pickActionPromise = this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); + action = await this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); } - return pickActionPromise.then(action => { - if (!action) { - return undefined; - } + if (!action) { + return; + } - const resource = toResource(activeControl!.input, { supportSideBySide: SideBySideEditor.MASTER }); + await timeout(50); // quick open is sensitive to being opened so soon after another - return timeout(50 /* quick open is sensitive to being opened so soon after another */) - .then(() => { - if (!resource || !this.fileService.canHandleResource(resource)) { - return Promise.resolve(null); // encoding detection only possible for resources the file service can handle - } + const resource = toResource(activeControl!.input, { supportSideBySide: SideBySideEditor.MASTER }); + if (!resource || !this.fileService.canHandleResource(resource)) { + return null; // encoding detection only possible for resources the file service can handle + } - return this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null); - }) - .then((guessedEncoding: string) => { - const isReopenWithEncoding = (action === reopenWithEncodingPick); - - const configuredEncoding = this.textResourceConfigurationService.getValue(withNullAsUndefined(resource), 'files.encoding'); - - let directMatchIndex: number | undefined; - let aliasMatchIndex: number | undefined; - - // All encodings are valid picks - const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS) - .sort((k1, k2) => { - if (k1 === configuredEncoding) { - return -1; - } else if (k2 === configuredEncoding) { - return 1; - } - - return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order; - }) - .filter(k => { - if (k === guessedEncoding && guessedEncoding !== configuredEncoding) { - return false; // do not show encoding if it is the guessed encoding that does not match the configured - } - - return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode - }) - .map((key, index) => { - if (key === encodingSupport.getEncoding()) { - directMatchIndex = index; - } else if (SUPPORTED_ENCODINGS[key].alias === encodingSupport.getEncoding()) { - aliasMatchIndex = index; - } - - return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key }; - }); - - const items = picks.slice() as IQuickPickItem[]; - - // If we have a guessed encoding, show it first unless it matches the configured encoding - if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) { - picks.unshift({ type: 'separator' }); - picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") }); - } + const content = await this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true }); + const guessedEncoding = content.encoding; - return this.quickInputService.pick(picks, { - placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"), - activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1] - }).then(encoding => { - if (!encoding) { - return; - } + const isReopenWithEncoding = (action === reopenWithEncodingPick); - const activeControl = this.editorService.activeControl; - if (!activeControl) { - return; - } + const configuredEncoding = this.textResourceConfigurationService.getValue(withNullAsUndefined(resource), 'files.encoding'); - const encodingSupport = toEditorWithEncodingSupport(activeControl.input); - if (typeof encoding.id !== 'undefined' && encodingSupport && encodingSupport.getEncoding() !== encoding.id) { - encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding - } - }); - }); + let directMatchIndex: number | undefined; + let aliasMatchIndex: number | undefined; + + // All encodings are valid picks + const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS) + .sort((k1, k2) => { + if (k1 === configuredEncoding) { + return -1; + } else if (k2 === configuredEncoding) { + return 1; + } + + return SUPPORTED_ENCODINGS[k1].order - SUPPORTED_ENCODINGS[k2].order; + }) + .filter(k => { + if (k === guessedEncoding && guessedEncoding !== configuredEncoding) { + return false; // do not show encoding if it is the guessed encoding that does not match the configured + } + + return !isReopenWithEncoding || !SUPPORTED_ENCODINGS[k].encodeOnly; // hide those that can only be used for encoding if we are about to decode + }) + .map((key, index) => { + if (key === encodingSupport.getEncoding()) { + directMatchIndex = index; + } else if (SUPPORTED_ENCODINGS[key].alias === encodingSupport.getEncoding()) { + aliasMatchIndex = index; + } + + return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key }; + }); + + const items = picks.slice() as IQuickPickItem[]; + + // If we have a guessed encoding, show it first unless it matches the configured encoding + if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) { + picks.unshift({ type: 'separator' }); + picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") }); + } + + const encoding = await this.quickInputService.pick(picks, { + placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"), + activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1] }); + + if (!encoding) { + return; + } + + if (!this.editorService.activeControl) { + return; + } + + const activeEncodingSupport = toEditorWithEncodingSupport(this.editorService.activeControl.input); + if (typeof encoding.id !== 'undefined' && activeEncodingSupport && activeEncodingSupport.getEncoding() !== encoding.id) { + activeEncodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding + } } } diff --git a/src/vs/workbench/browser/parts/editor/media/editorstatus.css b/src/vs/workbench/browser/parts/editor/media/editorstatus.css index 1f9538dcb300..e19f7e75d811 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorstatus.css +++ b/src/vs/workbench/browser/parts/editor/media/editorstatus.css @@ -3,25 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .editor-statusbar-item > a:not(:first-child) { - margin-left: 5px; -} - -.monaco-workbench .editor-statusbar-item > .editor-status-mode, -.monaco-workbench .editor-statusbar-item > .editor-status-encoding, -.monaco-workbench .editor-statusbar-item > .editor-status-eol, -.monaco-workbench .editor-statusbar-item > .editor-status-selection, -.monaco-workbench .editor-statusbar-item > .editor-status-indentation, -.monaco-workbench .editor-statusbar-item > .editor-status-metadata, -.monaco-workbench .editor-statusbar-item > .editor-status-tabfocusmode, -.monaco-workbench .editor-statusbar-item > .editor-status-screenreadermode { - padding: 0 5px 0 5px; -} - -.monaco-workbench .editor-statusbar-item > .editor-status-metadata { - cursor: default !important; -} - .monaco-workbench .screen-reader-detected-explanation { width: 420px; top: 30px; diff --git a/src/vs/workbench/browser/parts/editor/media/resourceviewer.css b/src/vs/workbench/browser/parts/editor/media/resourceviewer.css index 54be06f243ab..5c68448e81ae 100644 --- a/src/vs/workbench/browser/parts/editor/media/resourceviewer.css +++ b/src/vs/workbench/browser/parts/editor/media/resourceviewer.css @@ -14,19 +14,23 @@ .monaco-resource-viewer.image { padding: 0; - background-position: 0 0, 8px 8px; - background-size: 16px 16px; display: flex; box-sizing: border-box; } -.vs .monaco-resource-viewer.image { +.monaco-resource-viewer.image img { + padding: 0; + background-position: 0 0, 8px 8px; + background-size: 16px 16px; +} + +.vs .monaco-resource-viewer.image img { background-image: linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230)), linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230)); } -.vs-dark .monaco-resource-viewer.image { +.vs-dark .monaco-resource-viewer.image img { background-image: linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20)), linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20)); @@ -54,6 +58,10 @@ cursor: zoom-out; } +.monaco-workbench .part.statusbar > .items-container > .statusbar-item > a.zoom-statusbar-item { + padding: 0 5px 0 5px; +} + .monaco-resource-viewer .embedded-link, .monaco-resource-viewer .embedded-link:hover { cursor: pointer; diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 2d81485130d6..96313c932185 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -47,7 +47,7 @@ export class NoTabsTitleControl extends TitleControl { // Breadcrumbs this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent }); toggleClass(this.titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl)); - this.toDispose.push({ dispose: () => removeClass(this.titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node + this._register({ dispose: () => removeClass(this.titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node // Right Actions Container const actionsContainer = document.createElement('div'); diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index ec9bb061ff3e..c37f95c3cc98 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -15,13 +15,13 @@ import { clamp } from 'vs/base/common/numbers'; import { Themable } from 'vs/workbench/common/theme'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { memoize } from 'vs/base/common/decorators'; import * as platform from 'vs/base/common/platform'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IFileService } from 'vs/platform/files/common/files'; export interface IResourceDescriptor { readonly resource: URI; @@ -78,7 +78,7 @@ export class ResourceViewer { static show( descriptor: IResourceDescriptor, - textFileService: ITextFileService, + fileService: IFileService, container: HTMLElement, scrollbar: DomScrollableElement, delegate: ResourceViewerDelegate @@ -89,7 +89,7 @@ export class ResourceViewer { // Images if (ResourceViewer.isImageResource(descriptor)) { - return ImageView.create(container, descriptor, textFileService, scrollbar, delegate); + return ImageView.create(container, descriptor, fileService, scrollbar, delegate); } // Large Files @@ -118,12 +118,12 @@ class ImageView { static create( container: HTMLElement, descriptor: IResourceDescriptor, - textFileService: ITextFileService, + fileService: IFileService, scrollbar: DomScrollableElement, delegate: ResourceViewerDelegate ): ResourceViewerContext { if (ImageView.shouldShowImageInline(descriptor)) { - return InlineImageView.create(container, descriptor, textFileService, scrollbar, delegate); + return InlineImageView.create(container, descriptor, fileService, scrollbar, delegate); } return LargeImageView.create(container, descriptor, delegate); @@ -160,7 +160,7 @@ class LargeImageView { DOM.clearNode(container); - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const label = document.createElement('p'); label.textContent = nls.localize('largeImageError', "The image is not displayed in the editor because it is too large ({0}).", size); @@ -172,10 +172,10 @@ class LargeImageView { link.setAttribute('role', 'button'); link.textContent = nls.localize('resourceOpenExternalButton', "Open image using external program?"); - disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openExternal(descriptor.resource))); + disposables.add(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openExternal(descriptor.resource))); } - return combinedDisposable(disposables); + return disposables; } } @@ -212,7 +212,7 @@ class FileSeemsBinaryFileView { DOM.clearNode(container); - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const label = document.createElement('p'); label.textContent = nls.localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding."); @@ -223,12 +223,12 @@ class FileSeemsBinaryFileView { link.setAttribute('role', 'button'); link.textContent = nls.localize('openAsText', "Do you want to open it anyway?"); - disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => delegate.openInternalClb(descriptor.resource))); + disposables.add(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => delegate.openInternalClb(descriptor.resource))); } scrollbar.scanDomNode(); - return combinedDisposable(disposables); + return disposables; } } @@ -359,15 +359,15 @@ class InlineImageView { static create( container: HTMLElement, descriptor: IResourceDescriptor, - textFileService: ITextFileService, + fileService: IFileService, scrollbar: DomScrollableElement, delegate: ResourceViewerDelegate ) { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const context: ResourceViewerContext = { layout(dimension: DOM.Dimension) { }, - dispose: () => combinedDisposable(disposables).dispose() + dispose: () => disposables.dispose() }; const cacheKey = `${descriptor.resource.toString()}:${descriptor.etag}`; @@ -436,7 +436,7 @@ class InlineImageView { updateScale(scale); } - disposables.push(DOM.addDisposableListener(window, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { + disposables.add(DOM.addDisposableListener(window, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { if (!image) { return; } @@ -449,7 +449,7 @@ class InlineImageView { } })); - disposables.push(DOM.addDisposableListener(window, DOM.EventType.KEY_UP, (e: KeyboardEvent) => { + disposables.add(DOM.addDisposableListener(window, DOM.EventType.KEY_UP, (e: KeyboardEvent) => { if (!image) { return; } @@ -463,7 +463,7 @@ class InlineImageView { } })); - disposables.push(DOM.addDisposableListener(container, DOM.EventType.CLICK, (e: MouseEvent) => { + disposables.add(DOM.addDisposableListener(container, DOM.EventType.CLICK, (e: MouseEvent) => { if (!image) { return; } @@ -496,7 +496,7 @@ class InlineImageView { } })); - disposables.push(DOM.addDisposableListener(container, DOM.EventType.WHEEL, (e: WheelEvent) => { + disposables.add(DOM.addDisposableListener(container, DOM.EventType.WHEEL, (e: WheelEvent) => { if (!image) { return; } @@ -518,7 +518,7 @@ class InlineImageView { updateScale(scale as number * (1 - delta * InlineImageView.SCALE_PINCH_FACTOR)); })); - disposables.push(DOM.addDisposableListener(container, DOM.EventType.SCROLL, () => { + disposables.add(DOM.addDisposableListener(container, DOM.EventType.SCROLL, () => { if (!image || !image.parentElement || scale === 'fit') { return; } @@ -536,7 +536,7 @@ class InlineImageView { image = DOM.append(container, DOM.$('img.scale-to-fit')); image.style.visibility = 'hidden'; - disposables.push(DOM.addDisposableListener(image, DOM.EventType.LOAD, e => { + disposables.add(DOM.addDisposableListener(image, DOM.EventType.LOAD, e => { if (!image) { return; } @@ -557,25 +557,29 @@ class InlineImageView { } })); - InlineImageView.imageSrc(descriptor, textFileService).then(dataUri => { - const imgs = container.getElementsByTagName('img'); - if (imgs.length) { - imgs[0].src = dataUri; + InlineImageView.imageSrc(descriptor, fileService).then(src => { + const img = container.querySelector('img'); + if (img) { + if (typeof src === 'string') { + img.src = src; + } else { + const url = URL.createObjectURL(src); + disposables.add(toDisposable(() => URL.revokeObjectURL(url))); + img.src = url; + } } }); return context; } - private static async imageSrc(descriptor: IResourceDescriptor, textFileService: ITextFileService): Promise { + private static async imageSrc(descriptor: IResourceDescriptor, fileService: IFileService): Promise { if (descriptor.resource.scheme === Schemas.data) { - return Promise.resolve(descriptor.resource.toString(true /* skip encoding */)); + return descriptor.resource.toString(true /* skip encoding */); } - const data = await textFileService.read(descriptor.resource, { encoding: 'base64' }); - const mime = getMime(descriptor); - - return `data:${mime};base64,${data.value}`; + const { value } = await fileService.readFile(descriptor.resource); + return new Blob([value.buffer], { type: getMime(descriptor) }); } } diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index f4411f624cb8..4cbb73c524c7 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -169,7 +169,7 @@ export class SideBySideEditor extends BaseEditor { } if (!this.detailsEditor || !this.masterEditor) { - return Promise.resolve(); + return; } await Promise.all([ @@ -213,8 +213,6 @@ export class SideBySideEditor extends BaseEditor { this.detailsEditor.setInput(detailsInput, null, token), this.masterEditor.setInput(masterInput, options, token)] ); - - return this.focus(); } updateStyles(): void { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 933fdfe71236..be4e9ab2fe9d 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -20,13 +20,12 @@ 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 { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; -// {{SQL CARBON EDIT}} -- Display the editor's tab color -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; -import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; +import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; // {{SQL CARBON EDIT}} -- Display the editor's tab color +import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; @@ -118,7 +117,7 @@ export class TabsTitleControl extends TitleControl { this.registerTabsContainerListeners(); // Tabs Scrollbar - this.tabsScrollbar = this.createTabsScrollbar(this.tabsContainer); + this.tabsScrollbar = this._register(this.createTabsScrollbar(this.tabsContainer)); tabsAndActionsContainer.appendChild(this.tabsScrollbar.getDomNode()); // Editor Toolbar Container @@ -465,13 +464,13 @@ export class TabsTitleControl extends TitleControl { // Eventing const eventsDisposable = this.registerTabListeners(tabContainer, index); - this.tabDisposeables.push(combinedDisposable([eventsDisposable, tabActionBar, tabActionRunner, editorLabel])); + this.tabDisposeables.push(combinedDisposable(eventsDisposable, tabActionBar, tabActionRunner, editorLabel)); return tabContainer; } private registerTabListeners(tab: HTMLElement, index: number): IDisposable { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); const handleClickOrTouch = (e: MouseEvent | GestureEvent): void => { tab.blur(); @@ -507,16 +506,16 @@ export class TabsTitleControl extends TitleControl { }; // Open on Click / Touch - disposables.push(addDisposableListener(tab, EventType.MOUSE_DOWN, (e: MouseEvent) => handleClickOrTouch(e))); - disposables.push(addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e))); + disposables.add(addDisposableListener(tab, EventType.MOUSE_DOWN, (e: MouseEvent) => handleClickOrTouch(e))); + disposables.add(addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e))); // Touch Scroll Support - disposables.push(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => { + disposables.add(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => { this.tabsScrollbar.setScrollPosition({ scrollLeft: this.tabsScrollbar.getScrollPosition().scrollLeft - e.translationX }); })); // Close on mouse middle click - disposables.push(addDisposableListener(tab, EventType.MOUSE_UP, (e: MouseEvent) => { + disposables.add(addDisposableListener(tab, EventType.MOUSE_UP, (e: MouseEvent) => { EventHelper.stop(e); tab.blur(); @@ -530,7 +529,7 @@ export class TabsTitleControl extends TitleControl { })); // Context menu on Shift+F10 - disposables.push(addDisposableListener(tab, EventType.KEY_DOWN, (e: KeyboardEvent) => { + disposables.add(addDisposableListener(tab, EventType.KEY_DOWN, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); if (event.shiftKey && event.keyCode === KeyCode.F10) { showContextMenu(e); @@ -538,12 +537,12 @@ export class TabsTitleControl extends TitleControl { })); // Context menu on touch context menu gesture - disposables.push(addDisposableListener(tab, TouchEventType.Contextmenu, (e: GestureEvent) => { + disposables.add(addDisposableListener(tab, TouchEventType.Contextmenu, (e: GestureEvent) => { showContextMenu(e); })); // Keyboard accessibility - disposables.push(addDisposableListener(tab, EventType.KEY_UP, (e: KeyboardEvent) => { + disposables.add(addDisposableListener(tab, EventType.KEY_UP, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); let handled = false; @@ -588,14 +587,14 @@ export class TabsTitleControl extends TitleControl { })); // Pin on double click - disposables.push(addDisposableListener(tab, EventType.DBLCLICK, (e: MouseEvent) => { + disposables.add(addDisposableListener(tab, EventType.DBLCLICK, (e: MouseEvent) => { EventHelper.stop(e); this.group.pinEditor(this.group.getEditor(index) || undefined); })); // Context menu - disposables.push(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => { + disposables.add(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => { EventHelper.stop(e, true); const input = this.group.getEditor(index); @@ -605,7 +604,7 @@ export class TabsTitleControl extends TitleControl { }, true /* use capture to fix https://github.com/Microsoft/vscode/issues/19145 */)); // Drag support - disposables.push(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => { + disposables.add(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => { const editor = this.group.getEditor(index); if (!editor) { return; @@ -627,7 +626,7 @@ export class TabsTitleControl extends TitleControl { })); // Drop support - disposables.push(new DragAndDropObserver(tab, { + disposables.add(new DragAndDropObserver(tab, { onDragEnter: e => { // Update class to signal drag operation @@ -680,7 +679,7 @@ export class TabsTitleControl extends TitleControl { } })); - return combinedDisposable(disposables); + return disposables; } private isSupportedDropTransfer(e: DragEvent): boolean { @@ -721,10 +720,10 @@ export class TabsTitleControl extends TitleControl { element.style.outlineColor = activeContrastBorderColor; element.style.outlineOffset = isTab ? '-5px' : '-3px'; } else { - element.style.outlineWidth = null; - element.style.outlineStyle = null; - element.style.outlineColor = activeContrastBorderColor; - element.style.outlineOffset = null; + element.style.outlineWidth = ''; + element.style.outlineStyle = ''; + element.style.outlineColor = activeContrastBorderColor || ''; + element.style.outlineOffset = ''; } // {{SQL CARBON EDIT}} -- Display the editor's tab color @@ -872,7 +871,7 @@ export class TabsTitleControl extends TitleControl { // Borders / Outline const borderRightColor = (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)); tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : null; - tabContainer.style.outlineColor = this.getColor(activeContrastBorder); + tabContainer.style.outlineColor = this.getColor(activeContrastBorder) || ''; // Settings const options = this.accessor.partOptions; @@ -930,7 +929,7 @@ export class TabsTitleControl extends TitleControl { // Container addClass(tabContainer, 'active'); tabContainer.setAttribute('aria-selected', 'true'); - tabContainer.style.backgroundColor = this.getColor(TAB_ACTIVE_BACKGROUND); + tabContainer.style.backgroundColor = this.getColor(isGroupActive ? TAB_ACTIVE_BACKGROUND : TAB_UNFOCUSED_ACTIVE_BACKGROUND); const activeTabBorderColorBottom = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER : TAB_UNFOCUSED_ACTIVE_BORDER); if (activeTabBorderColorBottom) { @@ -1296,35 +1295,29 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { adjustedTabDragBackground = editorGroupHeaderTabsBackground.flatten(editorBackgroundColor, editorDragAndDropBackground, editorBackgroundColor, workbenchBackground); } + // Adjust gradient for focused and unfocused hover background + const makeTabHoverBackgroundRule = (color: Color, colorDrag: Color, hasFocus = false) => ` + .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { + background: linear-gradient(to left, ${color}, transparent) !important; + } + + .monaco-workbench .part.editor > .content.dragged-over .editor-group-container${hasFocus ? '.active' : ''} > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { + background: linear-gradient(to left, ${colorDrag}, transparent) !important; + } + `; + // Adjust gradient for (focused) hover background if (tabHoverBackground && adjustedTabBackground && adjustedTabDragBackground) { const adjustedColor = tabHoverBackground.flatten(adjustedTabBackground); const adjustedColorDrag = tabHoverBackground.flatten(adjustedTabDragBackground); - collector.addRule(` - .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { - background: linear-gradient(to left, ${adjustedColor}, transparent) !important; - } - - - .monaco-workbench .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { - background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important; - } - `); + collector.addRule(makeTabHoverBackgroundRule(adjustedColor, adjustedColorDrag, true)); } // Adjust gradient for unfocused hover background if (tabUnfocusedHoverBackground && adjustedTabBackground && adjustedTabDragBackground) { const adjustedColor = tabUnfocusedHoverBackground.flatten(adjustedTabBackground); const adjustedColorDrag = tabUnfocusedHoverBackground.flatten(adjustedTabDragBackground); - collector.addRule(` - .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { - background: linear-gradient(to left, ${adjustedColor}, transparent) !important; - } - - .monaco-workbench .part.editor > .content.dragged-over .editor-group-container > .title .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after { - background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important; - } - `); + collector.addRule(makeTabHoverBackgroundRule(adjustedColor, adjustedColorDrag)); } // Adjust gradient for drag and drop background @@ -1338,20 +1331,31 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } - // Adjust gradient for active tab background + // Adjust gradient for active tab background (focused and unfocused editor groups) + const makeTabActiveBackgroundRule = (color: Color, colorDrag: Color, hasFocus = false) => ` + .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container${hasFocus ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after { + background: linear-gradient(to left, ${color}, transparent); + } + + .monaco-workbench .part.editor > .content.dragged-over .editor-group-container${hasFocus ? '.active' : ':not(.active)'} > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after { + background: linear-gradient(to left, ${colorDrag}, transparent); + } + `; + + // Adjust gradient for unfocused active tab background const tabActiveBackground = theme.getColor(TAB_ACTIVE_BACKGROUND); if (tabActiveBackground && adjustedTabBackground && adjustedTabDragBackground) { const adjustedColor = tabActiveBackground.flatten(adjustedTabBackground); const adjustedColorDrag = tabActiveBackground.flatten(adjustedTabDragBackground); - collector.addRule(` - .monaco-workbench .part.editor > .content:not(.dragged-over) .editor-group-container > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after { - background: linear-gradient(to left, ${adjustedColor}, transparent); - } + collector.addRule(makeTabActiveBackgroundRule(adjustedColor, adjustedColorDrag, true)); + } - .monaco-workbench .part.editor > .content.dragged-over .editor-group-container > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after { - background: linear-gradient(to left, ${adjustedColorDrag}, transparent); - } - `); + // Adjust gradient for unfocused active tab background + const tabUnfocusedActiveBackground = theme.getColor(TAB_UNFOCUSED_ACTIVE_BACKGROUND); + if (tabUnfocusedActiveBackground && adjustedTabBackground && adjustedTabDragBackground) { + const adjustedColor = tabUnfocusedActiveBackground.flatten(adjustedTabBackground); + const adjustedColorDrag = tabUnfocusedActiveBackground.flatten(adjustedTabDragBackground); + collector.addRule(makeTabActiveBackgroundRule(adjustedColor, adjustedColorDrag)); } // Adjust gradient for inactive tab background diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 155fa3c38ecf..9110ced6a34e 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -70,7 +70,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { // Assert Model instance if (!(resolvedModel instanceof BaseTextEditorModel)) { - return Promise.reject(new Error('Unable to open file as text')); + throw new Error('Unable to open file as text'); } // Set Editor Model diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 1cc53647f081..9d98b4d55c40 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -18,7 +18,7 @@ import { localize } from 'vs/nls'; import { createActionViewItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ExecuteCommandAction, IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -33,7 +33,7 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; -import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor } from 'vs/workbench/common/editor'; +import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions, SideBySideEditor, EditorPinnedContext } from 'vs/workbench/common/editor'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { Themable } from 'vs/workbench/common/theme'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -59,6 +59,8 @@ export abstract class TitleControl extends Themable { protected editorActionsToolbar: ToolBar; private resourceContext: ResourceContextKey; + private editorPinnedContext: IContextKey; + private editorToolBarMenuDisposables: IDisposable[] = []; private contextMenu: IMenu; @@ -85,6 +87,8 @@ export abstract class TitleControl extends Themable { super(themeService); this.resourceContext = this._register(instantiationService.createInstance(ResourceContextKey)); + this.editorPinnedContext = EditorPinnedContext.bindTo(contextKeyService); + this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService)); this.create(parent); @@ -221,8 +225,9 @@ export abstract class TitleControl extends Themable { // Dispose previous listeners this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables); - // Update the resource context + // Update contexts this.resourceContext.set(this.group.activeEditor ? withUndefinedAsNull(toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER })) : null); + this.editorPinnedContext.set(this.group.activeEditor ? this.group.isPinned(this.group.activeEditor) : false); // Editor actions require the editor control to be there, so we retrieve it via service const activeControl = this.group.activeControl; @@ -287,9 +292,11 @@ export abstract class TitleControl extends Themable { protected onContextMenu(editor: IEditorInput, e: Event, node: HTMLElement): void { - // Update the resource context - const currentContext = this.resourceContext.get(); + // Update contexts based on editor picked and remember previous to restore + const currentResourceContext = this.resourceContext.get(); this.resourceContext.set(withUndefinedAsNull(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }))); + const currentPinnedContext = !!this.editorPinnedContext.get(); + this.editorPinnedContext.set(this.group.isPinned(editor)); // Find target anchor let anchor: HTMLElement | { x: number, y: number } = node; @@ -310,8 +317,9 @@ export abstract class TitleControl extends Themable { getKeyBinding: (action) => this.getKeybinding(action), onHide: () => { - // restore previous context - this.resourceContext.set(currentContext || null); + // restore previous contexts + this.resourceContext.set(currentResourceContext || null); + this.editorPinnedContext.set(currentPinnedContext); // restore focus to active group this.accessor.activeGroup.focus(); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index ae4a58bdb254..d30922211b75 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -121,12 +121,12 @@ export class ConfigureNotificationAction extends Action { constructor( id: string, label: string, - private _configurationActions: IAction[] + private readonly _configurationActions: ReadonlyArray ) { super(id, label, 'configure-notification-action'); } - get configurationActions(): IAction[] { + get configurationActions(): ReadonlyArray { return this._configurationActions; } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index 50a1a61a8d4f..c91720b6a79f 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -219,7 +219,7 @@ export class NotificationsList extends Themable { this.listContainer.style.background = background ? background.toString() : null; const outlineColor = this.getColor(contrastBorder); - this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : null; + this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : ''; } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index 98c250d6a153..7ef2e80223c1 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -3,16 +3,20 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem } from 'vs/workbench/common/notifications'; +import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem, IStatusMessageChangeEvent, StatusMessageChangeType, IStatusMessageViewItem } from 'vs/workbench/common/notifications'; import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { localize } from 'vs/nls'; export class NotificationsStatus extends Disposable { - private statusItem: IStatusbarEntryAccessor; + + private notificationsCenterStatusItem: IStatusbarEntryAccessor; + private currentNotifications = new Set(); + + private currentStatusMessage: [IStatusMessageViewItem, IDisposable] | undefined; + private isNotificationsCenterVisible: boolean; - private _counter: Set; constructor( private model: INotificationsModel, @@ -20,29 +24,18 @@ export class NotificationsStatus extends Disposable { ) { super(); - this._counter = new Set(); + this.updateNotificationsCenterStatusItem(); - this.updateNotificationsStatusItem(); + if (model.statusMessage) { + this.doSetStatusMessage(model.statusMessage); + } this.registerListeners(); } - private get count(): number { - return this._counter.size; - } - - update(isCenterVisible: boolean): void { - if (this.isNotificationsCenterVisible !== isCenterVisible) { - this.isNotificationsCenterVisible = isCenterVisible; - - // Showing the notification center resets the counter to 0 - this._counter.clear(); - this.updateNotificationsStatusItem(); - } - } - private registerListeners(): void { this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); + this._register(this.model.onDidStatusMessageChange(e => this.onDidStatusMessageChange(e))); } private onDidNotificationChange(e: INotificationChangeEvent): void { @@ -52,29 +45,29 @@ export class NotificationsStatus extends Disposable { // Notification got Added if (e.kind === NotificationChangeType.ADD) { - this._counter.add(e.item); + this.currentNotifications.add(e.item); } // Notification got Removed else if (e.kind === NotificationChangeType.REMOVE) { - this._counter.delete(e.item); + this.currentNotifications.delete(e.item); } - this.updateNotificationsStatusItem(); + this.updateNotificationsCenterStatusItem(); } - private updateNotificationsStatusItem(): void { + private updateNotificationsCenterStatusItem(): void { const statusProperties: IStatusbarEntry = { - text: this.count === 0 ? '$(bell)' : `$(bell) ${this.count}`, + text: this.currentNotifications.size === 0 ? '$(bell)' : `$(bell) ${this.currentNotifications.size}`, command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER, tooltip: this.getTooltip(), showBeak: this.isNotificationsCenterVisible }; - if (!this.statusItem) { - this.statusItem = this.statusbarService.addEntry(statusProperties, StatusbarAlignment.RIGHT, -1000 /* towards the far end of the right hand side */); + if (!this.notificationsCenterStatusItem) { + this.notificationsCenterStatusItem = this.statusbarService.addEntry(statusProperties, 'status.notifications', localize('status.notifications', "Notifications"), StatusbarAlignment.RIGHT, -1000 /* towards the far end of the right hand side */); } else { - this.statusItem.update(statusProperties); + this.notificationsCenterStatusItem.update(statusProperties); } } @@ -87,14 +80,96 @@ export class NotificationsStatus extends Disposable { return localize('zeroNotifications', "No Notifications"); } - if (this.count === 0) { + if (this.currentNotifications.size === 0) { return localize('noNotifications', "No New Notifications"); } - if (this.count === 1) { + if (this.currentNotifications.size === 1) { return localize('oneNotification', "1 New Notification"); } - return localize('notifications', "{0} New Notifications", this.count); + return localize('notifications', "{0} New Notifications", this.currentNotifications.size); + } + + update(isCenterVisible: boolean): void { + if (this.isNotificationsCenterVisible !== isCenterVisible) { + this.isNotificationsCenterVisible = isCenterVisible; + + // Showing the notification center resets the counter to 0 + this.currentNotifications.clear(); + this.updateNotificationsCenterStatusItem(); + } + } + + private onDidStatusMessageChange(e: IStatusMessageChangeEvent): void { + const statusItem = e.item; + + switch (e.kind) { + + // Show status notification + case StatusMessageChangeType.ADD: + this.doSetStatusMessage(statusItem); + + break; + + // Hide status notification (if its still the current one) + case StatusMessageChangeType.REMOVE: + if (this.currentStatusMessage && this.currentStatusMessage[0] === statusItem) { + dispose(this.currentStatusMessage[1]); + this.currentStatusMessage = undefined; + } + + break; + } + } + + private doSetStatusMessage(item: IStatusMessageViewItem): void { + const message = item.message; + + const showAfter = item.options && typeof item.options.showAfter === 'number' ? item.options.showAfter : 0; + const hideAfter = item.options && typeof item.options.hideAfter === 'number' ? item.options.hideAfter : -1; + + // Dismiss any previous + if (this.currentStatusMessage) { + dispose(this.currentStatusMessage[1]); + } + + // Create new + let statusMessageEntry: IStatusbarEntryAccessor; + let showHandle: any = setTimeout(() => { + statusMessageEntry = this.statusbarService.addEntry( + { text: message }, + 'status.message', + localize('status.message', "Status Message"), + StatusbarAlignment.LEFT, + -Number.MAX_VALUE /* far right on left hand side */ + ); + showHandle = null; + }, showAfter); + + // Dispose function takes care of timeouts and actual entry + let hideHandle: any; + const statusMessageDispose = { + dispose: () => { + if (showHandle) { + clearTimeout(showHandle); + } + + if (hideHandle) { + clearTimeout(hideHandle); + } + + if (statusMessageEntry) { + statusMessageEntry.dispose(); + } + } + }; + + if (hideAfter > 0) { + hideHandle = setTimeout(() => statusMessageDispose.dispose(), hideAfter); + } + + // Remember as current status message + this.currentStatusMessage = [item, statusMessageDispose]; } } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 95f418625c82..f52f265e4eb1 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -16,6 +16,7 @@ import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/ import { ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { IActivity } from 'vs/workbench/common/activity'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ActivePanelContext, PanelPositionContext } from 'vs/workbench/common/panel'; export class ClosePanelAction extends Action { @@ -92,8 +93,8 @@ export class TogglePanelPositionAction extends Action { static readonly ID = 'workbench.action.togglePanelPosition'; static readonly LABEL = nls.localize('toggledPanelPosition', "Toggle Panel Position"); - private static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move Panel Right"); - private static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move Panel to Bottom"); + static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move Panel Right"); + static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move Panel to Bottom"); private toDispose: IDisposable[]; @@ -271,16 +272,28 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { group: '2_workbench_layout', command: { id: TogglePanelAction.ID, - title: nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel") + title: nls.localize({ key: 'miShowPanel', comment: ['&& denotes a mnemonic'] }, "Show &&Panel"), + toggled: ActivePanelContext }, order: 5 }); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '2_workbench_layout', + group: '3_workbench_layout_move', command: { id: TogglePanelPositionAction.ID, - title: TogglePanelPositionAction.LABEL + title: TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL }, - order: 3 + when: PanelPositionContext.isEqualTo('bottom'), + order: 5 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '3_workbench_layout_move', + command: { + id: TogglePanelPositionAction.ID, + title: TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL + }, + when: PanelPositionContext.isEqualTo('right'), + order: 5 }); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 06803503dc54..b66891317fd6 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -70,7 +70,7 @@ export class PanelPart extends CompositePart implements IPanelService { private panelFocusContextKey: IContextKey; private compositeBar: CompositeBar; - private compositeActions: { [compositeId: string]: { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null); + private compositeActions: Map = new Map(); private blockOpeningPanel: boolean; private dimension: Dimension; @@ -245,7 +245,7 @@ export class PanelPart extends CompositePart implements IPanelService { .sort((p1, p2) => pinnedCompositeIds.indexOf(p1.id) - pinnedCompositeIds.indexOf(p2.id)); } - protected getActions(): IAction[] { + protected getActions(): ReadonlyArray { return [ this.instantiationService.createInstance(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), this.instantiationService.createInstance(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL) @@ -261,6 +261,11 @@ export class PanelPart extends CompositePart implements IPanelService { } hideActivePanel(): void { + // First check if panel is visible and hide if so + if (this.layoutService.isVisible(Parts.PANEL_PART)) { + this.layoutService.setPanelHidden(true); + } + this.hideActiveComposite(); } @@ -311,13 +316,14 @@ export class PanelPart extends CompositePart implements IPanelService { } private getCompositeActions(compositeId: string): { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } { - let compositeActions = this.compositeActions[compositeId]; + let compositeActions = this.compositeActions.get(compositeId); if (!compositeActions) { compositeActions = { activityAction: this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)), pinnedAction: new ToggleCompositePinnedAction(this.getPanel(compositeId), this.compositeBar) }; - this.compositeActions[compositeId] = compositeActions; + + this.compositeActions.set(compositeId, compositeActions); } return compositeActions; @@ -325,11 +331,11 @@ export class PanelPart extends CompositePart implements IPanelService { protected removeComposite(compositeId: string): boolean { if (super.removeComposite(compositeId)) { - const compositeActions = this.compositeActions[compositeId]; + const compositeActions = this.compositeActions.get(compositeId); if (compositeActions) { compositeActions.activityAction.dispose(); compositeActions.pinnedAction.dispose(); - delete this.compositeActions[compositeId]; + this.compositeActions.delete(compositeId); } return true; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index fb53949b52d4..d884d522ce30 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -27,7 +27,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Emitter, Event } from 'vs/base/common/event'; import { Button } from 'vs/base/browser/ui/button/button'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -97,7 +97,7 @@ type Visibilities = { customButton?: boolean; }; -class QuickInput implements IQuickInput { +class QuickInput extends Disposable implements IQuickInput { private _title: string; private _steps: number; @@ -109,18 +109,17 @@ class QuickInput implements IQuickInput { private _ignoreFocusOut = false; private _buttons: IQuickInputButton[] = []; private buttonsUpdated = false; - private onDidTriggerButtonEmitter = new Emitter(); - private onDidHideEmitter = new Emitter(); + private readonly onDidTriggerButtonEmitter = this._register(new Emitter()); + private readonly onDidHideEmitter = this._register(new Emitter()); - protected visibleDisposables: IDisposable[] = []; - protected disposables: IDisposable[] = [ - this.onDidTriggerButtonEmitter, - this.onDidHideEmitter, - ]; + protected readonly visibleDisposables = this._register(new DisposableStore()); private busyDelay: TimeoutTimer | null; - constructor(protected ui: QuickInputUI) { + constructor( + protected ui: QuickInputUI + ) { + super(); } get title() { @@ -202,7 +201,7 @@ class QuickInput implements IQuickInput { if (this.visible) { return; } - this.visibleDisposables.push( + this.visibleDisposables.add( this.ui.onDidTriggerButton(button => { if (this.buttons.indexOf(button) !== -1) { this.onDidTriggerButtonEmitter.fire(button); @@ -223,7 +222,7 @@ class QuickInput implements IQuickInput { didHide(): void { this.visible = false; - this.visibleDisposables = dispose(this.visibleDisposables); + this.visibleDisposables.clear(); this.onDidHideEmitter.fire(); } @@ -317,7 +316,7 @@ class QuickInput implements IQuickInput { public dispose(): void { this.hide(); - this.disposables = dispose(this.disposables); + super.dispose(); } } @@ -327,9 +326,9 @@ class QuickPick extends QuickInput implements IQuickPi private _value = ''; private _placeholder: string; - private onDidChangeValueEmitter = new Emitter(); - private onDidAcceptEmitter = new Emitter(); - private onDidCustomEmitter = new Emitter(); + private readonly onDidChangeValueEmitter = this._register(new Emitter()); + private readonly onDidAcceptEmitter = this._register(new Emitter()); + private readonly onDidCustomEmitter = this._register(new Emitter()); private _items: Array = []; private itemsUpdated = false; private _canSelectMany = false; @@ -340,12 +339,12 @@ class QuickPick extends QuickInput implements IQuickPi private _activeItems: T[] = []; private activeItemsUpdated = false; private activeItemsToConfirm: T[] | null = []; - private onDidChangeActiveEmitter = new Emitter(); + private readonly onDidChangeActiveEmitter = this._register(new Emitter()); private _selectedItems: T[] = []; private selectedItemsUpdated = false; private selectedItemsToConfirm: T[] | null = []; - private onDidChangeSelectionEmitter = new Emitter(); - private onDidTriggerItemButtonEmitter = new Emitter>(); + private readonly onDidChangeSelectionEmitter = this._register(new Emitter()); + private readonly onDidTriggerItemButtonEmitter = this._register(new Emitter>()); private _valueSelection: Readonly<[number, number]>; private valueSelectionUpdated = true; private _validationMessage: string; @@ -356,17 +355,6 @@ class QuickPick extends QuickInput implements IQuickPi quickNavigate: IQuickNavigateConfiguration; - constructor(ui: QuickInputUI) { - super(ui); - this.disposables.push( - this.onDidChangeValueEmitter, - this.onDidAcceptEmitter, - this.onDidCustomEmitter, - this.onDidChangeActiveEmitter, - this.onDidChangeSelectionEmitter, - this.onDidTriggerItemButtonEmitter, - ); - } get value() { return this._value; @@ -542,7 +530,7 @@ class QuickPick extends QuickInput implements IQuickPi show() { if (!this.visible) { - this.visibleDisposables.push( + this.visibleDisposables.add( this.ui.inputBox.onDidChange(value => { if (value === this.value) { return; @@ -551,105 +539,104 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.list.filter(this.ui.inputBox.value); this.trySelectFirst(); this.onDidChangeValueEmitter.fire(value); - }), - this.ui.inputBox.onMouseDown(event => { - if (!this.autoFocusOnList) { - this.ui.list.clearFocus(); - } - }), - this.ui.inputBox.onKeyDown(event => { - switch (event.keyCode) { - case KeyCode.DownArrow: - this.ui.list.focus('Next'); - if (this.canSelectMany) { - this.ui.list.domFocus(); - } - event.preventDefault(); - break; - case KeyCode.UpArrow: - if (this.ui.list.getFocusedElements().length) { - this.ui.list.focus('Previous'); - } else { - this.ui.list.focus('Last'); - } - if (this.canSelectMany) { - this.ui.list.domFocus(); - } - event.preventDefault(); - break; - case KeyCode.PageDown: - if (this.ui.list.getFocusedElements().length) { - this.ui.list.focus('NextPage'); - } else { - this.ui.list.focus('First'); - } - if (this.canSelectMany) { - this.ui.list.domFocus(); - } - event.preventDefault(); - break; - case KeyCode.PageUp: - if (this.ui.list.getFocusedElements().length) { - this.ui.list.focus('PreviousPage'); - } else { - this.ui.list.focus('Last'); - } - if (this.canSelectMany) { - this.ui.list.domFocus(); - } - event.preventDefault(); - break; - } - }), - this.ui.onDidAccept(() => { - if (!this.canSelectMany && this.activeItems[0]) { - this._selectedItems = [this.activeItems[0]]; - this.onDidChangeSelectionEmitter.fire(this.selectedItems); - } - this.onDidAcceptEmitter.fire(undefined); - }), - this.ui.onDidCustom(() => { - this.onDidCustomEmitter.fire(undefined); - }), - this.ui.list.onDidChangeFocus(focusedItems => { - if (this.activeItemsUpdated) { - return; // Expect another event. - } - if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) { - return; - } - this._activeItems = focusedItems as T[]; - this.onDidChangeActiveEmitter.fire(focusedItems as T[]); - }), - this.ui.list.onDidChangeSelection(selectedItems => { - if (this.canSelectMany) { - if (selectedItems.length) { - this.ui.list.setSelectedElements([]); + })); + this.visibleDisposables.add(this.ui.inputBox.onMouseDown(event => { + if (!this.autoFocusOnList) { + this.ui.list.clearFocus(); + } + })); + this.visibleDisposables.add(this.ui.inputBox.onKeyDown(event => { + switch (event.keyCode) { + case KeyCode.DownArrow: + this.ui.list.focus('Next'); + if (this.canSelectMany) { + this.ui.list.domFocus(); } - return; - } - if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) { - return; - } - this._selectedItems = selectedItems as T[]; - this.onDidChangeSelectionEmitter.fire(selectedItems as T[]); + event.preventDefault(); + break; + case KeyCode.UpArrow: + if (this.ui.list.getFocusedElements().length) { + this.ui.list.focus('Previous'); + } else { + this.ui.list.focus('Last'); + } + if (this.canSelectMany) { + this.ui.list.domFocus(); + } + event.preventDefault(); + break; + case KeyCode.PageDown: + if (this.ui.list.getFocusedElements().length) { + this.ui.list.focus('NextPage'); + } else { + this.ui.list.focus('First'); + } + if (this.canSelectMany) { + this.ui.list.domFocus(); + } + event.preventDefault(); + break; + case KeyCode.PageUp: + if (this.ui.list.getFocusedElements().length) { + this.ui.list.focus('PreviousPage'); + } else { + this.ui.list.focus('Last'); + } + if (this.canSelectMany) { + this.ui.list.domFocus(); + } + event.preventDefault(); + break; + } + })); + this.visibleDisposables.add(this.ui.onDidAccept(() => { + if (!this.canSelectMany && this.activeItems[0]) { + this._selectedItems = [this.activeItems[0]]; + this.onDidChangeSelectionEmitter.fire(this.selectedItems); + } + this.onDidAcceptEmitter.fire(undefined); + })); + this.visibleDisposables.add(this.ui.onDidCustom(() => { + this.onDidCustomEmitter.fire(undefined); + })); + this.visibleDisposables.add(this.ui.list.onDidChangeFocus(focusedItems => { + if (this.activeItemsUpdated) { + return; // Expect another event. + } + if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) { + return; + } + this._activeItems = focusedItems as T[]; + this.onDidChangeActiveEmitter.fire(focusedItems as T[]); + })); + this.visibleDisposables.add(this.ui.list.onDidChangeSelection(selectedItems => { + if (this.canSelectMany) { if (selectedItems.length) { - this.onDidAcceptEmitter.fire(undefined); - } - }), - this.ui.list.onChangedCheckedElements(checkedItems => { - if (!this.canSelectMany) { - return; + this.ui.list.setSelectedElements([]); } - if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) { - return; - } - this._selectedItems = checkedItems as T[]; - this.onDidChangeSelectionEmitter.fire(checkedItems as T[]); - }), - this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent)), - this.registerQuickNavigation() - ); + return; + } + if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) { + return; + } + this._selectedItems = selectedItems as T[]; + this.onDidChangeSelectionEmitter.fire(selectedItems as T[]); + if (selectedItems.length) { + this.onDidAcceptEmitter.fire(undefined); + } + })); + this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => { + if (!this.canSelectMany) { + return; + } + if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) { + return; + } + this._selectedItems = checkedItems as T[]; + this.onDidChangeSelectionEmitter.fire(checkedItems as T[]); + })); + this.visibleDisposables.add(this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent))); + this.visibleDisposables.add(this.registerQuickNavigation()); this.valueSelectionUpdated = true; } super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.) @@ -784,16 +771,8 @@ class InputBox extends QuickInput implements IInputBox { private _prompt: string; private noValidationMessage = InputBox.noPromptMessage; private _validationMessage: string; - private onDidValueChangeEmitter = new Emitter(); - private onDidAcceptEmitter = new Emitter(); - - constructor(ui: QuickInputUI) { - super(ui); - this.disposables.push( - this.onDidValueChangeEmitter, - this.onDidAcceptEmitter, - ); - } + private readonly onDidValueChangeEmitter = this._register(new Emitter()); + private readonly onDidAcceptEmitter = this._register(new Emitter()); get value() { return this._value; @@ -849,22 +828,21 @@ class InputBox extends QuickInput implements IInputBox { this.update(); } - onDidChangeValue = this.onDidValueChangeEmitter.event; + readonly onDidChangeValue = this.onDidValueChangeEmitter.event; - onDidAccept = this.onDidAcceptEmitter.event; + readonly onDidAccept = this.onDidAcceptEmitter.event; show() { if (!this.visible) { - this.visibleDisposables.push( + this.visibleDisposables.add( this.ui.inputBox.onDidChange(value => { if (value === this.value) { return; } this._value = value; this.onDidValueChangeEmitter.fire(value); - }), - this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire(undefined)), - ); + })); + this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire(undefined))); this.valueSelectionUpdated = true; } super.show(); @@ -920,10 +898,10 @@ export class QuickInputService extends Component implements IQuickInputService { private enabled = true; private inQuickOpenWidgets: Record = {}; private inQuickOpenContext: IContextKey; - private contexts: { [id: string]: IContextKey; } = Object.create(null); - private onDidAcceptEmitter = this._register(new Emitter()); - private onDidCustomEmitter = this._register(new Emitter()); - private onDidTriggerButtonEmitter = this._register(new Emitter()); + private contexts: Map> = new Map(); + private readonly onDidAcceptEmitter = this._register(new Emitter()); + private readonly onDidCustomEmitter = this._register(new Emitter()); + private readonly onDidTriggerButtonEmitter = this._register(new Emitter()); private keyMods: Writeable = { ctrlCmd: false, alt: false }; private controller: QuickInput | null = null; @@ -969,11 +947,11 @@ export class QuickInputService extends Component implements IQuickInputService { private setContextKey(id?: string) { let key: IContextKey | undefined; if (id) { - key = this.contexts[id]; + key = this.contexts.get(id); if (!key) { key = new RawContextKey(id, false) .bindTo(this.contextKeyService); - this.contexts[id] = key; + this.contexts.set(id, key); } } @@ -989,11 +967,11 @@ export class QuickInputService extends Component implements IQuickInputService { } private resetContextKeys() { - for (const key in this.contexts) { - if (this.contexts[key].get()) { - this.contexts[key].reset(); + this.contexts.forEach(context => { + if (context.get()) { + context.reset(); } - } + }); } private registerKeyModsListeners() { @@ -1128,6 +1106,9 @@ export class QuickInputService extends Component implements IQuickInputService { this.hide(true); } })); + this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e: FocusEvent) => { + inputBox.setFocus(); + })); this._register(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); switch (event.keyCode) { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts b/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts index 5be7b6a53992..b680d30a8320 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts @@ -8,25 +8,24 @@ import * as dom from 'vs/base/browser/dom'; import { InputBox, IRange, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry'; import { ITheme } from 'vs/platform/theme/common/themeService'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import Severity from 'vs/base/common/severity'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; const $ = dom.$; -export class QuickInputBox { +export class QuickInputBox extends Disposable { private container: HTMLElement; private inputBox: InputBox; - private disposables: IDisposable[] = []; constructor( private parent: HTMLElement ) { + super(); this.container = dom.append(this.parent, $('.quick-input-box')); - this.inputBox = new InputBox(this.container, undefined); - this.disposables.push(this.inputBox); + this.inputBox = this._register(new InputBox(this.container, undefined)); } onKeyDown = (handler: (event: StandardKeyboardEvent) => void): IDisposable => { @@ -129,8 +128,4 @@ export class QuickInputBox { inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder), }); } - - dispose() { - this.disposables = dispose(this.disposables); - } } diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 07fdb8ef9e5c..f369c886534f 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -74,9 +74,9 @@ export class QuickOpenController extends Component implements IQuickOpenService private lastInputValue: string; private lastSubmittedInputValue: string; private quickOpenWidget: QuickOpenWidget; - private mapResolvedHandlersToPrefix: { [prefix: string]: Promise; } = Object.create(null); - private mapContextKeyToContext: { [id: string]: IContextKey; } = Object.create(null); - private handlerOnOpenCalled: { [prefix: string]: boolean; } = Object.create(null); + private mapResolvedHandlersToPrefix: Map> = new Map(); + private mapContextKeyToContext: Map> = new Map(); + private handlerOnOpenCalled: Set = new Set(); private promisesToCompleteOnHide: ValueCallback[] = []; private previousActiveHandlerDescriptor: QuickOpenHandlerDescriptor | null; private actionProvider = new ContributableActionProvider(); @@ -258,13 +258,13 @@ export class QuickOpenController extends Component implements IQuickOpenService this.cancelPendingGetResultsInvocation(); // Pass to handlers - for (let prefix in this.mapResolvedHandlersToPrefix) { - this.mapResolvedHandlersToPrefix[prefix].then(handler => { - this.handlerOnOpenCalled[prefix] = false; + 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) { @@ -294,16 +294,16 @@ export class QuickOpenController extends Component implements IQuickOpenService } private resetQuickOpenContextKeys(): void { - Object.keys(this.mapContextKeyToContext).forEach(k => this.mapContextKeyToContext[k].reset()); + this.mapContextKeyToContext.forEach(context => context.reset()); } private setQuickOpenContextKey(id?: string): void { let key: IContextKey | undefined; if (id) { - key = this.mapContextKeyToContext[id]; + key = this.mapContextKeyToContext.get(id); if (!key) { key = new RawContextKey(id, false).bindTo(this.contextKeyService); - this.mapContextKeyToContext[id] = key; + this.mapContextKeyToContext.set(id, key); } } @@ -454,14 +454,16 @@ export class QuickOpenController extends Component implements IQuickOpenService const previousInput = this.quickOpenWidget.getInput(); const wasShowingHistory = previousInput && previousInput.entries && previousInput.entries.some(e => e instanceof EditorHistoryEntry || e instanceof EditorHistoryEntryGroup); if (wasShowingHistory || matchingHistoryEntries.length > 0) { - if (resolvedHandler.hasShortResponseTime()) { - await timeout(QuickOpenController.MAX_SHORT_RESPONSE_TIME); - } + (async () => { + if (resolvedHandler.hasShortResponseTime()) { + await timeout(QuickOpenController.MAX_SHORT_RESPONSE_TIME); + } - if (!token.isCancellationRequested && !inputSet) { - this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); - inputSet = true; - } + if (!token.isCancellationRequested && !inputSet) { + this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); + inputSet = true; + } + })(); } // Get results @@ -523,7 +525,7 @@ export class QuickOpenController extends Component implements IQuickOpenService const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(placeHolderLabel)], this.actionProvider); this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); - return Promise.resolve(undefined); + return; } // Support extra class from handler @@ -579,38 +581,45 @@ export class QuickOpenController extends Component implements IQuickOpenService return mapEntryToPath; } - private resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { - let result = this._resolveHandler(handler); + private async resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { + let result = this.doResolveHandler(handler); const id = handler.getId(); - if (!this.handlerOnOpenCalled[id]) { + if (!this.handlerOnOpenCalled.has(id)) { const original = result; - this.handlerOnOpenCalled[id] = true; - result = this.mapResolvedHandlersToPrefix[id] = original.then(resolved => { - this.mapResolvedHandlersToPrefix[id] = original; + this.handlerOnOpenCalled.add(id); + result = original.then(resolved => { + this.mapResolvedHandlersToPrefix.set(id, original); resolved.onOpen(); return resolved; }); + + this.mapResolvedHandlersToPrefix.set(id, result); } - return result.then(null, (error) => { - delete this.mapResolvedHandlersToPrefix[id]; + try { + return await result; + } catch (error) { + this.mapResolvedHandlersToPrefix.delete(id); - return Promise.reject(new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`)); - }); + throw new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`); + } } - private _resolveHandler(handler: QuickOpenHandlerDescriptor): Promise { + private doResolveHandler(handler: QuickOpenHandlerDescriptor): Promise { const id = handler.getId(); // Return Cached - if (this.mapResolvedHandlersToPrefix[id]) { - return this.mapResolvedHandlersToPrefix[id]; + if (this.mapResolvedHandlersToPrefix.has(id)) { + return this.mapResolvedHandlersToPrefix.get(id)!; } // Otherwise load and create - return this.mapResolvedHandlersToPrefix[id] = Promise.resolve(handler.instantiate(this.instantiationService)); + const result = Promise.resolve(handler.instantiate(this.instantiationService)); + this.mapResolvedHandlersToPrefix.set(id, result); + + return result; } layout(dimension: Dimension): void { diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index ed5644728e78..8098f4e4af19 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -191,7 +191,7 @@ export class SidebarPart extends CompositePart implements IViewletServi async openViewlet(id: string | undefined, focus?: boolean): Promise { if (typeof id === 'string' && this.getViewlet(id)) { - return Promise.resolve(this.doOpenViewlet(id, focus)); + return this.doOpenViewlet(id, focus); } await this.extensionService.whenInstalledExtensionsRegistered(); diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index 9a7130125d5b..4753dfb10fb6 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -9,20 +9,35 @@ width: 100%; height: 22px; font-size: 12px; + display: flex; } -.monaco-workbench .part.statusbar > .statusbar-item { +.monaco-workbench .part.statusbar > .left-items, +.monaco-workbench .part.statusbar > .right-items { + display: flex; + flex-wrap: wrap; /* individual entries should not shrink since we do not control their content */ +} + +.monaco-workbench .part.statusbar > .right-items { + flex-direction: row-reverse; /* ensures that the most right elements wrap last when space is little */ +} + +.monaco-workbench .part.statusbar > .left-items { + flex-grow: 1; /* left items push right items to the far right end */ +} + +.monaco-workbench .part.statusbar > .items-container > .statusbar-item { display: inline-block; line-height: 22px; height: 100%; vertical-align: top; } -.monaco-workbench .part.statusbar > .statusbar-item.has-beak { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak { position: relative; } -.monaco-workbench .part.statusbar > .statusbar-item.has-beak:before { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { content: ''; position: absolute; left: 11px; @@ -33,66 +48,54 @@ border-right: 5px solid transparent; } -.monaco-workbench .part.statusbar > .statusbar-item.left > :first-child, -.monaco-workbench .part.statusbar > .statusbar-item.right > :first-child -{ +.monaco-workbench .part.statusbar > .items-container > .statusbar-item > :first-child { margin-right: 3px; margin-left: 3px; } -.monaco-workbench .part.statusbar > .statusbar-item.right { - float: right; -} - -/* adding padding to the most left status bar item */ -.monaco-workbench .part.statusbar > .statusbar-item.left:first-child, -.monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.left { - padding-left: 7px; +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.left.first-visible-item { + padding-left: 7px; /* Add padding to the most left status bar item */ } -/* adding padding to the most right status bar item */ -.monaco-workbench .part.statusbar > .statusbar-item.right:first-child { - padding-right: 7px; +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.right.last-visible-item { + padding-right: 7px; /* Add padding to the most right status bar item */ } /* tweak appearance for items with background to improve hover feedback */ -.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.left:first-child, -.monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.has-background-color.left, -.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.right:first-child { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color.left.first-visible-item, +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color.right.last-visible-item { padding-right: 0; padding-left: 0; } -.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.left > :first-child, -.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.right > :first-child -{ +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-background-color > :first-child { margin-right: 0; margin-left: 0; padding-left: 10px; padding-right: 10px; } -.monaco-workbench .part.statusbar > .statusbar-item a { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item a { cursor: pointer; display: inline-block; height: 100%; } -.monaco-workbench .part.statusbar > .statusbar-entry > span { +.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > span { height: 100%; } -.monaco-workbench .part.statusbar > .statusbar-entry > span, -.monaco-workbench .part.statusbar > .statusbar-entry > a { +.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > span, +.monaco-workbench .part.statusbar > .items-container > .statusbar-entry > a { padding: 0 5px 0 5px; white-space: pre; /* gives some degree of styling */ } -.monaco-workbench .part.statusbar > .statusbar-entry span.octicon { +.monaco-workbench .part.statusbar > .items-container > .statusbar-entry span.octicon { text-align: center; font-size: 14px; } -.monaco-workbench .part.statusbar > .statusbar-item a:hover { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { text-decoration: none; } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/statusbar/statusbar.ts b/src/vs/workbench/browser/parts/statusbar/statusbar.ts index 77b24eb01fef..e0151a876dee 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbar.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbar.ts @@ -14,11 +14,21 @@ export interface IStatusbarItem { } export class StatusbarItemDescriptor { - syncDescriptor: SyncDescriptor0; - alignment: StatusbarAlignment; - priority: number; - - constructor(ctor: IConstructorSignature0, alignment?: StatusbarAlignment, priority?: number) { + readonly syncDescriptor: SyncDescriptor0; + readonly id: string; + readonly name: string; + readonly alignment: StatusbarAlignment; + readonly priority: number; + + constructor( + ctor: IConstructorSignature0, + id: string, + name: string, + alignment?: StatusbarAlignment, + priority?: number + ) { + this.id = id; + this.name = name; this.syncDescriptor = createSyncDescriptor(ctor); this.alignment = alignment || StatusbarAlignment.LEFT; this.priority = priority || 0; @@ -26,21 +36,16 @@ export class StatusbarItemDescriptor { } export interface IStatusbarRegistry { + + readonly items: StatusbarItemDescriptor[]; + registerStatusbarItem(descriptor: StatusbarItemDescriptor): void; - items: StatusbarItemDescriptor[]; } class StatusbarRegistry implements IStatusbarRegistry { - private _items: StatusbarItemDescriptor[]; - - constructor() { - this._items = []; - } - - get items(): StatusbarItemDescriptor[] { - return this._items; - } + private readonly _items: StatusbarItemDescriptor[] = []; + get items(): StatusbarItemDescriptor[] { return this._items; } registerStatusbarItem(descriptor: StatusbarItemDescriptor): void { this._items.push(descriptor); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index b54df672bce7..b57445d7abe9 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/statusbarpart'; import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { Registry } from 'vs/platform/registry/common/platform'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -17,28 +17,308 @@ import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiat import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { Action } from 'vs/base/common/actions'; +import { Action, IAction } from 'vs/base/common/actions'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService'; import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { isThemeColor } from 'vs/editor/common/editorCommon'; import { Color } from 'vs/base/common/color'; -import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, clearNode, removeClass } from 'vs/base/browser/dom'; +import { addClass, EventHelper, createStyleSheet, addDisposableListener, addClasses, clearNode, removeClass, EventType, hide, show, removeClasses } from 'vs/base/browser/dom'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { coalesce } from 'vs/base/common/arrays'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { ToggleStatusbarVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; +import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { values } from 'vs/base/common/map'; + +interface IPendingStatusbarEntry { + id: string; + name: string; + entry: IStatusbarEntry; + alignment: StatusbarAlignment; + priority: number; + accessor?: IStatusbarEntryAccessor; +} + +interface IStatusbarViewModelEntry { + id: string; + name: string; + alignment: StatusbarAlignment; + priority: number; + container: HTMLElement; +} -interface PendingEntry { entry: IStatusbarEntry; alignment: StatusbarAlignment; priority: number; accessor?: IStatusbarEntryAccessor; } +class StatusbarViewModel extends Disposable { -export class StatusbarPart extends Part implements IStatusbarService { + private static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden'; + + private readonly _entries: IStatusbarViewModelEntry[] = []; + get entries(): IStatusbarViewModelEntry[] { return this._entries; } + + private hidden: Set; + + constructor(private storageService: IStorageService) { + super(); + + this.restoreState(); + this.registerListeners(); + } + + private restoreState(): void { + const hiddenRaw = this.storageService.get(StatusbarViewModel.HIDDEN_ENTRIES_KEY, StorageScope.GLOBAL); + if (hiddenRaw) { + try { + const hiddenArray: string[] = JSON.parse(hiddenRaw); + this.hidden = new Set(hiddenArray); + } catch (error) { + // ignore parsing errors + } + } + + if (!this.hidden) { + this.hidden = new Set(); + } + } + + private registerListeners(): void { + this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); + } + + private onDidStorageChange(event: IWorkspaceStorageChangeEvent): void { + if (event.key === StatusbarViewModel.HIDDEN_ENTRIES_KEY && event.scope === StorageScope.GLOBAL) { + + // Keep current hidden entries + const currentlyHidden = new Set(this.hidden); + + // Load latest state of hidden entries + this.hidden.clear(); + this.restoreState(); + + const changed = new Set(); + + // Check for each entry that is now visible + currentlyHidden.forEach(id => { + if (!this.hidden.has(id)) { + changed.add(id); + } + }); + + // Check for each entry that is now hidden + this.hidden.forEach(id => { + if (!currentlyHidden.has(id)) { + changed.add(id); + } + }); + + // Update visibility for entries have changed + if (changed.size > 0) { + this._entries.forEach(entry => { + if (changed.has(entry.id)) { + this.updateVisibility(entry.id); + + changed.delete(entry.id); + } + }); + } + } + } + + add(entry: IStatusbarViewModelEntry): IDisposable { + this._entries.push(entry); // intentionally not using a map here since multiple entries can have the same ID! + + // Update visibility directly + this.updateVisibility(entry); + + // Sort according to priority + this.sort(); + + // Mark first/last visible entry + this.markFirstLastVisibleEntry(); + + return toDisposable(() => this.remove(entry)); + } + + private remove(entry: IStatusbarViewModelEntry): void { + const index = this._entries.indexOf(entry); + if (index >= 0) { + this._entries.splice(index, 1); + + // Mark first/last visible entry + this.markFirstLastVisibleEntry(); + } + } + + isHidden(id: string): boolean { + return this.hidden.has(id); + } - _serviceBrand: ServiceIdentifier; + hide(id: string): void { + if (!this.hidden.has(id)) { + this.hidden.add(id); - private static readonly PRIORITY_PROP = 'statusbar-entry-priority'; - private static readonly ALIGNMENT_PROP = 'statusbar-entry-alignment'; + this.updateVisibility(id); + + this.saveState(); + } + } + + show(id: string): void { + if (this.hidden.has(id)) { + this.hidden.delete(id); + + this.updateVisibility(id); + + this.saveState(); + } + } + + findEntry(container: HTMLElement): IStatusbarViewModelEntry | undefined { + for (const entry of this._entries) { + if (entry.container === container) { + return entry; + } + } + + return undefined; + } + + getEntries(alignment: StatusbarAlignment): IStatusbarViewModelEntry[] { + return this._entries.filter(entry => entry.alignment === alignment); + } + + private updateVisibility(id: string): void; + private updateVisibility(entry: IStatusbarViewModelEntry): void; + private updateVisibility(arg1: string | IStatusbarViewModelEntry): void { + + // By identifier + if (typeof arg1 === 'string') { + const id = arg1; + + for (const entry of this._entries) { + if (entry.id === id) { + this.updateVisibility(entry); + } + } + } + + // By entry + else { + const entry = arg1; + const isHidden = this.isHidden(entry.id); + + // Use CSS to show/hide item container + if (isHidden) { + hide(entry.container); + } else { + show(entry.container); + } + + // Mark first/last visible entry + this.markFirstLastVisibleEntry(); + } + } + + private saveState(): void { + if (this.hidden.size > 0) { + this.storageService.store(StatusbarViewModel.HIDDEN_ENTRIES_KEY, JSON.stringify(values(this.hidden)), StorageScope.GLOBAL); + } else { + this.storageService.remove(StatusbarViewModel.HIDDEN_ENTRIES_KEY, StorageScope.GLOBAL); + } + } + + private sort(): void { + this._entries.sort((entryA, entryB) => { + if (entryA.alignment === entryB.alignment) { + return entryB.priority - entryA.priority; // higher priority towards the left + } + + if (entryA.alignment === StatusbarAlignment.LEFT) { + return -1; + } + + if (entryB.alignment === StatusbarAlignment.LEFT) { + return 1; + } + + return 0; + }); + } + + private markFirstLastVisibleEntry(): void { + this.doMarkFirstLastVisibleStatusbarItem(this.getEntries(StatusbarAlignment.LEFT)); + this.doMarkFirstLastVisibleStatusbarItem(this.getEntries(StatusbarAlignment.RIGHT)); + } + + private doMarkFirstLastVisibleStatusbarItem(entries: IStatusbarViewModelEntry[]): void { + let firstVisibleItem: IStatusbarViewModelEntry | undefined; + let lastVisibleItem: IStatusbarViewModelEntry | undefined; + + for (const entry of entries) { + + // Clear previous first + removeClasses(entry.container, 'first-visible-item', 'last-visible-item'); + + const isVisible = !this.isHidden(entry.id); + if (isVisible) { + if (!firstVisibleItem) { + firstVisibleItem = entry; + } + + lastVisibleItem = entry; + } + } + + // Mark: first visible item + if (firstVisibleItem) { + addClass(firstVisibleItem.container, 'first-visible-item'); + } + + // Mark: last visible item + if (lastVisibleItem) { + addClass(lastVisibleItem.container, 'last-visible-item'); + } + } +} + +class ToggleStatusbarEntryVisibilityAction extends Action { + + constructor(id: string, label: string, private model: StatusbarViewModel) { + super(id, label, undefined, true); + + this.checked = !model.isHidden(id); + } + + run(): Promise { + if (this.model.isHidden(this.id)) { + this.model.show(this.id); + } else { + this.model.hide(this.id); + } + + return Promise.resolve(true); + } +} + +class HideStatusbarEntryAction extends Action { + + constructor(id: string, private model: StatusbarViewModel) { + super(id, nls.localize('hide', "Hide"), undefined, true); + } + + run(): Promise { + this.model.hide(this.id); + + return Promise.resolve(true); + } +} + +export class StatusbarPart extends Part implements IStatusbarService { + + _serviceBrand: ServiceIdentifier; //#region IView @@ -49,20 +329,27 @@ export class StatusbarPart extends Part implements IStatusbarService { //#endregion - private statusMessageDispose: IDisposable; private styleElement: HTMLStyleElement; - private pendingEntries: PendingEntry[] = []; + private pendingEntries: IPendingStatusbarEntry[] = []; + + private readonly viewModel: StatusbarViewModel; + + private leftItemsContainer: HTMLElement; + private rightItemsContainer: HTMLElement; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IContextMenuService private contextMenuService: IContextMenuService ) { super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); + this.viewModel = this._register(new StatusbarViewModel(storageService)); + this.registerListeners(); } @@ -70,133 +357,233 @@ export class StatusbarPart extends Part implements IStatusbarService { this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles())); } - addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IStatusbarEntryAccessor { + addEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number = 0): IStatusbarEntryAccessor { // As long as we have not been created into a container yet, record all entries // that are pending so that they can get created at a later point if (!this.element) { - const pendingEntry: PendingEntry = { - entry, alignment, priority - }; - this.pendingEntries.push(pendingEntry); - - const accessor: IStatusbarEntryAccessor = { - update: (entry: IStatusbarEntry) => { - if (pendingEntry.accessor) { - pendingEntry.accessor.update(entry); - } else { - pendingEntry.entry = entry; - } - }, - dispose: () => { - if (pendingEntry.accessor) { - pendingEntry.accessor.dispose(); - } else { - this.pendingEntries = this.pendingEntries.filter(entry => entry !== pendingEntry); - } - } - }; - return accessor; + return this.doAddPendingEntry(entry, id, name, alignment, priority); } - // Render entry in status bar - const el = this.doCreateStatusItem(alignment, priority, ...coalesce(['statusbar-entry', entry.showBeak ? 'has-beak' : undefined])); - const item = this.instantiationService.createInstance(StatusBarEntryItem, el, entry); + // Otherwise add to view + return this.doAddEntry(entry, id, name, alignment, priority); + } - // Insert according to priority - const container = this.element; - const neighbours = this.getEntries(alignment); - let inserted = false; - for (const neighbour of neighbours) { - const nPriority = Number(neighbour.getAttribute(StatusbarPart.PRIORITY_PROP)); - if ( - alignment === StatusbarAlignment.LEFT && nPriority < priority || - alignment === StatusbarAlignment.RIGHT && nPriority > priority - ) { - container.insertBefore(el, neighbour); - inserted = true; - break; + private doAddPendingEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor { + const pendingEntry: IPendingStatusbarEntry = { entry, id, name, alignment, priority }; + this.pendingEntries.push(pendingEntry); + + const accessor: IStatusbarEntryAccessor = { + update: (entry: IStatusbarEntry) => { + if (pendingEntry.accessor) { + pendingEntry.accessor.update(entry); + } else { + pendingEntry.entry = entry; + } + }, + + dispose: () => { + if (pendingEntry.accessor) { + pendingEntry.accessor.dispose(); + } else { + this.pendingEntries = this.pendingEntries.filter(entry => entry !== pendingEntry); + } } - } + }; - if (!inserted) { - container.appendChild(el); - } + return accessor; + } + + private doAddEntry(entry: IStatusbarEntry, id: string, name: string, alignment: StatusbarAlignment, priority: number): IStatusbarEntryAccessor { + + // Create item + const itemContainer = this.doCreateStatusItem(id, name, alignment, priority, ...coalesce(['statusbar-entry', entry.showBeak ? 'has-beak' : undefined])); + const item = this.instantiationService.createInstance(StatusbarEntryItem, itemContainer, entry); + + // Append to parent + this.appendOneStatusbarEntry(itemContainer, alignment, priority); + + // Add to view model + const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer }; + const viewModelEntryDispose = this.viewModel.add(viewModelEntry); return { update: entry => { // Update beak if (entry.showBeak) { - addClass(el, 'has-beak'); + addClass(itemContainer, 'has-beak'); } else { - removeClass(el, 'has-beak'); + removeClass(itemContainer, 'has-beak'); } // Update entry item.update(entry); }, dispose: () => { - el.remove(); + dispose(viewModelEntryDispose); + itemContainer.remove(); dispose(item); } }; } - private getEntries(alignment: StatusbarAlignment): HTMLElement[] { - const entries: HTMLElement[] = []; - - const container = this.element; - const children = container.children; - for (let i = 0; i < children.length; i++) { - const childElement = children.item(i); - if (Number(childElement.getAttribute(StatusbarPart.ALIGNMENT_PROP)) === alignment) { - entries.push(childElement); - } + updateEntryVisibility(id: string, visible: boolean): void { + if (visible) { + this.viewModel.show(id); + } else { + this.viewModel.hide(id); } - - return entries; } createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; - // Fill in initial items that were contributed from the registry + // Left items container + this.leftItemsContainer = document.createElement('div'); + addClasses(this.leftItemsContainer, 'left-items', 'items-container'); + this.element.appendChild(this.leftItemsContainer); + + // Right items container + this.rightItemsContainer = document.createElement('div'); + addClasses(this.rightItemsContainer, 'right-items', 'items-container'); + this.element.appendChild(this.rightItemsContainer); + + // Context menu support + this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e))); + + // Initial status bar entries + this.createInitialStatusbarEntries(); + + return this.element; + } + + private createInitialStatusbarEntries(): void { const registry = Registry.as(Extensions.Statusbar); - const descriptors = registry.items.slice().sort((a, b) => { - if (a.alignment === b.alignment) { - if (a.alignment === StatusbarAlignment.LEFT) { - return b.priority - a.priority; - } else { - return a.priority - b.priority; - } - } else if (a.alignment === StatusbarAlignment.LEFT) { - return 1; - } else if (a.alignment === StatusbarAlignment.RIGHT) { - return -1; - } else { - return 0; - } - }); + // Create initial items that were contributed from the registry + for (const { id, name, alignment, priority, syncDescriptor } of registry.items) { - for (const descriptor of descriptors) { - const item = this.instantiationService.createInstance(descriptor.syncDescriptor); - const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority); + // Create item + const item = this.instantiationService.createInstance(syncDescriptor); + const itemContainer = this.doCreateStatusItem(id, name, alignment, priority); + this._register(item.render(itemContainer)); - this._register(item.render(el)); - this.element.appendChild(el); + // Add to view model + const viewModelEntry: IStatusbarViewModelEntry = { id, name, alignment, priority, container: itemContainer }; + this.viewModel.add(viewModelEntry); } + // Add items in order according to alignment + this.appendAllStatusbarEntries(); + // Fill in pending entries if any while (this.pendingEntries.length) { - const entry = this.pendingEntries.shift(); + const pending = this.pendingEntries.shift(); + if (pending) { + pending.accessor = this.addEntry(pending.entry, pending.id, pending.name, pending.alignment, pending.priority); + } + } + } + + private appendAllStatusbarEntries(): void { + + // Append in order of priority + [ + ...this.viewModel.getEntries(StatusbarAlignment.LEFT), + ...this.viewModel.getEntries(StatusbarAlignment.RIGHT).reverse() // reversing due to flex: row-reverse + ].forEach(entry => { + const target = entry.alignment === StatusbarAlignment.LEFT ? this.leftItemsContainer : this.rightItemsContainer; + + target.appendChild(entry.container); + }); + } + + private appendOneStatusbarEntry(itemContainer: HTMLElement, alignment: StatusbarAlignment, priority: number): void { + const entries = this.viewModel.getEntries(alignment); + + if (alignment === StatusbarAlignment.RIGHT) { + entries.reverse(); // reversing due to flex: row-reverse + } + + const target = alignment === StatusbarAlignment.LEFT ? this.leftItemsContainer : this.rightItemsContainer; + + // find an entry that has lower priority than the new one + // and then insert the item before that one + let appended = false; + for (const entry of entries) { + if ( + alignment === StatusbarAlignment.LEFT && entry.priority < priority || + alignment === StatusbarAlignment.RIGHT && entry.priority > priority // reversing due to flex: row-reverse + ) { + target.insertBefore(itemContainer, entry.container); + appended = true; + break; + } + } + + // Fallback to just appending otherwise + if (!appended) { + target.appendChild(itemContainer); + } + } + + private showContextMenu(e: MouseEvent): void { + EventHelper.stop(e, true); + + const event = new StandardMouseEvent(e); + + let actions: IAction[] | undefined = undefined; + this.contextMenuService.showContextMenu({ + getAnchor: () => ({ x: event.posx, y: event.posy }), + getActions: () => { + actions = this.getContextMenuActions(event); + + return actions; + }, + onHide: () => { + if (actions) { + dispose(actions); + } + } + }); + } + + private getContextMenuActions(event: StandardMouseEvent): IAction[] { + const actions: Action[] = []; + + // Figure out if mouse is over an entry + let statusEntryUnderMouse: IStatusbarViewModelEntry | undefined = undefined; + for (let element: HTMLElement | null = event.target; element; element = element.parentElement) { + const entry = this.viewModel.findEntry(element); if (entry) { - entry.accessor = this.addEntry(entry.entry, entry.alignment, entry.priority); + statusEntryUnderMouse = entry; + break; } } - return this.element; + if (statusEntryUnderMouse) { + actions.push(new HideStatusbarEntryAction(statusEntryUnderMouse.id, this.viewModel)); + actions.push(new Separator()); + } + + // Show an entry per known status entry + // Note: even though entries have an identifier, there can be multiple entries + // having the same identifier (e.g. from extensions). So we make sure to only + // show a single entry per identifier we handled. + const handledEntries = new Set(); + this.viewModel.entries.forEach(entry => { + if (!handledEntries.has(entry.id)) { + actions.push(new ToggleStatusbarEntryVisibilityAction(entry.id, entry.name, this.viewModel)); + handledEntries.add(entry.id); + } + }); + + // Provide an action to hide the status bar at last + actions.push(new Separator()); + actions.push(this.instantiationService.createInstance(ToggleStatusbarVisibilityAction, ToggleStatusbarVisibilityAction.ID, nls.localize('hideStatusBar', "Hide Status Bar"))); + + return actions; } updateStyles(): void { @@ -220,64 +607,25 @@ export class StatusbarPart extends Part implements IStatusbarService { this.styleElement = createStyleSheet(container); } - this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`; + this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`; } - private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0, ...extraClasses: string[]): HTMLElement { - const el = document.createElement('div'); - addClass(el, 'statusbar-item'); + private doCreateStatusItem(id: string, name: string, alignment: StatusbarAlignment, priority: number = 0, ...extraClasses: string[]): HTMLElement { + const itemContainer = document.createElement('div'); + itemContainer.title = name; + + addClass(itemContainer, 'statusbar-item'); if (extraClasses) { - addClasses(el, ...extraClasses); + addClasses(itemContainer, ...extraClasses); } if (alignment === StatusbarAlignment.RIGHT) { - addClass(el, 'right'); + addClass(itemContainer, 'right'); } else { - addClass(el, 'left'); + addClass(itemContainer, 'left'); } - el.setAttribute(StatusbarPart.PRIORITY_PROP, String(priority)); - el.setAttribute(StatusbarPart.ALIGNMENT_PROP, String(alignment)); - - return el; - } - - setStatusMessage(message: string, autoDisposeAfter: number = -1, delayBy: number = 0): IDisposable { - - // Dismiss any previous - dispose(this.statusMessageDispose); - - // Create new - let statusMessageEntry: IStatusbarEntryAccessor; - let showHandle: any = setTimeout(() => { - statusMessageEntry = this.addEntry({ text: message }, StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */); - showHandle = null; - }, delayBy); - let hideHandle: any; - - // Dispose function takes care of timeouts and actual entry - const statusMessageDispose = { - dispose: () => { - if (showHandle) { - clearTimeout(showHandle); - } - - if (hideHandle) { - clearTimeout(hideHandle); - } - - if (statusMessageEntry) { - statusMessageEntry.dispose(); - } - } - }; - this.statusMessageDispose = statusMessageDispose; - - if (typeof autoDisposeAfter === 'number' && autoDisposeAfter > 0) { - hideHandle = setTimeout(() => statusMessageDispose.dispose(), autoDisposeAfter); - } - - return statusMessageDispose; + return itemContainer; } layout(width: number, height: number): void { @@ -291,27 +639,20 @@ export class StatusbarPart extends Part implements IStatusbarService { } } -let manageExtensionAction: ManageExtensionAction; -class StatusBarEntryItem extends Disposable { +class StatusbarEntryItem extends Disposable { private entryDisposables: IDisposable[] = []; constructor( private container: HTMLElement, entry: IStatusbarEntry, @ICommandService private readonly commandService: ICommandService, - @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, @IEditorService private readonly editorService: IEditorService, @IThemeService private readonly themeService: IThemeService ) { super(); - if (!manageExtensionAction) { - manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction); - } - this.render(entry); } @@ -351,19 +692,6 @@ class StatusBarEntryItem extends Disposable { addClass(this.container, 'has-background-color'); } - // Context Menu - if (entry.extensionId) { - this.entryDisposables.push((addDisposableListener(textContainer, 'contextmenu', e => { - EventHelper.stop(e, true); - - this.contextMenuService.showContextMenu({ - getAnchor: () => this.container, - getActionsContext: () => entry.extensionId!.value, - getActions: () => [manageExtensionAction] - }); - }))); - } - this.container.appendChild(textContainer); } @@ -412,43 +740,30 @@ class StatusBarEntryItem extends Disposable { } } -class ManageExtensionAction extends Action { - - constructor( - @ICommandService private readonly commandService: ICommandService - ) { - super('statusbar.manage.extension', nls.localize('manageExtension', "Manage Extension")); - } - - run(extensionId: string): Promise { - return this.commandService.executeCommand('_extensions.manage', extensionId); - } -} - registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND); if (statusBarItemHoverBackground) { - collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`); + collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:hover { background-color: ${statusBarItemHoverBackground}; }`); } const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND); if (statusBarItemActiveBackground) { - collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a:active { background-color: ${statusBarItemActiveBackground}; }`); + collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a:active { background-color: ${statusBarItemActiveBackground}; }`); } const statusBarProminentItemForeground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_FOREGROUND); if (statusBarProminentItemForeground) { - collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item .status-bar-info { color: ${statusBarProminentItemForeground}; }`); + collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item .status-bar-info { color: ${statusBarProminentItemForeground}; }`); } const statusBarProminentItemBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_BACKGROUND); if (statusBarProminentItemBackground) { - collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item .status-bar-info { background-color: ${statusBarProminentItemBackground}; }`); + collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item .status-bar-info { background-color: ${statusBarProminentItemBackground}; }`); } const statusBarProminentItemHoverBackground = theme.getColor(STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND); if (statusBarProminentItemHoverBackground) { - collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a.status-bar-info:hover { background-color: ${statusBarProminentItemHoverBackground}; }`); + collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item a.status-bar-info:hover { background-color: ${statusBarProminentItemHoverBackground}; }`); } }); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 7413f2e16f74..07630381121e 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -41,7 +41,7 @@ } /* Windows/Linux: Rules for custom title (icon, window controls) */ - +.monaco-workbench.web .part.titlebar, .monaco-workbench.windows .part.titlebar, .monaco-workbench.linux .part.titlebar { padding: 0; @@ -51,6 +51,7 @@ overflow: visible; } +.monaco-workbench.web .part.titlebar > .window-title, .monaco-workbench.windows .part.titlebar > .window-title, .monaco-workbench.linux .part.titlebar > .window-title { cursor: default; diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 62ebb0ac8528..8a9cf5070ddc 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -13,7 +13,7 @@ import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import * as DOM from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { isMacintosh, isLinux } from 'vs/base/common/platform'; +import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -34,22 +34,19 @@ import { assign } from 'vs/base/common/objects'; import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; export class MenubarControl extends Disposable { private keys = [ - 'files.autoSave', 'window.menuBarVisibility', - 'editor.multiCursorModifier', - 'workbench.sideBar.location', - 'workbench.statusBar.visible', - 'workbench.activityBar.visible', 'window.enableMenuBarMnemonics', 'window.nativeTabs' ]; // {{SQL CARBON EDIT}} - Disable unusued menus - private topLevelMenus: { + private menus: { 'File': IMenu; 'Edit': IMenu; // 'Selection': IMenu; @@ -79,15 +76,17 @@ export class MenubarControl extends Disposable { private container: HTMLElement; private recentlyOpened: IRecentlyOpened; private alwaysOnMnemonics: boolean; + private isNative: boolean; private readonly _onVisibilityChange: Emitter; private readonly _onFocusStateChange: Emitter; + private menubarService: IMenubarService; + private static MAX_MENU_RECENT_ENTRIES = 10; constructor( @IThemeService private readonly themeService: IThemeService, - @IMenubarService private readonly menubarService: IMenubarService, @IMenuService private readonly menuService: IMenuService, @IWindowService private readonly windowService: IWindowService, @IWindowsService private readonly windowsService: IWindowsService, @@ -100,13 +99,22 @@ export class MenubarControl extends Disposable { @INotificationService private readonly notificationService: INotificationService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IAccessibilityService private readonly accessibilityService: IAccessibilityService + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); - // {{SQL CARBON EDIT}} - Disable unused menus - this.topLevelMenus = { + this.isNative = !isWeb && (isMacintosh || this.currentTitlebarStyleSetting !== 'custom'); + + this.instantiationService.invokeFunction((accessor: ServicesAccessor) => { + if (this.isNative) { + this.menubarService = accessor.get(IMenubarService); + } + }); + + // {{SQL CARBON EDIT}} - Disable unusued menus + this.menus = { 'File': this._register(this.menuService.createMenu(MenuId.MenubarFileMenu, this.contextKeyService)), 'Edit': this._register(this.menuService.createMenu(MenuId.MenubarEditMenu, this.contextKeyService)), // 'Selection': this._register(this.menuService.createMenu(MenuId.MenubarSelectionMenu, this.contextKeyService)), @@ -117,8 +125,9 @@ export class MenubarControl extends Disposable { 'Help': this._register(this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService)) }; - if (isMacintosh) { - this.topLevelMenus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService)); + if (isMacintosh && this.isNative) { + this.menus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService)); + this.topLevelTitles['Preferences'] = nls.localize('mPreferences', "Preferences"); } this.menuUpdater = this._register(new RunOnceScheduler(() => this.doUpdateMenubar(false), 200)); @@ -126,9 +135,9 @@ export class MenubarControl extends Disposable { this._onVisibilityChange = this._register(new Emitter()); this._onFocusStateChange = this._register(new Emitter()); - if (isMacintosh || this.currentTitlebarStyleSetting !== 'custom') { - for (const topLevelMenuName of Object.keys(this.topLevelMenus)) { - const menu = this.topLevelMenus[topLevelMenuName]; + if (this.isNative) { + for (const topLevelMenuName of Object.keys(this.topLevelTitles)) { + const menu = this.menus[topLevelMenuName]; if (menu) { this._register(menu.onDidChange(() => this.updateMenubar())); } @@ -138,13 +147,11 @@ export class MenubarControl extends Disposable { this.windowService.getRecentlyOpened().then((recentlyOpened) => { this.recentlyOpened = recentlyOpened; - if (isMacintosh || this.currentTitlebarStyleSetting !== 'custom') { + if (this.isNative) { this.doUpdateMenubar(true); } }); - this.notifyExistingLinuxUser(); - this.notifyUserOfCustomMenubarAccessibility(); this.registerListeners(); @@ -159,28 +166,6 @@ export class MenubarControl extends Disposable { return enableMenuBarMnemonics; } - private get currentSidebarPosition(): string { - return this.configurationService.getValue('workbench.sideBar.location'); - } - - private get currentStatusBarVisibility(): boolean { - let setting = this.configurationService.getValue('workbench.statusBar.visible'); - if (typeof setting !== 'boolean') { - setting = true; - } - - return setting; - } - - private get currentActivityBarVisibility(): boolean { - let setting = this.configurationService.getValue('workbench.activityBar.visible'); - if (typeof setting !== 'boolean') { - setting = true; - } - - return setting; - } - private get currentMenubarVisibility(): MenuBarVisibility { return this.configurationService.getValue('window.menuBarVisibility'); } @@ -217,38 +202,8 @@ export class MenubarControl extends Disposable { }); } - // TODO@sbatten remove after feb19 - private notifyExistingLinuxUser(): void { - if (!isLinux) { - return; - } - - const isNewUser = !this.storageService.get('telemetry.lastSessionDate', StorageScope.GLOBAL); - const hasBeenNotified = this.storageService.getBoolean('menubar/linuxTitlebarRevertNotified', StorageScope.GLOBAL, false); - const titleBarConfiguration = this.configurationService.inspect('window.titleBarStyle'); - const customShown = getTitleBarStyle(this.configurationService, this.environmentService) === 'custom'; - - if (!hasBeenNotified) { - this.storageService.store('menubar/linuxTitlebarRevertNotified', true, StorageScope.GLOBAL); - } - - if (isNewUser || hasBeenNotified || (titleBarConfiguration && titleBarConfiguration.user) || customShown) { - return; - } - - const message = nls.localize('menubar.linuxTitlebarRevertNotification', "We have updated the default title bar on Linux to use the native setting. If you prefer, you can go back to the custom setting. More information is available in our [online documentation](https://go.microsoft.com/fwlink/?linkid=2074137)."); - this.notificationService.prompt(Severity.Info, message, [ - { - label: nls.localize('goToSetting', "Open Settings"), - run: () => { - return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); - } - } - ]); - } - private notifyUserOfCustomMenubarAccessibility(): void { - if (isMacintosh) { + if (isWeb || isMacintosh) { return; } @@ -288,7 +243,7 @@ export class MenubarControl extends Disposable { this._register(this.keybindingService.onDidUpdateKeybindings(() => this.updateMenubar())); // These listeners only apply when the custom menubar is being used - if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') { + if (!this.isNative) { // Listen for window focus changes this._register(this.windowService.onDidChangeFocus(e => this.onDidChangeWindowFocus(e))); @@ -304,7 +259,7 @@ export class MenubarControl extends Disposable { } private doUpdateMenubar(firstTime: boolean): void { - if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') { + if (!this.isNative) { this.setupCustomMenubar(firstTime); } else { // Send menus to main process to be rendered by Electron @@ -322,30 +277,6 @@ export class MenubarControl extends Disposable { private calculateActionLabel(action: IAction | IMenubarMenuItemAction): string { let label = action.label; switch (action.id) { - case 'workbench.action.toggleSidebarPosition': - if (this.currentSidebarPosition !== 'right') { - label = nls.localize({ key: 'miMoveSidebarRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Right"); - } else { - label = nls.localize({ key: 'miMoveSidebarLeft', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left"); - } - break; - - case 'workbench.action.toggleStatusbarVisibility': - if (this.currentStatusBarVisibility) { - label = nls.localize({ key: 'miHideStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Hide Status Bar"); - } else { - label = nls.localize({ key: 'miShowStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Show Status Bar"); - } - break; - - case 'workbench.action.toggleActivityBarVisibility': - if (this.currentActivityBarVisibility) { - label = nls.localize({ key: 'miHideActivityBar', comment: ['&& denotes a mnemonic'] }, "Hide &&Activity Bar"); - } else { - label = nls.localize({ key: 'miShowActivityBar', comment: ['&& denotes a mnemonic'] }, "Show &&Activity Bar"); - } - break; - default: break; } @@ -472,7 +403,7 @@ export class MenubarControl extends Disposable { break; case 'workbench.action.showAboutDialog': - if (!isMacintosh) { + if (!isMacintosh && !isWeb) { const updateAction = this.getUpdateAction(); if (updateAction) { updateAction.label = mnemonicMenuLabel(updateAction.label); @@ -512,7 +443,7 @@ export class MenubarControl extends Disposable { } // Update the menu actions - const updateActions = (menu: IMenu, target: IAction[]) => { + const updateActions = (menu: IMenu, target: IAction[], topLevelTitle: string) => { target.splice(0); let groups = menu.getActions(); for (let group of groups) { @@ -521,11 +452,20 @@ export class MenubarControl extends Disposable { for (let action of actions) { this.insertActionsBefore(action, target); if (action instanceof SubmenuItemAction) { - const submenu = this.menuService.createMenu(action.item.submenu, this.contextKeyService); + if (!this.menus[action.item.submenu]) { + this.menus[action.item.submenu] = this.menuService.createMenu(action.item.submenu, this.contextKeyService); + const submenu = this.menus[action.item.submenu]; + this._register(submenu!.onDidChange(() => { + const actions: IAction[] = []; + updateActions(menu, actions, topLevelTitle); + this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[topLevelTitle]) }); + }, this)); + } + + const submenu = this.menus[action.item.submenu]!; const submenuActions: SubmenuAction[] = []; - updateActions(submenu, submenuActions); + updateActions(submenu, submenuActions, topLevelTitle); target.push(new SubmenuAction(mnemonicMenuLabel(action.label), submenuActions)); - submenu.dispose(); } else { action.label = mnemonicMenuLabel(this.calculateActionLabel(action)); target.push(action); @@ -538,19 +478,19 @@ export class MenubarControl extends Disposable { target.pop(); }; - for (const title of Object.keys(this.topLevelMenus)) { - const menu = this.topLevelMenus[title]; + for (const title of Object.keys(this.topLevelTitles)) { + const menu = this.menus[title]; if (firstTime && menu) { this._register(menu.onDidChange(() => { const actions: IAction[] = []; - updateActions(menu, actions); + updateActions(menu, actions, title); this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); })); } const actions: IAction[] = []; if (menu) { - updateActions(menu, actions); + updateActions(menu, actions, title); } if (!firstTime) { @@ -591,6 +531,12 @@ export class MenubarControl extends Disposable { if (menuItem instanceof SubmenuItemAction) { const submenu = { items: [] }; + + if (!this.menus[menuItem.item.submenu]) { + this.menus[menuItem.item.submenu] = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService); + this._register(this.menus[menuItem.item.submenu]!.onDidChange(() => this.updateMenubar())); + } + const menuToDispose = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService); this.populateMenuItems(menuToDispose, submenu, keybindings); @@ -637,7 +583,7 @@ export class MenubarControl extends Disposable { private getAdditionalKeybindings(): { [id: string]: IMenubarKeybinding } { const keybindings = {}; - if (isMacintosh) { + if (isMacintosh && this.isNative) { keybindings['workbench.action.quit'] = (this.getMenubarKeybinding('workbench.action.quit')); } @@ -650,8 +596,8 @@ export class MenubarControl extends Disposable { } menubarData.keybindings = this.getAdditionalKeybindings(); - for (const topLevelMenuName of Object.keys(this.topLevelMenus)) { - const menu = this.topLevelMenus[topLevelMenuName]; + for (const topLevelMenuName of Object.keys(this.topLevelTitles)) { + const menu = this.menus[topLevelMenuName]; if (menu) { const menubarMenu: IMenubarMenu = { items: [] }; this.populateMenuItems(menu, menubarMenu, menubarData.keybindings); @@ -698,7 +644,7 @@ export class MenubarControl extends Disposable { // Build the menubar if (this.container) { - if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') { + if (!this.isNative) { this.doUpdateMenubar(true); } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 388d3279ba96..b22fcdfc6f01 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -22,7 +22,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER } from 'vs/workbench/common/theme'; -import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform'; +import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; import { trim } from 'vs/base/common/strings'; @@ -50,8 +50,8 @@ export class TitlebarPart extends Part implements ITitleService { readonly minimumWidth: number = 0; readonly maximumWidth: number = Number.POSITIVE_INFINITY; - get minimumHeight(): number { return isMacintosh ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); } - get maximumHeight(): number { return isMacintosh ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); } + get minimumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); } + get maximumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); } //#endregion @@ -135,9 +135,9 @@ export class TitlebarPart extends Part implements ITitleService { } private onMenubarVisibilityChanged(visible: boolean) { - if (isWindows || isLinux) { + if (isWeb || isWindows || isLinux) { // Hide title when toggling menu bar - if (this.configurationService.getValue('window.menuBarVisibility') === 'toggle' && visible) { + if (!isWeb && this.configurationService.getValue('window.menuBarVisibility') === 'toggle' && visible) { // Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor hide(this.dragRegion); setTimeout(() => show(this.dragRegion), 50); @@ -150,7 +150,7 @@ export class TitlebarPart extends Part implements ITitleService { } private onMenubarFocusChanged(focused: boolean) { - if (isWindows || isLinux) { + if (!isWeb && (isWindows || isLinux)) { if (focused) { hide(this.dragRegion); } else { @@ -207,7 +207,7 @@ export class TitlebarPart extends Part implements ITitleService { this.pendingTitle = title; } - if ((isWindows || isLinux) && this.title) { + if ((isWeb || isWindows || isLinux) && this.title) { if (this.lastLayoutDimensions) { this.updateLayout(this.lastLayoutDimensions); } @@ -322,10 +322,12 @@ export class TitlebarPart extends Part implements ITitleService { this.element = parent; // Draggable region that we can manipulate for #52522 - this.dragRegion = append(this.element, $('div.titlebar-drag-region')); + if (!isWeb) { + this.dragRegion = append(this.element, $('div.titlebar-drag-region')); + } - // App Icon (Windows/Linux) - if (!isMacintosh) { + // App Icon (Native Windows/Linux) + if (!isMacintosh && !isWeb) { this.appIcon = append(this.element, $('div.window-appicon')); this.onUpdateAppIconDragBehavior(); @@ -341,7 +343,7 @@ export class TitlebarPart extends Part implements ITitleService { this.menubarPart.create(this.menubar); - if (!isMacintosh) { + if (!isMacintosh || isWeb) { this._register(this.menubarPart.onVisibilityChange(e => this.onMenubarVisibilityChanged(e))); this._register(this.menubarPart.onFocusStateChange(e => this.onMenubarFocusChanged(e))); } @@ -355,7 +357,7 @@ export class TitlebarPart extends Part implements ITitleService { } // Maximize/Restore on doubleclick - if (isMacintosh) { + if (isMacintosh && !isWeb) { this._register(addDisposableListener(this.element, EventType.DBLCLICK, e => { EventHelper.stop(e); @@ -374,8 +376,8 @@ export class TitlebarPart extends Part implements ITitleService { })); }); - // Window Controls (Windows/Linux) - if (!isMacintosh) { + // Window Controls (Native Windows/Linux) + if (!isMacintosh && !isWeb) { this.windowControls = append(this.element, $('div.window-controls-container')); @@ -546,17 +548,24 @@ export class TitlebarPart extends Part implements ITitleService { } private adjustTitleMarginToCenter(): void { - if (!isMacintosh && - (this.appIcon.clientWidth + this.menubar.clientWidth + 10 > (this.element.clientWidth - this.title.clientWidth) / 2 || - this.element.clientWidth - this.windowControls.clientWidth - 10 < (this.element.clientWidth + this.title.clientWidth) / 2)) { - this.title.style.position = null; - this.title.style.left = null; - this.title.style.transform = null; - } else { - this.title.style.position = 'absolute'; - this.title.style.left = '50%'; - this.title.style.transform = 'translate(-50%, 0)'; + if (!isMacintosh || isWeb) { + const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10; + const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10; + + // Not enough space to center the titlebar within window, + // Center between menu and window controls + if (leftMarker > (this.element.clientWidth - this.title.clientWidth) / 2 || + rightMarker < (this.element.clientWidth + this.title.clientWidth) / 2) { + this.title.style.position = null; + this.title.style.left = null; + this.title.style.transform = null; + return; + } } + + this.title.style.position = 'absolute'; + this.title.style.left = '50%'; + this.title.style.transform = 'translate(-50%, 0)'; } updateLayout(dimension: Dimension): void { @@ -564,15 +573,15 @@ export class TitlebarPart extends Part implements ITitleService { if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { // Only prevent zooming behavior on macOS or when the menubar is not visible - if (isMacintosh || this.configurationService.getValue('window.menuBarVisibility') === 'hidden') { + if ((!isWeb && isMacintosh) || this.configurationService.getValue('window.menuBarVisibility') === 'hidden') { this.title.style.zoom = `${1 / getZoomFactor()}`; - if (isWindows || isLinux) { + if (!isWeb && (isWindows || isLinux)) { this.appIcon.style.zoom = `${1 / getZoomFactor()}`; this.windowControls.style.zoom = `${1 / getZoomFactor()}`; } } else { this.title.style.zoom = null; - if (isWindows || isLinux) { + if (!isWeb && (isWindows || isLinux)) { this.appIcon.style.zoom = null; this.windowControls.style.zoom = null; } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 0d23af2842c8..6d3e69f4bfd6 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -17,7 +17,7 @@ import { IViewsService, ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeVie import { IViewletViewOptions, FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IProgressService2 } from 'vs/platform/progress/common/progress'; +import { IProgressService } from 'vs/platform/progress/common/progress'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -60,9 +60,9 @@ export class CustomTreeViewPanel extends ViewletPanel { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView; - this.treeView.onDidChangeActions(() => this.updateActions(), this, this.disposables); - this.disposables.push(toDisposable(() => this.treeView.setVisibility(false))); - this.disposables.push(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility())); + this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); + this._register(toDisposable(() => this.treeView.setVisibility(false))); + this._register(this.onDidChangeBodyVisibility(() => this.updateTreeVisibility())); this.updateTreeVisibility(); } @@ -98,11 +98,6 @@ export class CustomTreeViewPanel extends ViewletPanel { private updateTreeVisibility(): void { this.treeView.setVisibility(this.isBodyVisible()); } - - dispose(): void { - dispose(this.disposables); - super.dispose(); - } } class TitleMenus implements IDisposable { @@ -216,7 +211,7 @@ export class CustomTreeView extends Disposable implements ITreeView { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommandService private readonly commandService: ICommandService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IProgressService2 private readonly progressService: IProgressService2 + @IProgressService private readonly progressService: IProgressService ) { super(); this.root = new Root(); diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index b6bdf29506a4..a1d047083ba0 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -46,16 +46,16 @@ export abstract class ViewletPanel extends Panel implements IView { private static AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions'; - private _onDidFocus = new Emitter(); + private _onDidFocus = this._register(new Emitter()); readonly onDidFocus: Event = this._onDidFocus.event; - private _onDidBlur = new Emitter(); + private _onDidBlur = this._register(new Emitter()); readonly onDidBlur: Event = this._onDidBlur.event; - private _onDidChangeBodyVisibility = new Emitter(); + private _onDidChangeBodyVisibility = this._register(new Emitter()); readonly onDidChangeBodyVisibility: Event = this._onDidChangeBodyVisibility.event; - protected _onDidChangeTitleArea = new Emitter(); + protected _onDidChangeTitleArea = this._register(new Emitter()); readonly onDidChangeTitleArea: Event = this._onDidChangeTitleArea.event; private _isVisible: boolean = false; @@ -78,8 +78,6 @@ export abstract class ViewletPanel extends Panel implements IView { this.id = options.id; this.title = options.title; this.actionRunner = options.actionRunner; - - this.disposables.push(this._onDidFocus, this._onDidBlur, this._onDidChangeBodyVisibility, this._onDidChangeTitleArea); } setVisible(visible: boolean): void { @@ -113,9 +111,9 @@ export abstract class ViewletPanel extends Panel implements IView { super.render(); const focusTracker = trackFocus(this.element); - this.disposables.push(focusTracker); - this.disposables.push(focusTracker.onDidFocus(() => this._onDidFocus.fire())); - this.disposables.push(focusTracker.onDidBlur(() => this._onDidBlur.fire())); + this._register(focusTracker); + this._register(focusTracker.onDidFocus(() => this._onDidFocus.fire())); + this._register(focusTracker.onDidBlur(() => this._onDidBlur.fire())); } protected renderHeader(container: HTMLElement): void { @@ -132,11 +130,11 @@ export abstract class ViewletPanel extends Panel implements IView { actionRunner: this.actionRunner }); - this.disposables.push(this.toolbar); + this._register(this.toolbar); this.setActions(); const onDidRelevantConfigurationChange = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewletPanel.AlwaysShowActionsConfig)); - onDidRelevantConfigurationChange(this.updateActionsVisibility, this, this.disposables); + this._register(onDidRelevantConfigurationChange(this.updateActionsVisibility, this)); this.updateActionsVisibility(); } @@ -355,7 +353,7 @@ export class PanelViewlet extends Viewlet { headerBorder: SIDE_BAR_SECTION_HEADER_BORDER, dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND }, panel); - const disposable = combinedDisposable([onDidFocus, onDidChangeTitleArea, panelStyler, onDidChange]); + const disposable = combinedDisposable(onDidFocus, onDidChangeTitleArea, panelStyler, onDidChange); const panelItem: IViewletPanelItem = { panel, disposable }; this.panelItems.splice(index, 0, panelItem); diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 7053b2f079a9..5f877fd885b4 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -29,14 +29,15 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { localize } from 'vs/nls'; import { IAddedViewDescriptorRef, IViewDescriptorRef, PersistentContributableViewsModel } from 'vs/workbench/browser/parts/views/views'; import { Registry } from 'vs/platform/registry/common/platform'; +import { MementoObject } from 'vs/workbench/common/memento'; export interface IViewletViewOptions extends IViewletPanelOptions { - viewletState: object; + viewletState: MementoObject; } export abstract class ViewContainerViewlet extends PanelViewlet implements IViewsViewlet { - private readonly viewletState: object; + private readonly viewletState: MementoObject; private didLayout = false; private dimension: DOM.Dimension; private areExtensionsReady: boolean = false; @@ -207,7 +208,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView this.viewsModel.setCollapsed(viewDescriptor.id, collapsed); }); - this.viewDisposables.splice(index, 0, combinedDisposable([contextMenuDisposable, collapseDisposable])); + this.viewDisposables.splice(index, 0, combinedDisposable(contextMenuDisposable, collapseDisposable)); panelsToAdd.push({ panel, size: size || panel.minimumSize, index }); } diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index 96d47cf334f9..535c4d2432bd 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/style'; +import 'vs/css!./media/icons'; import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 76280b37f071..486568bcf895 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -21,13 +21,33 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/workbench/services/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { URI } from 'vs/base/common/uri'; +import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; +import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; +import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache'; +import { ConfigurationFileService } from 'vs/workbench/services/configuration/common/configuration'; +import { WebResources } from 'vs/workbench/browser/web.resources'; + +interface IWindowConfiguration { + settingsUri: URI; + remoteAuthority: string; + folderUri?: URI; + workspaceUri?: URI; +} class CodeRendererMain extends Disposable { private workbench: Workbench; + constructor(private readonly configuration: IWindowConfiguration) { + super(); + } + async open(): Promise { - const services = this.initServices(); + const services = await this.initServices(); await domContentLoaded(); mark('willStartWorkbench'); @@ -42,6 +62,9 @@ class CodeRendererMain extends Disposable { // Layout this._register(addDisposableListener(window, EventType.RESIZE, () => this.workbench.layout())); + // Resource Loading + this._register(new WebResources(services.serviceCollection.get(IFileService))); + // Workbench Lifecycle this._register(this.workbench.onShutdown(() => this.dispose())); @@ -49,7 +72,7 @@ class CodeRendererMain extends Disposable { this.workbench.startup(); } - private initServices(): { serviceCollection: ServiceCollection, logService: ILogService } { + private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService }> { const serviceCollection = new ServiceCollection(); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -62,7 +85,7 @@ class CodeRendererMain extends Disposable { serviceCollection.set(ILogService, logService); // Environment - const environmentService = new SimpleWorkbenchEnvironmentService(); + const environmentService = this.createEnvironmentService(); serviceCollection.set(IWorkbenchEnvironmentService, environmentService); // Product @@ -87,12 +110,101 @@ class CodeRendererMain extends Disposable { fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider); } + const payload = await this.resolveWorkspaceInitializationPayload(); + + await Promise.all([ + this.createWorkspaceService(payload, fileService, remoteAgentService, logService).then(service => { + + // Workspace + serviceCollection.set(IWorkspaceContextService, service); + + // Configuration + serviceCollection.set(IConfigurationService, service); + + return service; + }), + ]); + return { serviceCollection, logService }; } + + private createEnvironmentService(): IWorkbenchEnvironmentService { + const environmentService = new SimpleWorkbenchEnvironmentService(); + environmentService.appRoot = '/web/'; + environmentService.args = { _: [] }; + environmentService.appSettingsHome = '/web/settings'; + environmentService.settingsResource = this.configuration.settingsUri; + environmentService.appKeybindingsPath = '/web/settings/keybindings.json'; + environmentService.logsPath = '/web/logs'; + environmentService.debugExtensionHost = { + port: null, + break: false + }; + return environmentService; + } + + private async createWorkspaceService(payload: IWorkspaceInitializationPayload, fileService: FileService, remoteAgentService: IRemoteAgentService, logService: ILogService): Promise { + + const workspaceService = new WorkspaceService({ userSettingsResource: this.configuration.settingsUri, remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache() }, new ConfigurationFileService(fileService), remoteAgentService); + + try { + await workspaceService.initialize(payload); + + return workspaceService; + } catch (error) { + onUnexpectedError(error); + logService.error(error); + + return workspaceService; + } + } + + private async resolveWorkspaceInitializationPayload(): Promise { + + const hash = (uri: URI) => { + return crypto.subtle.digest('SHA-1', new TextEncoder().encode(uri.toString())).then(buffer => { + // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Converting_a_digest_to_a_hex_string + return Array.prototype.map.call(new Uint8Array(buffer), (value: number) => `00${value.toString(16)}`.slice(-2)).join(''); + }); + }; + + // Multi-root workspace + if (this.configuration.workspaceUri) { + const id = await hash(this.configuration.workspaceUri); + return { id, configPath: this.configuration.workspaceUri }; + } + + // Single-folder workspace + if (this.configuration.folderUri) { + const id = await hash(this.configuration.folderUri); + return { id, folder: this.configuration.folderUri }; + } + + return { id: 'empty-window' }; + } } -export function main(): Promise { - const renderer = new CodeRendererMain(); +export interface IWindowConfigurationContents { + settingsPath: string; + folderPath?: string; + workspacePath?: string; +} +export function main(windowConfigurationContents: IWindowConfigurationContents): Promise { + const windowConfiguration: IWindowConfiguration = { + settingsUri: toResource(windowConfigurationContents.settingsPath), + folderUri: windowConfigurationContents.folderPath ? toResource(windowConfigurationContents.folderPath) : undefined, + workspaceUri: windowConfigurationContents.workspacePath ? toResource(windowConfigurationContents.workspacePath) : undefined, + remoteAuthority: document.location.host + }; + const renderer = new CodeRendererMain(windowConfiguration); return renderer.open(); } + +function toResource(path: string): URI { + return URI.from({ + scheme: Schemas.vscodeRemote, + authority: document.location.host, + path + }); +} diff --git a/src/vs/workbench/browser/web.resources.ts b/src/vs/workbench/browser/web.resources.ts new file mode 100644 index 000000000000..ea43363f0c0d --- /dev/null +++ b/src/vs/workbench/browser/web.resources.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; + +export class WebResources { + + private readonly _regexp = /url\(('|")?(vscode-remote:\/\/.*?)\1\)/g; + private readonly _cache = new Map(); + private readonly _observer: MutationObserver; + + constructor(@IFileService private readonly _fileService: IFileService) { + this._observer = new MutationObserver(r => this._handleMutation(r)); + + // todo@joh add observer to more than head-element + // todo@joh explore alternative approach + this._observer.observe(document.head, { subtree: true, childList: true }); + } + + dispose(): void { + this._observer.disconnect(); + this._cache.forEach(value => URL.revokeObjectURL(value)); + } + + private _handleMutation(records: MutationRecord[]): void { + for (const record of records) { + if (record.target.nodeName === 'STYLE') { + // style-element directly modified + this._handleStyleNode(record.target); + + } else if (record.target.nodeName === 'HEAD' && record.type === 'childList') { + // style-element added to head + record.addedNodes.forEach(node => { + if (node.nodeName === 'STYLE') { + this._handleStyleNode(node); + } + }); + } + } + } + + private _handleStyleNode(target: Node): void { + + if (!target.textContent) { + return; + } + + const positions: number[] = []; + const promises: Promise[] = []; + + let match: RegExpMatchArray | null = null; + while (match = this._regexp.exec(target.textContent)) { + + const remoteUrl = match[2]; + positions.push(match.index! + 'url('.length + match[1].length); + positions.push(remoteUrl.length); + + if (this._cache.has(remoteUrl)) { + promises.push(Promise.resolve()); + + } else { + promises.push(this._fileService.readFile(URI.parse(remoteUrl, true)).then(file => { + // todo@joh hack + const type = /\.woff$/.test(remoteUrl) ? 'application/font-woff' : 'image/svg+xml'; + this._cache.set(remoteUrl, URL.createObjectURL(new Blob([file.value.buffer], { type }))); + })); + } + } + + if (promises.length === 0) { + return; + } + + let content = target.textContent; + + Promise.all(promises).then(() => { + + if (target.textContent !== content) { + return; + } + + for (let i = positions.length - 1; i >= 0; i -= 2) { + const start = positions[i - 1]; + const len = positions[i]; + const url = this._cache.get(content.substr(start, len)); + content = content.substring(0, start) + url + content.substring(start + len); + } + + target.textContent = content; + + }).catch(e => { + console.error(e); + }); + } +} diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 5d8b22e82c49..b2925f3e098c 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -13,14 +13,13 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; // tslint:disable-next-line: import-patterns no-standalone-editor -import { SimpleConfigurationService as StandaloneEditorConfigurationService, StandaloneKeybindingService, SimpleResourcePropertiesService } from 'vs/editor/standalone/browser/simpleServices'; +import { StandaloneKeybindingService, SimpleResourcePropertiesService } from 'vs/editor/standalone/browser/simpleServices'; import { IDownloadService } from 'vs/platform/download/common/download'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionHostDebugParams, IDebugParams } from 'vs/platform/environment/common/environment'; import { IExtensionGalleryService, IQueryOptions, IGalleryExtension, InstallOperation, StatisticType, ITranslation, IGalleryExtensionVersion, IExtensionIdentifier, IReportedExtension, IExtensionManagementService, ILocalExtension, IGalleryMetadata, IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation, IExtensionEnablementService, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IPager } from 'vs/base/common/paging'; import { IExtensionManifest, ExtensionType, ExtensionIdentifier, IExtension } from 'vs/platform/extensions/common/extensions'; -import { NullExtensionService, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -29,9 +28,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; import { ILogService, LogLevel, ConsoleLogService } from 'vs/platform/log/common/log'; import { ShutdownReason, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar'; import { IProductService } from 'vs/platform/product/common/product'; -import { isEqualOrParent, isEqual } from 'vs/base/common/resources'; import { ISearchService, ITextQueryProps, ISearchProgressItem, ISearchComplete, IFileQueryProps, SearchProviderType, ISearchResultProvider, ITextQuery, IFileMatch, QueryType, FileMatch, pathIncludedInQuery } from 'vs/workbench/services/search/common/search'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -41,29 +38,37 @@ import { Schemas } from 'vs/base/common/network'; import { editorMatchesToTextSearchResults, addContextToEditorMatches } from 'vs/workbench/services/search/common/searchHelpers'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage'; -import { ITextMateService, IGrammar as ITextMategrammar } from 'vs/workbench/services/textMate/common/textMateService'; -import { LanguageId, TokenizationRegistry } from 'vs/editor/common/modes'; import { IUpdateService, State } from 'vs/platform/update/common/update'; import { IWindowConfiguration, IPath, IPathsToWaitFor, IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen, IMessageBoxResult, IWindowsService, IOpenSettings } from 'vs/platform/windows/common/windows'; import { IProcessEnvironment } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData, isSingleFolderWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { ExportData } from 'vs/base/common/performance'; import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { IWorkspaceContextService, Workspace, toWorkspaceFolder, IWorkspaceFolder, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Color, RGBA } from 'vs/base/common/color'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileService } from 'vs/platform/files/common/files'; - -export const workspaceResource = URI.file((self).USER_HOME_DIR || '/').with({ - scheme: Schemas.vscodeRemote, - authority: document.location.host -}); +import { IReloadSessionEvent, IExtensionHostDebugService, ICloseSessionEvent, IAttachSessionEvent, ILogToSessionEvent, ITerminateSessionEvent } from 'vs/workbench/services/extensions/common/extensionHostDebug'; +import { IRemoteConsoleLog } from 'vs/base/common/console'; +// tslint:disable-next-line: import-patterns +import { State as DebugState, IDebugService, IDebugSession, IConfigurationManager, IStackFrame, IThread, IViewModel, IExpression, IFunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; +// tslint:disable-next-line: import-patterns +import { IExtensionsWorkbenchService, IExtension as IExtension2 } from 'vs/workbench/contrib/extensions/common/extensions'; +// tslint:disable-next-line: import-patterns +import { ITerminalService, ITerminalConfigHelper, ITerminalTab, ITerminalInstance, ITerminalProcessExtHostRequest } from 'vs/workbench/contrib/terminal/common/terminal'; +// tslint:disable-next-line: import-patterns +import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; +// tslint:disable-next-line: import-patterns +import { TaskEvent } from 'vs/workbench/contrib/tasks/common/tasks'; +// tslint:disable-next-line: import-patterns +import { ICommentService, IResourceCommentThreadEvent, IWorkspaceCommentThreadsEvent } from 'vs/workbench/contrib/comments/browser/commentService'; +// tslint:disable-next-line: import-patterns +import { ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; +import { CommentingRanges } from 'vs/editor/common/modes'; +import { Range } from 'vs/editor/common/core/range'; //#region Backup File @@ -127,36 +132,6 @@ registerSingleton(IBackupFileService, SimpleBackupFileService, true); //#endregion -//#region Broadcast - -export const IBroadcastService = createDecorator('broadcastService'); - -export interface IBroadcast { - channel: string; - payload: any; -} - -export interface IBroadcastService { - _serviceBrand: any; - - onBroadcast: Event; - - broadcast(b: IBroadcast): void; -} - -export class SimpleBroadcastService implements IBroadcastService { - - _serviceBrand: any; - - readonly onBroadcast: Event = Event.None; - - broadcast(b: IBroadcast): void { } -} - -registerSingleton(IBroadcastService, SimpleBroadcastService, true); - -//#endregion - //#region Clipboard export class SimpleClipboardService implements IClipboardService { @@ -192,14 +167,6 @@ registerSingleton(IClipboardService, SimpleClipboardService, true); //#endregion -//#region Configuration - -export class SimpleConfigurationService extends StandaloneEditorConfigurationService { } - -registerSingleton(IConfigurationService, SimpleConfigurationService); - -//#endregion - //#region Dialog // export class SimpleDialogService extends StandaloneEditorDialogService { } @@ -231,17 +198,17 @@ export class SimpleWorkbenchEnvironmentService implements IWorkbenchEnvironmentS untitledWorkspacesHome: URI; extensionTestsLocationURI?: URI; _serviceBrand: any; - args = { _: [] }; + args: any; execPath: string; cliPath: string; - appRoot: string = '/web/'; + appRoot: string; userHome: string; userDataPath: string; appNameLong: string; appQuality?: string; - appSettingsHome: string = '/web/settings'; - appSettingsPath: string = '/web/settings/settings.json'; - appKeybindingsPath: string = '/web/settings/keybindings.json'; + appSettingsHome: string; + settingsResource: URI; + appKeybindingsPath: string; machineSettingsHome: string; machineSettingsPath: string; settingsSearchBuildId?: number; @@ -264,7 +231,7 @@ export class SimpleWorkbenchEnvironmentService implements IWorkbenchEnvironmentS wait: boolean; status: boolean; log?: string; - logsPath: string = '/web/logs'; + logsPath: string; verbose: boolean; skipGettingStarted: boolean; skipReleaseNotes: boolean; @@ -350,6 +317,235 @@ registerSingleton(IExtensionGalleryService, SimpleExtensionGalleryService, true) //#endregion +//#region IDebugService +export class SimpleDebugService implements IDebugService { + _serviceBrand: any; + state: DebugState; + onDidChangeState: Event = Event.None; + onDidNewSession: Event = Event.None; + onWillNewSession: Event = Event.None; + onDidEndSession: Event = Event.None; + getConfigurationManager(): IConfigurationManager { + return new class implements IConfigurationManager { + canSetBreakpointsIn: any; + selectedConfiguration: any; + selectConfiguration: any; + getLaunches: any; + getLaunch: any; + onDidSelectConfiguration: Event; + activateDebuggers: any; + hasDebugConfigurationProvider: any; + registerDebugConfigurationProvider: any; + unregisterDebugConfigurationProvider: any; + registerDebugAdapterDescriptorFactory: any; + unregisterDebugAdapterDescriptorFactory: any; + resolveConfigurationByProviders: any; + getDebugAdapterDescriptor: any; + registerDebugAdapterFactory() { return Disposable.None; } + createDebugAdapter: any; + substituteVariables: any; + runInTerminal: any; + }; + } + focusStackFrame: any; + addBreakpoints: any; + updateBreakpoints: any; + enableOrDisableBreakpoints: any; + setBreakpointsActivated: any; + removeBreakpoints: any; + addFunctionBreakpoint: any; + renameFunctionBreakpoint: any; + removeFunctionBreakpoints: any; + sendAllBreakpoints: any; + addWatchExpression: any; + renameWatchExpression: any; + moveWatchExpression: any; + removeWatchExpressions: any; + startDebugging: any; + restartSession: any; + stopSession: any; + sourceIsNotAvailable: any; + getModel: any; + getViewModel(): IViewModel { + return new class implements IViewModel { + focusedSession: IDebugSession | undefined; + focusedThread: IThread | undefined; + focusedStackFrame: IStackFrame | undefined; + getSelectedExpression(): IExpression | undefined { + throw new Error('Method not implemented.'); + } + getSelectedFunctionBreakpoint(): IFunctionBreakpoint | undefined { + throw new Error('Method not implemented.'); + } + setSelectedExpression(expression: IExpression | undefined): void { + throw new Error('Method not implemented.'); + } + setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint | undefined): void { + throw new Error('Method not implemented.'); + } + isMultiSessionView(): boolean { + throw new Error('Method not implemented.'); + } + onDidFocusSession: Event = Event.None; + onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; }> = Event.None; + onDidSelectExpression: Event = Event.None; + getId(): string { + throw new Error('Method not implemented.'); + } + }; + } +} +registerSingleton(IDebugService, SimpleDebugService, true); + +//#endregion IExtensionsWorkbenchService +export class SimpleExtensionsWorkbenchService implements IExtensionsWorkbenchService { + _serviceBrand: any; + onChange: Event; + local: IExtension2[]; + installed: IExtension2[]; + outdated: IExtension2[]; + queryLocal: any; + queryGallery: any; + canInstall: any; + install: any; + uninstall: any; + installVersion: any; + reinstall: any; + setEnablement: any; + open: any; + checkForUpdates: any; + allowedBadgeProviders: string[]; +} +registerSingleton(IExtensionsWorkbenchService, SimpleExtensionsWorkbenchService, true); +//#endregion + +//#region ITerminalService +export class SimpleTerminalService implements ITerminalService { + _serviceBrand: any; activeTabIndex: number; + configHelper: ITerminalConfigHelper; + onActiveTabChanged: Event = Event.None; + onTabDisposed: Event = Event.None; + onInstanceCreated: Event = Event.None; + onInstanceDisposed: Event = Event.None; + onInstanceProcessIdReady: Event = Event.None; + onInstanceDimensionsChanged: Event = Event.None; + onInstanceRequestExtHostProcess: Event = Event.None; + onInstancesChanged: Event = Event.None; + onInstanceTitleChanged: Event = Event.None; + onActiveInstanceChanged: Event = Event.None; + terminalInstances: ITerminalInstance[] = []; + terminalTabs: ITerminalTab[]; + createTerminal: any; + createTerminalRenderer: any; + createInstance: any; + getInstanceFromId: any; + getInstanceFromIndex: any; + getTabLabels: any; + getActiveInstance() { return null; } + setActiveInstance: any; + setActiveInstanceByIndex: any; + getActiveOrCreateInstance: any; + splitInstance: any; + getActiveTab: any; + setActiveTabToNext: any; + setActiveTabToPrevious: any; + setActiveTabByIndex: any; + refreshActiveTab: any; + showPanel: any; + hidePanel: any; + focusFindWidget: any; + hideFindWidget: any; + getFindState: any; + findNext: any; + findPrevious: any; + setContainers: any; + getDefaultShell: any; + selectDefaultWindowsShell: any; + setWorkspaceShellAllowed: any; + preparePathForTerminalAsync: any; + extHostReady() { } + requestExtHostProcess: any; +} +registerSingleton(ITerminalService, SimpleTerminalService, true); + +//#endregion + +//#region ITaskService +export class SimpleTaskService implements ITaskService { + _serviceBrand: any; + onDidStateChange: Event = Event.None; + supportsMultipleTaskExecutions: boolean; + configureAction: any; + build: any; + runTest: any; + run: any; + inTerminal: any; + isActive: any; + getActiveTasks: any; + restart: any; + terminate: any; + terminateAll: any; + tasks: any; + getWorkspaceTasks: any; + getTask: any; + getTasksForGroup: any; + getRecentlyUsedTasks: any; + createSorter: any; + needsFolderQualification: any; + canCustomize: any; + customize: any; + openConfig: any; + registerTaskProvider() { return Disposable.None; } + registerTaskSystem() { } + extensionCallbackTaskComplete: any; +} +registerSingleton(ITaskService, SimpleTaskService, true); +//#endregion + +//#region ICommentService +export class SimpleCommentService implements ICommentService { + _serviceBrand: any; + onDidSetResourceCommentInfos: Event = Event.None; + onDidSetAllCommentThreads: Event = Event.None; + onDidUpdateCommentThreads: Event = Event.None; + onDidChangeActiveCommentingRange: Event<{ range: Range; commentingRangesInfo: CommentingRanges; }> = Event.None; + onDidChangeActiveCommentThread: Event = Event.None; + onDidSetDataProvider: Event = Event.None; + onDidDeleteDataProvider: Event = Event.None; + setDocumentComments: any; + setWorkspaceComments: any; + removeWorkspaceComments: any; + registerCommentController: any; + unregisterCommentController: any; + getCommentController: any; + createCommentThreadTemplate: any; + updateCommentThreadTemplate: any; + getCommentMenus: any; + registerDataProvider: any; + unregisterDataProvider: any; + updateComments: any; + disposeCommentThread: any; + createNewCommentThread: any; + replyToCommentThread: any; + editComment: any; + deleteComment: any; + getComments() { return Promise.resolve([]); } + getCommentingRanges: any; + startDraft: any; + deleteDraft: any; + finishDraft: any; + getStartDraftLabel: any; + getDeleteDraftLabel: any; + getFinishDraftLabel: any; + addReaction: any; + deleteReaction: any; + getReactionGroup: any; + toggleReaction: any; + setActiveCommentThread: any; +} +registerSingleton(ICommentService, SimpleCommentService, true); +//#endregion + //#region Extension Management //#region Extension Enablement @@ -360,10 +556,10 @@ export class SimpleExtensionEnablementService implements IExtensionEnablementSer readonly onEnablementChanged = Event.None; - readonly allUserExtensionsDisabled = true; + readonly allUserExtensionsDisabled = false; getEnablementState(extension: IExtension): EnablementState { - return EnablementState.Disabled; + return EnablementState.Enabled; } canChangeEnablement(extension: IExtension): boolean { @@ -375,7 +571,7 @@ export class SimpleExtensionEnablementService implements IExtensionEnablementSer } isEnabled(extension: IExtension): boolean { - return false; + return true; } } @@ -479,14 +675,6 @@ registerSingleton(IExtensionManagementService, SimpleExtensionManagementService) //#endregion -//#region Extensions - -export class SimpleExtensionService extends NullExtensionService { } - -registerSingleton(IExtensionService, SimpleExtensionService); - -//#endregion - //#region Extension URL Handler export const IExtensionUrlHandler = createDecorator('inactiveExtensionUrlHandler'); @@ -582,21 +770,6 @@ export class SimpleLogService extends ConsoleLogService { } //#endregion -//#region Menu Bar - -export class SimpleMenubarService implements IMenubarService { - - _serviceBrand: any; - - updateMenubar(windowId: number, menuData: IMenubarData): Promise { - return Promise.resolve(undefined); - } -} - -registerSingleton(IMenubarService, SimpleMenubarService); - -//#endregion - //#region Multi Extension Management export class SimpleMultiExtensionsManagementService implements IExtensionManagementService { @@ -659,9 +832,12 @@ export class SimpleProductService implements IProductService { _serviceBrand: any; - version?: string; + version: string = '1.35.0'; commit?: string; - + nameLong: string = ''; + urlProtocol: string = ''; + extensionAllowedProposedApi: string[] = []; + uiExtensions?: string[]; enableTelemetry: boolean = false; } @@ -821,26 +997,6 @@ registerSingleton(ITelemetryService, SimpleTelemetryService); //#endregion -//#region Textmate - -TokenizationRegistry.setColorMap([null, new Color(new RGBA(212, 212, 212, 1)), new Color(new RGBA(30, 30, 30, 1))]); - -export class SimpleTextMateService implements ITextMateService { - - _serviceBrand: any; - - readonly onDidEncounterLanguage: Event = Event.None; - - createGrammar(modeId: string): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } -} - -registerSingleton(ITextMateService, SimpleTextMateService, true); - -//#endregion - //#region Text Resource Properties export class SimpleTextResourcePropertiesService extends SimpleResourcePropertiesService { } @@ -923,7 +1079,7 @@ export class SimpleWindowConfiguration implements IWindowConfiguration { workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; - remoteAuthority?: string; + remoteAuthority: string = document.location.host; zoomLevel?: number; fullscreen?: boolean; @@ -1071,6 +1227,30 @@ registerSingleton(IWindowService, SimpleWindowService); //#endregion +//#region ExtensionHostDebugService + +export class SimpleExtensionHostDebugService implements IExtensionHostDebugService { + _serviceBrand: any; + + reload(sessionId: string): void { } + onReload: Event = Event.None; + + close(sessionId: string): void { } + onClose: Event = Event.None; + + attachSession(sessionId: string, port: number, subId?: string): void { } + onAttachSession: Event = Event.None; + + logToSession(sessionId: string, log: IRemoteConsoleLog): void { } + onLogToSession: Event = Event.None; + + terminateSession(sessionId: string, subId?: string): void { } + onTerminateSession: Event = Event.None; +} +registerSingleton(IExtensionHostDebugService, SimpleExtensionHostDebugService); + +//#endregion + //#region Window export class SimpleWindowsService implements IWindowsService { @@ -1337,66 +1517,6 @@ registerSingleton(IWorkspaceEditingService, SimpleWorkspaceEditingService, true) //#endregion -//#region Workspace - -export class SimpleWorkspaceService implements IWorkspaceContextService { - _serviceBrand: any; - - private workspace: Workspace; - - readonly onDidChangeWorkspaceName = Event.None; - readonly onDidChangeWorkspaceFolders = Event.None; - readonly onDidChangeWorkbenchState = Event.None; - - constructor() { - this.workspace = new Workspace(workspaceResource.toString(), [toWorkspaceFolder(workspaceResource)]); - } - - getFolders(): IWorkspaceFolder[] { - return this.workspace ? this.workspace.folders : []; - } - - getWorkbenchState(): WorkbenchState { - if (this.workspace.configuration) { - return WorkbenchState.WORKSPACE; - } - - if (this.workspace.folders.length) { - return WorkbenchState.FOLDER; - } - - return WorkbenchState.EMPTY; - } - - getCompleteWorkspace(): Promise { - return Promise.resolve(this.getWorkspace()); - } - - getWorkspace(): IWorkspace { - return this.workspace; - } - - getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { - return this.workspace.getFolder(resource); - } - - isInsideWorkspace(resource: URI): boolean { - if (resource && this.workspace) { - return isEqualOrParent(resource, this.workspace.folders[0].uri); - } - - return false; - } - - isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { - return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && isEqual(this.workspace.folders[0].uri, workspaceIdentifier); - } -} - -registerSingleton(IWorkspaceContextService, SimpleWorkspaceService); - -//#endregion - //#region Workspaces export class SimpleWorkspacesService implements IWorkspacesService { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 4d89f096e8d8..1df527c99eda 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -180,6 +180,12 @@ import { isMacintosh } from 'vs/base/common/platform'; 'default': true, 'description': nls.localize('activityBarVisibility', "Controls the visibility of the activity bar in the workbench.") }, + // TODO @misolori remove before shipping stable + 'workbench.iconExploration.enabled': { + 'type': 'boolean', + 'default': false, + 'description': nls.localize('iconExplorationEnabled', "Controls the visibility of the icon exploration in the workbench.") + }, 'workbench.view.alwaysShowHeaderActions': { 'type': 'boolean', 'default': false, diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index fd7bcdb15ddb..0e4c78e686a1 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -267,6 +267,7 @@ export class Workbench extends Layout { const workbenchClasses = coalesce([ 'monaco-workbench', platformClass, + isWeb ? 'web' : undefined, this.state.sideBar.hidden ? 'nosidebar' : undefined, this.state.panel.hidden ? 'nopanel' : undefined, this.state.statusBar.hidden ? 'nostatusbar' : undefined, diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index 14b786ff7448..51eb00557b72 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -8,7 +8,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { SyncActionDescriptor, MenuRegistry, MenuId, ICommandAction } from 'vs/platform/actions/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -33,10 +33,10 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR } private registerWorkbenchCommandFromAction(descriptor: SyncActionDescriptor, alias: string, category?: string, when?: ContextKeyExpr): IDisposable { - let registrations: IDisposable[] = []; + const registrations = new DisposableStore(); // command - registrations.push(CommandsRegistry.registerCommand(descriptor.id, this.createCommandHandler(descriptor))); + registrations.add(CommandsRegistry.registerCommand(descriptor.id, this.createCommandHandler(descriptor))); // keybinding const weight = (typeof descriptor.keybindingWeight === 'undefined' ? KeybindingWeight.WorkbenchContrib : descriptor.keybindingWeight); @@ -72,13 +72,13 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR MenuRegistry.addCommand(command); - registrations.push(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command, when })); + registrations.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command, when })); } // TODO@alex,joh // support removal of keybinding rule // support removal of command-ui - return combinedDisposable(registrations); + return registrations; } private createCommandHandler(descriptor: SyncActionDescriptor): ICommandHandler { diff --git a/src/vs/workbench/common/component.ts b/src/vs/workbench/common/component.ts index c956882eaf48..b758771624a8 100644 --- a/src/vs/workbench/common/component.ts +++ b/src/vs/workbench/common/component.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Memento } from 'vs/workbench/common/memento'; +import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Themable } from 'vs/workbench/common/theme'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -35,7 +35,7 @@ export class Component extends Themable { return this.id; } - protected getMemento(scope: StorageScope): object { + protected getMemento(scope: StorageScope): MementoObject { return this.memento.getMemento(scope); } diff --git a/src/vs/workbench/common/composite.ts b/src/vs/workbench/common/composite.ts index c3a6e069379f..a6e6a00c8ac6 100644 --- a/src/vs/workbench/common/composite.ts +++ b/src/vs/workbench/common/composite.ts @@ -20,17 +20,17 @@ export interface IComposite { /** * Returns the primary actions of the composite. */ - getActions(): IAction[]; + getActions(): ReadonlyArray; /** * Returns the secondary actions of the composite. */ - getSecondaryActions(): IAction[]; + getSecondaryActions(): ReadonlyArray; /** * Returns an array of actions to show in the context menu of the composite */ - getContextMenuActions(): IAction[]; + getContextMenuActions(): ReadonlyArray; /** * Returns the action item for a specific action. diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 772939fa74bc..dcdf92395d9d 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -23,7 +23,9 @@ import { coalesce } from 'vs/base/common/arrays'; export const ActiveEditorContext = new RawContextKey('activeEditor', null); export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false); +export const EditorPinnedContext = new RawContextKey('editorPinned', false); export const EditorGroupActiveEditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false); +export const EditorGroupEditorsCountContext = new RawContextKey('groupEditorsCount', 0); export const NoEditorsVisibleContext: ContextKeyExpr = EditorsVisibleContext.toNegated(); export const TextCompareEditorVisibleContext = new RawContextKey('textCompareEditorVisible', false); export const TextCompareEditorActiveContext = new RawContextKey('textCompareEditorActive', false); @@ -31,6 +33,7 @@ export const ActiveEditorGroupEmptyContext = new RawContextKey('activeE export const MultipleEditorGroupsContext = new RawContextKey('multipleEditorGroups', false); export const SingleEditorGroupsContext = MultipleEditorGroupsContext.toNegated(); export const InEditorZenModeContext = new RawContextKey('inZenMode', false); +export const IsCenteredLayoutContext = new RawContextKey('isCenteredLayout', false); export const SplitEditorsVertically = new RawContextKey('splitEditorsVertically', false); /** @@ -175,7 +178,7 @@ export interface IEditorInputFactoryRegistry { * * @param editorInputId the identifier of the editor input */ - getEditorInputFactory(editorInputId: string): IEditorInputFactory; + getEditorInputFactory(editorInputId: string): IEditorInputFactory | undefined; /** * Starts the registry by providing the required services. @@ -1058,23 +1061,22 @@ export interface IEditorMemento { class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { private instantiationService: IInstantiationService; private fileInputFactory: IFileInputFactory; - private editorInputFactoryConstructors: { [editorInputId: string]: IConstructorSignature0 } = Object.create(null); - private readonly editorInputFactoryInstances: { [editorInputId: string]: IEditorInputFactory } = Object.create(null); + private readonly editorInputFactoryConstructors: Map> = new Map(); + private readonly editorInputFactoryInstances: Map = new Map(); start(accessor: ServicesAccessor): void { this.instantiationService = accessor.get(IInstantiationService); - for (let key in this.editorInputFactoryConstructors) { - const element = this.editorInputFactoryConstructors[key]; - this.createEditorInputFactory(key, element); - } + this.editorInputFactoryConstructors.forEach((ctor, key) => { + this.createEditorInputFactory(key, ctor); + }); - this.editorInputFactoryConstructors = Object.create(null); + this.editorInputFactoryConstructors.clear(); } private createEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): void { const instance = this.instantiationService.createInstance(ctor); - this.editorInputFactoryInstances[editorInputId] = instance; + this.editorInputFactoryInstances.set(editorInputId, instance); } registerFileInputFactory(factory: IFileInputFactory): void { @@ -1087,14 +1089,14 @@ class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): void { if (!this.instantiationService) { - this.editorInputFactoryConstructors[editorInputId] = ctor; + this.editorInputFactoryConstructors.set(editorInputId, ctor); } else { this.createEditorInputFactory(editorInputId, ctor); } } - getEditorInputFactory(editorInputId: string): IEditorInputFactory { - return this.editorInputFactoryInstances[editorInputId]; + getEditorInputFactory(editorInputId: string): IEditorInputFactory | undefined { + return this.editorInputFactoryInstances.get(editorInputId); } } diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 25ef75d54f9d..6905289180ef 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -90,7 +90,7 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { ref.dispose(); this.modelReference = null; - return Promise.reject(new Error(`Unexpected model for ResourceInput: ${this.resource}`)); + throw new Error(`Unexpected model for ResourceInput: ${this.resource}`); } this.cachedModel = model; diff --git a/src/vs/workbench/common/memento.ts b/src/vs/workbench/common/memento.ts index 7f1e366d88a6..7e3cb895ed0e 100644 --- a/src/vs/workbench/common/memento.ts +++ b/src/vs/workbench/common/memento.ts @@ -6,10 +6,12 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { isEmptyObject } from 'vs/base/common/types'; +export type MementoObject = { [key: string]: any }; + export class Memento { - private static globalMementos: { [id: string]: ScopedMemento } = Object.create(null); - private static workspaceMementos: { [id: string]: ScopedMemento } = Object.create(null); + private static readonly globalMementos = new Map(); + private static readonly workspaceMementos = new Map(); private static readonly COMMON_PREFIX = 'memento/'; @@ -19,24 +21,24 @@ export class Memento { this.id = Memento.COMMON_PREFIX + id; } - getMemento(scope: StorageScope): object { + getMemento(scope: StorageScope): MementoObject { // Scope by Workspace if (scope === StorageScope.WORKSPACE) { - let workspaceMemento = Memento.workspaceMementos[this.id]; + let workspaceMemento = Memento.workspaceMementos.get(this.id); if (!workspaceMemento) { workspaceMemento = new ScopedMemento(this.id, scope, this.storageService); - Memento.workspaceMementos[this.id] = workspaceMemento; + Memento.workspaceMementos.set(this.id, workspaceMemento); } return workspaceMemento.getMemento(); } // Scope Global - let globalMemento = Memento.globalMementos[this.id]; + let globalMemento = Memento.globalMementos.get(this.id); if (!globalMemento) { globalMemento = new ScopedMemento(this.id, scope, this.storageService); - Memento.globalMementos[this.id] = globalMemento; + Memento.globalMementos.set(this.id, globalMemento); } return globalMemento.getMemento(); @@ -45,13 +47,13 @@ export class Memento { saveMemento(): void { // Workspace - const workspaceMemento = Memento.workspaceMementos[this.id]; + const workspaceMemento = Memento.workspaceMementos.get(this.id); if (workspaceMemento) { workspaceMemento.save(); } // Global - const globalMemento = Memento.globalMementos[this.id]; + const globalMemento = Memento.globalMementos.get(this.id); if (globalMemento) { globalMemento.save(); } @@ -59,17 +61,17 @@ export class Memento { } class ScopedMemento { - private readonly mementoObj: object; + private readonly mementoObj: MementoObject; constructor(private id: string, private scope: StorageScope, private storageService: IStorageService) { this.mementoObj = this.load(); } - getMemento(): object { + getMemento(): MementoObject { return this.mementoObj; } - private load(): object { + private load(): MementoObject { const memento = this.storageService.get(this.id, this.scope); if (memento) { return JSON.parse(memento); diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 2ee1ceefff5e..6cac2f86b1b7 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -3,10 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice } from 'vs/platform/notification/common/notification'; +import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage, IPromptChoice, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { Action } from 'vs/base/common/actions'; import { isErrorWithActions } from 'vs/base/common/errorsWithActions'; @@ -15,10 +15,25 @@ import { localize } from 'vs/nls'; export interface INotificationsModel { + // + // Notifications as Toasts/Center + // + readonly notifications: INotificationViewItem[]; + readonly onDidNotificationChange: Event; - notify(notification: INotification): INotificationHandle; + addNotification(notification: INotification): INotificationHandle; + + // + // Notifications as Status + // + + readonly statusMessage: IStatusMessageViewItem | undefined; + + readonly onDidStatusMessageChange: Event; + + showStatusMessage(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable; } export const enum NotificationChangeType { @@ -45,6 +60,29 @@ export interface INotificationChangeEvent { kind: NotificationChangeType; } +export const enum StatusMessageChangeType { + ADD, + REMOVE +} + +export interface IStatusMessageViewItem { + message: string; + options?: IStatusMessageOptions; +} + +export interface IStatusMessageChangeEvent { + + /** + * The status message item this change is about. + */ + item: IStatusMessageViewItem; + + /** + * The kind of status message change. + */ + kind: StatusMessageChangeType; +} + export class NotificationHandle implements INotificationHandle { private readonly _onDidClose: Emitter = new Emitter(); @@ -90,13 +128,16 @@ export class NotificationsModel extends Disposable implements INotificationsMode private readonly _onDidNotificationChange: Emitter = this._register(new Emitter()); get onDidNotificationChange(): Event { return this._onDidNotificationChange.event; } + private readonly _onDidStatusMessageChange: Emitter = this._register(new Emitter()); + get onDidStatusMessageChange(): Event { return this._onDidStatusMessageChange.event; } + private readonly _notifications: INotificationViewItem[] = []; + get notifications(): INotificationViewItem[] { return this._notifications; } - get notifications(): INotificationViewItem[] { - return this._notifications; - } + private _statusMessage: IStatusMessageViewItem | undefined; + get statusMessage(): IStatusMessageViewItem | undefined { return this._statusMessage; } - notify(notification: INotification): INotificationHandle { + addNotification(notification: INotification): INotificationHandle { const item = this.createViewItem(notification); if (!item) { return NotificationsModel.NO_OP_NOTIFICATION; // return early if this is a no-op @@ -174,6 +215,26 @@ export class NotificationsModel extends Disposable implements INotificationsMode return item; } + + showStatusMessage(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable { + const item = StatusMessageViewItem.create(message, options); + if (!item) { + return Disposable.None; + } + + // Remember as current status message and fire events + this._statusMessage = item; + this._onDidStatusMessageChange.fire({ kind: StatusMessageChangeType.ADD, item }); + + return toDisposable(() => { + + // Only reset status message if the item is still the one we had remembered + if (this._statusMessage === item) { + this._statusMessage = undefined; + this._onDidStatusMessageChange.fire({ kind: StatusMessageChangeType.REMOVE, item }); + } + }); + } } export interface INotificationViewItem { @@ -621,4 +682,26 @@ export class ChoiceAction extends Action { this._onDidRun.dispose(); } +} + +class StatusMessageViewItem { + + static create(notification: NotificationMessage, options?: IStatusMessageOptions): IStatusMessageViewItem | null { + if (!notification || isPromiseCanceledError(notification)) { + return null; // we need a message to show + } + + let message: string | undefined; + if (notification instanceof Error) { + message = toErrorMessage(notification, false); + } else if (typeof notification === 'string') { + message = notification; + } + + if (!message) { + return null; // we need a message to show + } + + return { message, options }; + } } \ No newline at end of file diff --git a/src/vs/workbench/common/panel.ts b/src/vs/workbench/common/panel.ts index 0600d9d7876a..ed99ea801792 100644 --- a/src/vs/workbench/common/panel.ts +++ b/src/vs/workbench/common/panel.ts @@ -8,5 +8,6 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const ActivePanelContext = new RawContextKey('activePanel', ''); export const PanelFocusContext = new RawContextKey('panelFocus', false); +export const PanelPositionContext = new RawContextKey('panelPosition', 'bottom'); export interface IPanel extends IComposite { } diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 030ec18d673c..e66ae48327fc 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -30,6 +30,12 @@ export const TAB_ACTIVE_BACKGROUND = registerColor('tab.activeBackground', { hc: editorBackground }, nls.localize('tabActiveBackground', "Active tab background color. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_UNFOCUSED_ACTIVE_BACKGROUND = registerColor('tab.unfocusedActiveBackground', { + dark: TAB_ACTIVE_BACKGROUND, + light: TAB_ACTIVE_BACKGROUND, + hc: TAB_ACTIVE_BACKGROUND +}, nls.localize('tabUnfocusedActiveBackground', "Active tab background color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + export const TAB_INACTIVE_BACKGROUND = registerColor('tab.inactiveBackground', { dark: '#2D2D2D', light: '#ECECEC', @@ -138,7 +144,6 @@ export const TAB_UNFOCUSED_INACTIVE_FOREGROUND = registerColor('tab.unfocusedIna hc: Color.white }, nls.localize('tabUnfocusedInactiveForeground', "Inactive tab foreground color in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); - // < --- Editors --- > export const EDITOR_PANE_BACKGROUND = registerColor('editorPane.background', { @@ -570,4 +575,4 @@ export class Themable extends Disposable { return color ? color.toString() : null; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index e5c7207e3233..ce58032c378b 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -11,7 +11,7 @@ import { ITreeViewDataProvider } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { values, keys } from 'vs/base/common/map'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -71,12 +71,12 @@ export class ViewContainer { protected constructor(readonly id: string, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier) { } } -class ViewContainersRegistryImpl implements IViewContainersRegistry { +class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry { - private readonly _onDidRegister = new Emitter(); + private readonly _onDidRegister = this._register(new Emitter()); readonly onDidRegister: Event = this._onDidRegister.event; - private readonly _onDidDeregister = new Emitter(); + private readonly _onDidDeregister = this._register(new Emitter()); readonly onDidDeregister: Event = this._onDidDeregister.event; private viewContainers: Map = new Map(); @@ -169,15 +169,15 @@ export interface IViewsRegistry { getViewContainer(id: string): ViewContainer | null; } -class ViewsRegistry implements IViewsRegistry { +class ViewsRegistry extends Disposable implements IViewsRegistry { - private readonly _onViewsRegistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>(); + private readonly _onViewsRegistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>()); readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsRegistered.event; - private readonly _onViewsDeregistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>(); + private readonly _onViewsDeregistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>()); readonly onViewsDeregistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsDeregistered.event; - private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>(); + private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>()); readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._onDidChangeContainer.event; private _viewContainers: ViewContainer[] = []; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 88749118a423..501d3b29e5bd 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -117,7 +117,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { this.create(); this._peekViewService.addExclusiveWidget(editor, this); this._applyTheme(themeService.getTheme()); - themeService.onThemeChange(this._applyTheme, this, this._disposables); + this._disposables.add(themeService.onThemeChange(this._applyTheme, this)); } dispose(): void { @@ -230,18 +230,18 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { } }, Sizing.Distribute); - this._splitView.onDidSashChange(() => { + this._disposables.add(this._splitView.onDidSashChange(() => { if (this._dim.width) { this._layoutInfo.ratio = this._splitView.getViewSize(0) / this._dim.width; } - }, undefined, this._disposables); + })); // session state let localDispose: IDisposable[] = []; - this._disposables.push({ dispose() { dispose(localDispose); } }); + this._disposables.add({ dispose() { dispose(localDispose); } }); // update editor - this._tree.onDidChangeFocus(e => { + this._disposables.add(this._tree.onDidChangeFocus(e => { const [element] = e.elements; if (element && isNonEmptyArray(element.locations)) { @@ -287,9 +287,9 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { } this.setMetaTitle(localize('meta', " – {0}", names.join(' → '))); } - }, undefined, this._disposables); + })); - this._editor.onMouseDown(e => { + this._disposables.add(this._editor.onMouseDown(e => { const { event, target } = e; if (event.detail !== 2) { return; @@ -304,9 +304,9 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { options: { selection: target.range! } }); - }, undefined, this._disposables); + })); - this._tree.onMouseDblClick(e => { + this._disposables.add(this._tree.onMouseDblClick(e => { if (e.element && isNonEmptyArray(e.element.locations)) { this.dispose(); this._editorService.openEditor({ @@ -314,9 +314,9 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { options: { selection: e.element.locations[0].range } }); } - }, undefined, this._disposables); + })); - this._tree.onDidChangeSelection(e => { + this._disposables.add(this._tree.onDidChangeSelection(e => { const [element] = e.elements; // don't close on click if (element && isNonEmptyArray(element.locations) && e.browserEvent instanceof KeyboardEvent) { @@ -326,7 +326,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { options: { selection: element.locations[0].range } }); } - }, undefined, this._disposables); + })); } showLoading(): void { @@ -380,7 +380,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { } }; this._changeDirectionAction = new ChangeHierarchyDirectionAction(this._direction, changeDirection); - this._disposables.push(this._changeDirectionAction); + this._disposables.add(this._changeDirectionAction); this._actionbarWidget.push(this._changeDirectionAction, { icon: true, label: false }); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts index d460878a29ad..cae81e789ba4 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts @@ -32,14 +32,14 @@ export class ToggleMinimapAction extends Action { const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMinimapAction, ToggleMinimapAction.ID, ToggleMinimapAction.LABEL), 'View: Toggle Minimap'); -// {{SQL CARBON EDIT}} - Disable unused menu item -// MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { -// group: '5_editor', -// command: { -// id: ToggleMinimapAction.ID, -// title: nls.localize({ key: 'miToggleMinimap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Minimap"), -// toggled: ContextKeyExpr.equals('config.editor.minimap.enabled', true) -// }, -// order: 2 -// }); -// {{SQL CARBON EDIT}} - End +/* {{SQL CARBON EDIT}} - Disable unused menu item +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '5_editor', + command: { + id: ToggleMinimapAction.ID, + title: nls.localize({ key: 'miShowMinimap', comment: ['&& denotes a mnemonic'] }, "Show &&Minimap"), + toggled: ContextKeyExpr.equals('config.editor.minimap.enabled', true) + }, + order: 2 +}); +*/ diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts index 87785df7c716..7283241db6c4 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts @@ -33,14 +33,14 @@ export class ToggleRenderControlCharacterAction extends Action { const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRenderControlCharacterAction, ToggleRenderControlCharacterAction.ID, ToggleRenderControlCharacterAction.LABEL), 'View: Toggle Control Characters'); -// {{SQL CARBON EDIT}} - Disable unused menu item -// MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { -// group: '5_editor', -// command: { -// id: ToggleRenderControlCharacterAction.ID, -// title: nls.localize({ key: 'miToggleRenderControlCharacters', comment: ['&& denotes a mnemonic'] }, "Toggle &&Control Characters"), -// toggled: ContextKeyExpr.equals('config.editor.renderControlCharacters', true) -// }, -// order: 4 -// }); -// {{SQL CARBON EDIT}} - End +/* {{SQL CARBON EDIT}} - Disable unused menu item +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '5_editor', + command: { + id: ToggleRenderControlCharacterAction.ID, + title: nls.localize({ key: 'miToggleRenderControlCharacters', comment: ['&& denotes a mnemonic'] }, "Render &&Control Characters"), + toggled: ContextKeyExpr.equals('config.editor.renderControlCharacters', true) + }, + order: 5 +}); +*/ diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts index 5a219f3b4a5d..b182853a2cd0 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts @@ -41,14 +41,14 @@ export class ToggleRenderWhitespaceAction extends Action { const registry = Registry.as(ActionExtensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleRenderWhitespaceAction, ToggleRenderWhitespaceAction.ID, ToggleRenderWhitespaceAction.LABEL), 'View: Toggle Render Whitespace'); -// {{SQL CARBON EDIT}} - Disable unused menu item -// MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { -// group: '5_editor', -// command: { -// id: ToggleRenderWhitespaceAction.ID, -// title: nls.localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "Toggle &&Render Whitespace"), -// toggled: ContextKeyExpr.notEquals('config.editor.renderWhitespace', 'none') -// }, -// order: 3 -// }); -// {{SQL CARBON EDIT}} - End +/* {{SQL CARBON EDIT}} - Disable unused menu item +MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '5_editor', + command: { + id: ToggleRenderWhitespaceAction.ID, + title: nls.localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "&&Render Whitespace"), + toggled: ContextKeyExpr.notEquals('config.editor.renderWhitespace', 'none') + }, + order: 4 +}); +*/ diff --git a/src/vs/workbench/contrib/codeinset/common/codeInset.ts b/src/vs/workbench/contrib/codeinset/common/codeInset.ts deleted file mode 100644 index 3ef92e295dd1..000000000000 --- a/src/vs/workbench/contrib/codeinset/common/codeInset.ts +++ /dev/null @@ -1,70 +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 { ITextModel } from 'vs/editor/common/model'; -import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { mergeSort } from 'vs/base/common/arrays'; -import { Event } from 'vs/base/common/event'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; -import { ProviderResult } from 'vs/editor/common/modes'; -import { IRange } from 'vs/editor/common/core/range'; - -export interface ICodeInsetSymbol { - id: string; - range: IRange; - height?: number; -} - -export interface CodeInsetProvider { - onDidChange?: Event; - provideCodeInsets(model: ITextModel, token: CancellationToken): ProviderResult; - resolveCodeInset(model: ITextModel, codeInset: ICodeInsetSymbol, token: CancellationToken): ProviderResult; -} - -export const CodeInsetProviderRegistry = new LanguageFeatureRegistry(); - -export interface ICodeInsetData { - symbol: ICodeInsetSymbol; - provider: CodeInsetProvider; - resolved?: boolean; -} - -export function getCodeInsetData(model: ITextModel, token: CancellationToken): Promise { - - const symbols: ICodeInsetData[] = []; - const providers = CodeInsetProviderRegistry.ordered(model); - - const promises = providers.map(provider => - Promise.resolve(provider.provideCodeInsets(model, token)).then(result => { - if (Array.isArray(result)) { - for (let symbol of result) { - symbols.push({ symbol, provider }); - } - } - }).catch(onUnexpectedExternalError)); - - return Promise.all(promises).then(() => { - - return mergeSort(symbols, (a, b) => { - // sort by lineNumber, provider-rank, and column - if (a.symbol.range.startLineNumber < b.symbol.range.startLineNumber) { - return -1; - } else if (a.symbol.range.startLineNumber > b.symbol.range.startLineNumber) { - return 1; - } else if (providers.indexOf(a.provider) < providers.indexOf(b.provider)) { - return -1; - } else if (providers.indexOf(a.provider) > providers.indexOf(b.provider)) { - return 1; - } else if (a.symbol.range.startColumn < b.symbol.range.startColumn) { - return -1; - } else if (a.symbol.range.startColumn > b.symbol.range.startColumn) { - return 1; - } else { - return 0; - } - }); - }); -} diff --git a/src/vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution.ts b/src/vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution.ts deleted file mode 100644 index 303fd1700e37..000000000000 --- a/src/vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution.ts +++ /dev/null @@ -1,353 +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 { CancelablePromise, createCancelablePromise, RunOnceScheduler } from 'vs/base/common/async'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; -import * as editorBrowser from 'vs/editor/browser/editorBrowser'; -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; -import { CodeInsetProviderRegistry, getCodeInsetData, ICodeInsetData } from '../common/codeInset'; -import { CodeInsetWidget, CodeInsetHelper } from './codeInsetWidget'; -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; -// import { localize } from 'vs/nls'; - -export class CodeInsetController implements editorCommon.IEditorContribution { - - static get(editor: editorBrowser.ICodeEditor): CodeInsetController { - return editor.getContribution(CodeInsetController.ID); - } - - private static readonly ID: string = 'css.editor.codeInset'; - - private _isEnabled: boolean; - - private _globalToDispose: IDisposable[]; - private _localToDispose: IDisposable[]; - private _insetWidgets: CodeInsetWidget[]; - private _pendingWebviews = new Map any>(); - private _currentFindCodeInsetSymbolsPromise: CancelablePromise | null; - private _modelChangeCounter: number; - private _currentResolveCodeInsetSymbolsPromise: CancelablePromise | null; - private _detectVisibleInsets: RunOnceScheduler; - - constructor( - private _editor: editorBrowser.ICodeEditor, - @IConfigurationService private readonly _configService: IConfigurationService, - ) { - this._isEnabled = this._configService.getValue('editor.codeInsets'); - - this._globalToDispose = []; - this._localToDispose = []; - this._insetWidgets = []; - this._currentFindCodeInsetSymbolsPromise = null; - this._modelChangeCounter = 0; - - this._globalToDispose.push(this._editor.onDidChangeModel(() => this._onModelChange())); - this._globalToDispose.push(this._editor.onDidChangeModelLanguage(() => this._onModelChange())); - this._globalToDispose.push(this._configService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.codeInsets')) { - let prevIsEnabled = this._isEnabled; - this._isEnabled = this._configService.getValue('editor.codeInsets'); - if (prevIsEnabled !== this._isEnabled) { - this._onModelChange(); - } - } - })); - this._globalToDispose.push(CodeInsetProviderRegistry.onDidChange(this._onModelChange, this)); - this._onModelChange(); - } - - dispose(): void { - this._localDispose(); - this._globalToDispose = dispose(this._globalToDispose); - } - - acceptWebview(symbolId: string, webviewElement: WebviewElement): boolean { - const pendingWebview = this._pendingWebviews.get(symbolId); - if (pendingWebview) { - pendingWebview(webviewElement); - this._pendingWebviews.delete(symbolId); - return true; - } - return false; - } - - private _localDispose(): void { - if (this._currentFindCodeInsetSymbolsPromise) { - this._currentFindCodeInsetSymbolsPromise.cancel(); - this._currentFindCodeInsetSymbolsPromise = null; - this._modelChangeCounter++; - } - if (this._currentResolveCodeInsetSymbolsPromise) { - this._currentResolveCodeInsetSymbolsPromise.cancel(); - this._currentResolveCodeInsetSymbolsPromise = null; - } - this._localToDispose = dispose(this._localToDispose); - } - - getId(): string { - return CodeInsetController.ID; - } - - private _onModelChange(): void { - this._localDispose(); - - const model = this._editor.getModel(); - if (!model || !this._isEnabled || !CodeInsetProviderRegistry.has(model)) { - return; - } - - for (const provider of CodeInsetProviderRegistry.all(model)) { - if (typeof provider.onDidChange === 'function') { - let registration = provider.onDidChange(() => scheduler.schedule()); - this._localToDispose.push(registration); - } - } - - this._detectVisibleInsets = new RunOnceScheduler(() => { - this._onViewportChanged(); - }, 500); - - const scheduler = new RunOnceScheduler(() => { - const counterValue = ++this._modelChangeCounter; - if (this._currentFindCodeInsetSymbolsPromise) { - this._currentFindCodeInsetSymbolsPromise.cancel(); - } - - this._currentFindCodeInsetSymbolsPromise = createCancelablePromise(token => getCodeInsetData(model, token)); - - this._currentFindCodeInsetSymbolsPromise.then(codeInsetData => { - if (counterValue === this._modelChangeCounter) { // only the last one wins - this._renderCodeInsetSymbols(codeInsetData); - this._detectVisibleInsets.schedule(); - } - }, onUnexpectedError); - }, 250); - - this._localToDispose.push(scheduler); - - this._localToDispose.push(this._detectVisibleInsets); - - this._localToDispose.push(this._editor.onDidChangeModelContent(() => { - this._editor.changeDecorations(changeAccessor => { - this._editor.changeViewZones(viewAccessor => { - let toDispose: CodeInsetWidget[] = []; - let lastInsetLineNumber: number = -1; - this._insetWidgets.forEach(inset => { - if (!inset.isValid() || lastInsetLineNumber === inset.getLineNumber()) { - // invalid -> Inset collapsed, attach range doesn't exist anymore - // line_number -> insets should never be on the same line - toDispose.push(inset); - } - else { - inset.reposition(viewAccessor); - lastInsetLineNumber = inset.getLineNumber(); - } - }); - let helper = new CodeInsetHelper(); - toDispose.forEach((l) => { - l.dispose(helper, viewAccessor); - this._insetWidgets.splice(this._insetWidgets.indexOf(l), 1); - }); - helper.commit(changeAccessor); - }); - }); - // Compute new `visible` code insets - this._detectVisibleInsets.schedule(); - // Ask for all references again - scheduler.schedule(); - })); - - this._localToDispose.push(this._editor.onDidScrollChange(e => { - if (e.scrollTopChanged && this._insetWidgets.length > 0) { - this._detectVisibleInsets.schedule(); - } - })); - - this._localToDispose.push(this._editor.onDidLayoutChange(() => { - this._detectVisibleInsets.schedule(); - })); - - this._localToDispose.push(toDisposable(() => { - if (this._editor.getModel()) { - const scrollState = StableEditorScrollState.capture(this._editor); - this._editor.changeDecorations((changeAccessor) => { - this._editor.changeViewZones((accessor) => { - this._disposeAllInsets(changeAccessor, accessor); - }); - }); - scrollState.restore(this._editor); - } else { - // No accessors available - this._disposeAllInsets(null, null); - } - })); - - scheduler.schedule(); - } - - private _disposeAllInsets(decChangeAccessor: IModelDecorationsChangeAccessor | null, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | null): void { - let helper = new CodeInsetHelper(); - this._insetWidgets.forEach((Inset) => Inset.dispose(helper, viewZoneChangeAccessor)); - if (decChangeAccessor) { - helper.commit(decChangeAccessor); - } - this._insetWidgets = []; - } - - private _renderCodeInsetSymbols(symbols: ICodeInsetData[]): void { - if (!this._editor.hasModel()) { - return; - } - - let maxLineNumber = this._editor.getModel().getLineCount(); - let groups: ICodeInsetData[][] = []; - let lastGroup: ICodeInsetData[] | undefined; - - for (let symbol of symbols) { - let line = symbol.symbol.range.startLineNumber; - if (line < 1 || line > maxLineNumber) { - // invalid code Inset - continue; - } else if (lastGroup && lastGroup[lastGroup.length - 1].symbol.range.startLineNumber === line) { - // on same line as previous - lastGroup.push(symbol); - } else { - // on later line as previous - lastGroup = [symbol]; - groups.push(lastGroup); - } - } - - const scrollState = StableEditorScrollState.capture(this._editor); - - this._editor.changeDecorations(changeAccessor => { - this._editor.changeViewZones(accessor => { - - let codeInsetIndex = 0, groupsIndex = 0, helper = new CodeInsetHelper(); - - while (groupsIndex < groups.length && codeInsetIndex < this._insetWidgets.length) { - - let symbolsLineNumber = groups[groupsIndex][0].symbol.range.startLineNumber; - let codeInsetLineNumber = this._insetWidgets[codeInsetIndex].getLineNumber(); - - if (codeInsetLineNumber < symbolsLineNumber) { - this._insetWidgets[codeInsetIndex].dispose(helper, accessor); - this._insetWidgets.splice(codeInsetIndex, 1); - } else if (codeInsetLineNumber === symbolsLineNumber) { - this._insetWidgets[codeInsetIndex].updateCodeInsetSymbols(groups[groupsIndex], helper); - groupsIndex++; - codeInsetIndex++; - } else { - this._insetWidgets.splice( - codeInsetIndex, - 0, - new CodeInsetWidget(groups[groupsIndex], this._editor, helper) - ); - codeInsetIndex++; - groupsIndex++; - } - } - - // Delete extra code insets - while (codeInsetIndex < this._insetWidgets.length) { - this._insetWidgets[codeInsetIndex].dispose(helper, accessor); - this._insetWidgets.splice(codeInsetIndex, 1); - } - - // Create extra symbols - while (groupsIndex < groups.length) { - this._insetWidgets.push(new CodeInsetWidget( - groups[groupsIndex], - this._editor, helper - )); - groupsIndex++; - } - - helper.commit(changeAccessor); - }); - }); - - scrollState.restore(this._editor); - } - - private _onViewportChanged(): void { - if (this._currentResolveCodeInsetSymbolsPromise) { - this._currentResolveCodeInsetSymbolsPromise.cancel(); - this._currentResolveCodeInsetSymbolsPromise = null; - } - - const model = this._editor.getModel(); - if (!model) { - return; - } - - const allWidgetRequests: ICodeInsetData[][] = []; - const insetWidgets: CodeInsetWidget[] = []; - this._insetWidgets.forEach(inset => { - const widgetRequests = inset.computeIfNecessary(model); - if (widgetRequests) { - allWidgetRequests.push(widgetRequests); - insetWidgets.push(inset); - } - }); - - if (allWidgetRequests.length === 0) { - return; - } - - this._currentResolveCodeInsetSymbolsPromise = createCancelablePromise(token => { - - const allPromises = allWidgetRequests.map((widgetRequests, r) => { - - const widgetPromises = widgetRequests.map(request => { - if (request.resolved) { - return Promise.resolve(undefined); - } - let a = new Promise(resolve => { - this._pendingWebviews.set(request.symbol.id, element => { - request.resolved = true; - insetWidgets[r].adoptWebview(element); - resolve(); - }); - }); - let b = request.provider.resolveCodeInset(model, request.symbol, token); - return Promise.all([a, b]); - }); - - return Promise.all(widgetPromises); - }); - - return Promise.all(allPromises); - }); - - this._currentResolveCodeInsetSymbolsPromise.then(() => { - this._currentResolveCodeInsetSymbolsPromise = null; - }).catch(err => { - this._currentResolveCodeInsetSymbolsPromise = null; - onUnexpectedError(err); - }); - } -} - -registerEditorContribution(CodeInsetController); - - -Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - id: 'editor', - properties: { - // ['editor.codeInsets']: { - // description: localize('editor.codeInsets', "Enable/disable editor code insets"), - // type: 'boolean', - // default: false - // } - } -}); diff --git a/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts b/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts deleted file mode 100644 index d7acdaf730fa..000000000000 --- a/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts +++ /dev/null @@ -1,193 +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!./codeInsetWidget'; -import { Range } from 'vs/editor/common/core/range'; -import * as editorBrowser from 'vs/editor/browser/editorBrowser'; -import { ICodeInsetData } from '../common/codeInset'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { IModelDeltaDecoration, IModelDecorationsChangeAccessor, ITextModel } from 'vs/editor/common/model'; -import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; - - -export interface IDecorationIdCallback { - (decorationId: string): void; -} - -export class CodeInsetHelper { - - private _removeDecorations: string[]; - private _addDecorations: IModelDeltaDecoration[]; - private _addDecorationsCallbacks: IDecorationIdCallback[]; - - constructor() { - this._removeDecorations = []; - this._addDecorations = []; - this._addDecorationsCallbacks = []; - } - - addDecoration(decoration: IModelDeltaDecoration, callback: IDecorationIdCallback): void { - this._addDecorations.push(decoration); - this._addDecorationsCallbacks.push(callback); - } - - removeDecoration(decorationId: string): void { - this._removeDecorations.push(decorationId); - } - - commit(changeAccessor: IModelDecorationsChangeAccessor): void { - let resultingDecorations = changeAccessor.deltaDecorations(this._removeDecorations, this._addDecorations); - for (let i = 0, len = resultingDecorations.length; i < len; i++) { - this._addDecorationsCallbacks[i](resultingDecorations[i]); - } - } -} - -export class CodeInsetWidget { - - private readonly _editor: editorBrowser.ICodeEditor; - private _webview: WebviewElement; - private _viewZone: editorBrowser.IViewZone; - private _viewZoneId?: number = undefined; - private _decorationIds: string[]; - private _data: ICodeInsetData[]; - private _range: Range; - - constructor( - data: ICodeInsetData[], // all the insets on the same line (often just one) - editor: editorBrowser.ICodeEditor, - helper: CodeInsetHelper - ) { - this._editor = editor; - this._data = data; - this._decorationIds = new Array(this._data.length); - - this._data.forEach((codeInsetData, i) => { - - helper.addDecoration({ - range: codeInsetData.symbol.range, - options: ModelDecorationOptions.EMPTY - }, id => this._decorationIds[i] = id); - - // the range contains all insets on this line - if (!this._range) { - this._range = Range.lift(codeInsetData.symbol.range); - } else { - this._range = Range.plusRange(this._range, codeInsetData.symbol.range); - } - }); - } - - public dispose(helper: CodeInsetHelper, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | null): void { - while (this._decorationIds.length) { - const decoration = this._decorationIds.pop(); - if (decoration) { - helper.removeDecoration(decoration); - } - } - if (viewZoneChangeAccessor) { - if (typeof this._viewZoneId !== 'undefined') { - viewZoneChangeAccessor.removeZone(this._viewZoneId); - } - this._viewZone = undefined!; - } - if (this._webview) { - this._webview.dispose(); - } - } - - public isValid(): boolean { - return this._editor.hasModel() && this._decorationIds.some((id, i) => { - const range = this._editor.getModel()!.getDecorationRange(id); - const symbol = this._data[i].symbol; - return !!range && Range.isEmpty(symbol.range) === range.isEmpty(); - }); - } - - public updateCodeInsetSymbols(data: ICodeInsetData[], helper: CodeInsetHelper): void { - while (this._decorationIds.length) { - const decoration = this._decorationIds.pop(); - if (decoration) { - helper.removeDecoration(decoration); - } - } - this._data = data; - this._decorationIds = new Array(this._data.length); - this._data.forEach((codeInsetData, i) => { - helper.addDecoration({ - range: codeInsetData.symbol.range, - options: ModelDecorationOptions.EMPTY - }, id => this._decorationIds[i] = id); - }); - } - - public computeIfNecessary(model: ITextModel): ICodeInsetData[] { - // Read editor current state - for (let i = 0; i < this._decorationIds.length; i++) { - const range = model.getDecorationRange(this._decorationIds[i]); - if (range) { - this._data[i].symbol.range = range; - } - } - return this._data; - } - - public getLineNumber(): number { - if (this._editor.hasModel()) { - const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); - if (range) { - return range.startLineNumber; - } - } - return -1; - } - - public adoptWebview(webview: WebviewElement): void { - - const lineNumber = this._range.endLineNumber; - this._editor.changeViewZones(accessor => { - - if (this._viewZoneId) { - accessor.removeZone(this._viewZoneId); - this._webview.dispose(); - } - - const div = document.createElement('div'); - div.className = 'code-inset'; - webview.mountTo(div); - webview.onMessage((e: { type: string, payload: any }) => { - // The webview contents can use a "size-info" message to report its size. - if (e && e.type === 'size-info') { - const margin = e.payload.height > 0 ? 5 : 0; - this._viewZone.heightInPx = e.payload.height + margin; - this._editor.changeViewZones(accessor => { - if (this._viewZoneId) { - accessor.layoutZone(this._viewZoneId); - } - }); - } - }); - this._viewZone = { - afterLineNumber: lineNumber, - heightInPx: 50, - domNode: div - }; - this._viewZoneId = accessor.addZone(this._viewZone); - this._webview = webview; - }); - } - - public reposition(viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void { - if (this.isValid() && this._editor.hasModel()) { - const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); - if (range) { - this._viewZone.afterLineNumber = range.endLineNumber; - } - if (this._viewZoneId) { - viewZoneChangeAccessor.layoutZone(this._viewZoneId); - } - } - } -} diff --git a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts index 28d508fe792d..ef737c498a12 100644 --- a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts @@ -6,41 +6,57 @@ import * as DOM from 'vs/base/browser/dom'; import { Button } from 'vs/base/browser/ui/button/button'; import { IAction } from 'vs/base/common/actions'; -import { Disposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IMenu } from 'vs/platform/actions/common/actions'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -export class CommentFormActions extends Disposable { +export class CommentFormActions implements IDisposable { private _buttonElements: HTMLElement[] = []; + private readonly _toDispose = new DisposableStore(); + private _actions: IAction[]; constructor( private container: HTMLElement, private actionHandler: (action: IAction) => void, private themeService: IThemeService - ) { - super(); - } + ) { } setActions(menu: IMenu) { - dispose(this._toDispose); + this._toDispose.clear(); + this._buttonElements.forEach(b => DOM.removeNode(b)); const groups = menu.getActions({ shouldForwardArgs: true }); for (const group of groups) { const [, actions] = group; + this._actions = actions; actions.forEach(action => { const button = new Button(this.container); this._buttonElements.push(button.element); - this._toDispose.push(button); - this._toDispose.push(attachButtonStyler(button, this.themeService)); - this._toDispose.push(button.onDidClick(() => this.actionHandler(action))); + this._toDispose.add(button); + this._toDispose.add(attachButtonStyler(button, this.themeService)); + this._toDispose.add(button.onDidClick(() => this.actionHandler(action))); button.enabled = action.enabled; button.label = action.label; }); } } + + triggerDefaultAction() { + if (this._actions.length) { + let lastAction = this._actions[0]; + + if (lastAction.enabled) { + this.actionHandler(lastAction); + } + } + } + + dispose() { + this._toDispose.dispose(); + } } \ No newline at end of file diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index bd686dd3cb16..98b720ad1bd2 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -178,8 +178,8 @@ export class CommentNode extends Disposable { let commentMenus = this.commentService.getCommentMenus(this.owner); const menu = commentMenus.getCommentTitleActions(this.comment, this._contextKeyService); - this._toDispose.push(menu); - this._toDispose.push(menu.onDidChange(e => { + this._register(menu); + this._register(menu.onDidChange(e => { const contributedActions = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], []); this.toolbar.setActions(contributedActions); })); @@ -217,7 +217,7 @@ export class CommentNode extends Disposable { this.registerActionBarListeners(this._actionsToolbarContainer); this.toolbar.setActions(actions, [])(); - this._toDispose.push(this.toolbar); + this._register(this.toolbar); } } @@ -354,7 +354,7 @@ export class CommentNode extends Disposable { return this.actionViewItemProvider(action as Action); } }); - this._toDispose.push(this._reactionsActionBar); + this._register(this._reactionsActionBar); this.comment.commentReactions!.map(reaction => { let action = new ReactionAction(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted && reaction.canEdit ? 'active' : '', reaction.canEdit, async () => { @@ -424,12 +424,14 @@ export class CommentNode extends Disposable { uri: this._commentEditor.getModel()!.uri, value: this.comment.body.value }; + this.commentService.setActiveCommentThread(commentThread); this._commentEditorDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => { commentThread.input = { uri: this._commentEditor!.getModel()!.uri, value: this.comment.body.value }; + this.commentService.setActiveCommentThread(commentThread); })); this._commentEditorDisposables.push(this._commentEditor.onDidChangeModelContent(e => { @@ -439,13 +441,14 @@ export class CommentNode extends Disposable { let input = commentThread.input; input.value = newVal; commentThread.input = input; + this.commentService.setActiveCommentThread(commentThread); } } })); } - this._toDispose.push(this._commentEditor); - this._toDispose.push(this._commentEditorModel); + this._register(this._commentEditor); + this._register(this._commentEditorModel); } private removeCommentEditor() { @@ -486,6 +489,7 @@ export class CommentNode extends Disposable { uri: this._commentEditor.getModel()!.uri, value: newBody }; + this.commentService.setActiveCommentThread(commentThread); let commandId = this.comment.editCommand.id; let args = this.comment.editCommand.arguments || []; @@ -523,6 +527,7 @@ export class CommentNode extends Disposable { if (result.confirmed) { try { if (this.comment.deleteCommand) { + this.commentService.setActiveCommentThread(this.commentThread); let commandId = this.comment.deleteCommand.id; let args = this.comment.deleteCommand.arguments || []; @@ -561,8 +566,8 @@ export class CommentNode extends Disposable { const menus = this.commentService.getCommentMenus(this.owner); const menu = menus.getCommentActions(this.comment, this._contextKeyService); - this._toDispose.push(menu); - this._toDispose.push(menu.onDidChange(() => { + this._register(menu); + this._register(menu.onDidChange(() => { this._commentFormActions.setActions(menu); })); @@ -599,17 +604,17 @@ export class CommentNode extends Disposable { const cancelEditButton = new Button(formActions); cancelEditButton.label = nls.localize('label.cancel', "Cancel"); - this._toDispose.push(attachButtonStyler(cancelEditButton, this.themeService)); + this._register(attachButtonStyler(cancelEditButton, this.themeService)); - this._toDispose.push(cancelEditButton.onDidClick(_ => { + this._register(cancelEditButton.onDidClick(_ => { this.removeCommentEditor(); })); this._updateCommentButton = new Button(formActions); this._updateCommentButton.label = UPDATE_COMMENT_LABEL; - this._toDispose.push(attachButtonStyler(this._updateCommentButton, this.themeService)); + this._register(attachButtonStyler(this._updateCommentButton, this.themeService)); - this._toDispose.push(this._updateCommentButton.onDidClick(_ => { + this._register(this._updateCommentButton.onDidClick(_ => { this.editComment(); })); @@ -622,21 +627,21 @@ export class CommentNode extends Disposable { } private registerActionBarListeners(actionsContainer: HTMLElement): void { - this._toDispose.push(dom.addDisposableListener(this._domNode, 'mouseenter', () => { + this._register(dom.addDisposableListener(this._domNode, 'mouseenter', () => { actionsContainer.classList.remove('hidden'); })); - this._toDispose.push(dom.addDisposableListener(this._domNode, 'focus', () => { + this._register(dom.addDisposableListener(this._domNode, 'focus', () => { actionsContainer.classList.remove('hidden'); })); - this._toDispose.push(dom.addDisposableListener(this._domNode, 'mouseleave', () => { + this._register(dom.addDisposableListener(this._domNode, 'mouseleave', () => { if (!this._domNode.contains(document.activeElement)) { actionsContainer.classList.add('hidden'); } })); - this._toDispose.push(dom.addDisposableListener(this._domNode, 'focusout', (e: FocusEvent) => { + this._register(dom.addDisposableListener(this._domNode, 'focusout', (e: FocusEvent) => { if (!this._domNode.contains((e.relatedTarget))) { actionsContainer.classList.add('hidden'); @@ -709,8 +714,4 @@ export class CommentNode extends Disposable { }, 3000); } } - - dispose() { - this._toDispose.forEach(disposeable => disposeable.dispose()); - } } diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 52b3f2fe7cb7..af60032068be 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -38,6 +38,7 @@ export interface ICommentService { readonly onDidSetResourceCommentInfos: Event; readonly onDidSetAllCommentThreads: Event; readonly onDidUpdateCommentThreads: Event; + readonly onDidChangeActiveCommentThread: Event; readonly onDidChangeActiveCommentingRange: Event<{ range: Range, commentingRangesInfo: CommentingRanges }>; readonly onDidSetDataProvider: Event; readonly onDidDeleteDataProvider: Event; @@ -48,6 +49,7 @@ export interface ICommentService { unregisterCommentController(owner: string): void; getCommentController(owner: string): MainThreadCommentController | undefined; createCommentThreadTemplate(owner: string, resource: URI, range: Range): void; + updateCommentThreadTemplate(owner: string, threadHandle: number, range: Range): Promise; getCommentMenus(owner: string): CommentMenus; registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void; unregisterDataProvider(owner: string): void; @@ -69,6 +71,7 @@ export interface ICommentService { deleteReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; getReactionGroup(owner: string): CommentReaction[] | undefined; toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise; + setActiveCommentThread(commentThread: CommentThread | null): void; } export class CommentService extends Disposable implements ICommentService { @@ -89,6 +92,9 @@ export class CommentService extends Disposable implements ICommentService { private readonly _onDidUpdateCommentThreads: Emitter = this._register(new Emitter()); readonly onDidUpdateCommentThreads: Event = this._onDidUpdateCommentThreads.event; + private readonly _onDidChangeActiveCommentThread = this._register(new Emitter()); + readonly onDidChangeActiveCommentThread = this._onDidChangeActiveCommentThread.event; + private readonly _onDidChangeActiveCommentingRange: Emitter<{ range: Range, commentingRangesInfo: CommentingRanges @@ -109,6 +115,10 @@ export class CommentService extends Disposable implements ICommentService { super(); } + setActiveCommentThread(commentThread: CommentThread | null) { + this._onDidChangeActiveCommentThread.fire(commentThread); + } + setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void { this._onDidSetResourceCommentInfos.fire({ resource, commentInfos }); } @@ -145,6 +155,16 @@ export class CommentService extends Disposable implements ICommentService { commentController.createCommentThreadTemplate(resource, range); } + async updateCommentThreadTemplate(owner: string, threadHandle: number, range: Range) { + const commentController = this._commentControls.get(owner); + + if (!commentController) { + return; + } + + await commentController.updateCommentThreadTemplate(threadHandle, range); + } + disposeCommentThread(owner: string, threadId: string) { let controller = this.getCommentController(owner); if (controller) { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 0a205c102796..e813687dc755 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -211,6 +211,10 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._bodyElement = dom.$('.body'); container.appendChild(this._bodyElement); + + dom.addDisposableListener(this._bodyElement, dom.EventType.FOCUS_IN, e => { + this.commentService.setActiveCommentThread(this._commentThread); + }); } protected _fillHead(container: HTMLElement): void { @@ -232,7 +236,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } }); - this._disposables.push(this._actionbarWidget); + this._disposables.add(this._actionbarWidget); this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), COLLAPSE_ACTION_CLASS, true, () => this.collapse()); @@ -240,8 +244,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const menu = this._commentMenus.getCommentThreadTitleActions(this._commentThread as modes.CommentThread2, this._contextKeyService); this.setActionBarActions(menu); - this._disposables.push(menu); - this._disposables.push(menu.onDidChange(e => { + this._disposables.add(menu); + this._disposables.add(menu.onDidChange(e => { this.setActionBarActions(menu); })); } else { @@ -265,6 +269,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } else { const deleteCommand = (this._commentThread as modes.CommentThread2).deleteCommand; if (deleteCommand) { + this.commentService.setActiveCommentThread(this._commentThread); return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || [])); } else if (this._commentEditor.getValue() === '') { this.commentService.disposeCommentThread(this._owner, this._commentThread.threadId!); @@ -415,8 +420,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget display(lineNumber: number) { this._commentGlyph = new CommentGlyphWidget(this.editor, lineNumber); - this._disposables.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); - this._disposables.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); + this._disposables.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); + this._disposables.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); let headHeight = Math.ceil(this.editor.getConfiguration().lineHeight * 1.2); this._headElement.style.height = `${headHeight}px`; @@ -454,10 +459,10 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } const model = this.modelService.createModel(this._pendingComment || '', this.modeService.createByFilepathOrFirstLine(resource.path), resource, false); - this._disposables.push(model); + this._disposables.add(model); this._commentEditor.setModel(model); - this._disposables.push(this._commentEditor); - this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { + this._disposables.add(this._commentEditor); + this._disposables.add(this._commentEditor.getModel()!.onDidChangeContent(() => { this.setCommentEditorDecorations(); this._commentEditorIsEmpty.set(!this._commentEditor.getValue()); })); @@ -516,6 +521,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; + this.commentService.setActiveCommentThread(this._commentThread); })); this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { @@ -526,6 +532,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget newInput.value = modelContent; thread.input = newInput; } + this.commentService.setActiveCommentThread(this._commentThread); })); this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => { @@ -651,7 +658,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget deletedraftButton.label = deleteDraftLabel; deletedraftButton.enabled = true; - this._disposables.push(deletedraftButton.onDidClick(async () => { + this._disposables.add(deletedraftButton.onDidClick(async () => { try { await this.commentService.deleteDraft(this._owner, this.editor.getModel()!.uri); } catch (e) { @@ -684,7 +691,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const startDraftLabel = this.commentService.getStartDraftLabel(this._owner); if (startDraftLabel) { const draftButton = new Button(container); - this._disposables.push(attachButtonStyler(draftButton, this.themeService)); + this._disposables.add(attachButtonStyler(draftButton, this.themeService)); draftButton.label = this.commentService.getStartDraftLabel(this._owner)!; draftButton.enabled = model.getValueLength() > 0; @@ -696,7 +703,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } })); - this._disposables.push(draftButton.onDidClick(async () => { + this._disposables.add(draftButton.onDidClick(async () => { try { await this.commentService.startDraft(this._owner, this.editor.getModel()!.uri); await this.createComment(); @@ -719,19 +726,20 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget if (acceptInputCommand) { const button = new Button(container); - this._disposables.push(attachButtonStyler(button, this.themeService)); + this._disposables.add(attachButtonStyler(button, this.themeService)); button.label = acceptInputCommand.title; - this._disposables.push(button.onDidClick(async () => { + this._disposables.add(button.onDidClick(async () => { commentThread.input = { uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; + this.commentService.setActiveCommentThread(this._commentThread); await this.commandService.executeCommand(acceptInputCommand.id, ...(acceptInputCommand.arguments || [])); })); button.enabled = model.getValueLength() > 0; - this._disposables.push(this._commentEditor.onDidChangeModelContent(_ => { + this._disposables.add(this._commentEditor.onDidChangeModelContent(_ => { if (this._commentEditor.getValue()) { button.enabled = true; } else { @@ -743,14 +751,15 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget if (additionalCommands) { additionalCommands.reverse().forEach(command => { const button = new Button(container); - this._disposables.push(attachButtonStyler(button, this.themeService)); + this._disposables.add(attachButtonStyler(button, this.themeService)); button.label = command.title; - this._disposables.push(button.onDidClick(async () => { + this._disposables.add(button.onDidClick(async () => { commentThread.input = { uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; + this.commentService.setActiveCommentThread(this._commentThread); await this.commandService.executeCommand(command.id, ...(command.arguments || [])); })); }); @@ -758,12 +767,19 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const menu = this._commentMenus.getCommentThreadActions(commentThread, this._contextKeyService); - this._disposables.push(menu); - this._disposables.push(menu.onDidChange(() => { + this._disposables.add(menu); + this._disposables.add(menu.onDidChange(() => { this._commentFormActions.setActions(menu); })); - this._commentFormActions = new CommentFormActions(container, (action: IAction) => { + this._commentFormActions = new CommentFormActions(container, async (action: IAction) => { + if (!commentThread.comments || !commentThread.comments.length) { + let newPosition = this.getPosition(); + + if (newPosition) { + this.commentService.updateCommentThreadTemplate(this.owner, commentThread.commentThreadHandle, new Range(newPosition.lineNumber, 1, newPosition.lineNumber, 1)); + } + } action.run({ thread: this._commentThread, text: this._commentEditor.getValue(), @@ -786,8 +802,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this, this._markdownRenderer); - this._disposables.push(newCommentNode); - this._disposables.push(newCommentNode.onDidDelete(deletedNode => { + this._disposables.add(newCommentNode); + this._disposables.add(newCommentNode.onDidDelete(deletedNode => { const deletedNodeId = deletedNode.comment.commentId; const deletedElementIndex = arrays.firstIndex(this._commentElements, commentNode => commentNode.comment.commentId === deletedNodeId); if (deletedElementIndex > -1) { @@ -821,11 +837,14 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; + this.commentService.setActiveCommentThread(this._commentThread); let commandId = commentThread.acceptInputCommand.id; let args = commentThread.acceptInputCommand.arguments || []; await this.commandService.executeCommand(commandId, ...args); return; + } else if (this._commentFormActions) { + this._commentFormActions.triggerDefaultAction(); } } else { this.createComment(); @@ -944,8 +963,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } this._reviewThreadReplyButton.textContent = nls.localize('reply', "Reply..."); // bind click/escape actions for reviewThreadReplyButton and textArea - this._disposables.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea())); - this._disposables.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'focus', _ => this.expandReplyArea())); + this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea())); + this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'focus', _ => this.expandReplyArea())); this._commentEditor.onDidBlurEditorWidget(() => { if (this._commentEditor.getModel()!.getValueLength() === 0 && dom.hasClass(this._commentForm, 'expand')) { @@ -963,6 +982,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const frameThickness = Math.round(lineHeight / 9) * 2; const computedLinesNumber = Math.ceil((headHeight + dimensions.height + arrowHeight + frameThickness + 8 /** margin bottom to avoid margin collapse */) / lineHeight); + + let currentPosition = this.getPosition(); + + if (this._viewZone && currentPosition && currentPosition.lineNumber !== this._viewZone.afterLineNumber) { + this._viewZone.afterLineNumber = currentPosition.lineNumber; + } + this._relayout(computedLinesNumber); } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 81797bda5f22..ee5db0c323e4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IDataSource, IFilter, IRenderer as ITreeRenderer, ITree } from 'vs/base/parts/tree/browser/tree'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -62,7 +62,7 @@ interface ICommentThreadTemplateData { icon: HTMLImageElement; userName: HTMLSpanElement; commentText: HTMLElement; - disposables: Disposable[]; + disposables: IDisposable[]; } export class CommentsModelRenderer implements ITreeRenderer { diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index f6e500ff2eb8..053cbc7f4f95 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -178,6 +178,7 @@ .monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.toolbar-toggle-pickReactions { display: none; background-image: url(./reaction.svg); + background-size: 24px; width: 26px; height: 16px; background-repeat: no-repeat; @@ -188,6 +189,7 @@ .monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions:hover .action-item a.action-label.toolbar-toggle-pickReactions { display: inline-block; + background-size: 24px; } .monaco-editor.vs-dark .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.toolbar-toggle-pickReactions { @@ -212,7 +214,7 @@ background-image: url(./reaction.svg); width: 18px; height: 18px; - background-size: 100% auto; + background-size: 24px; background-position: center; background-repeat: no-repeat; } diff --git a/src/vs/workbench/contrib/comments/common/commentModel.ts b/src/vs/workbench/contrib/comments/common/commentModel.ts index ca770a151bfb..271660695216 100644 --- a/src/vs/workbench/contrib/comments/common/commentModel.ts +++ b/src/vs/workbench/contrib/comments/common/commentModel.ts @@ -98,7 +98,7 @@ export class CommentsModel { const index = firstIndex(matchingResourceData.commentThreads, (commentThread) => commentThread.threadId === thread.threadId); if (index >= 0) { matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(URI.parse(matchingResourceData.id), thread); - } else { + } else if (thread.comments && thread.comments.length) { matchingResourceData.commentThreads.push(ResourceWithCommentThreads.createCommentNode(URI.parse(matchingResourceData.id), thread)); } }); diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index c6d1aa593e24..f35839475990 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -221,4 +221,4 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer(); + data.set(this.breakpoint.getId(), { + condition, + hitCondition, + logMessage + }); + this.debugService.updateBreakpoints(this.breakpoint.uri, data, false); } else { const model = this.editor.getModel(); if (model) { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 3b9210ec0fab..25468d6eb164 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -61,7 +61,7 @@ export class BreakpointsView extends ViewletPanel { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService); this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize(); - this.disposables.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); + this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); } public renderBody(container: HTMLElement): void { @@ -81,9 +81,9 @@ export class BreakpointsView extends ViewletPanel { CONTEXT_BREAKPOINTS_FOCUSED.bindTo(this.list.contextKeyService); - this.list.onContextMenu(this.onListContextMenu, this, this.disposables); + this._register(this.list.onContextMenu(this.onListContextMenu, this)); - this.disposables.push(this.list.onDidOpen(e => { + this._register(this.list.onDidOpen(e => { let isSingleClick = false; let isDoubleClick = false; let isMiddleClick = false; @@ -120,7 +120,7 @@ export class BreakpointsView extends ViewletPanel { this.list.splice(0, this.list.length, this.elements); - this.disposables.push(this.onDidChangeBodyVisibility(visible => { + this._register(this.onDidChangeBodyVisibility(visible => { if (visible && this.needsRefresh) { this.onBreakpointsChange(); } @@ -557,7 +557,6 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea options: { preserveFocus, selection, - revealIfVisible: true, revealIfOpened: true, revealInCenterIfOutsideViewport: true, pinned: !preserveFocus diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 7dca7bd8acaa..356e9cdfe8a6 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -63,7 +63,7 @@ export class CallStackView extends ViewletPanel { this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService); this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService); - this.disposables.push(this.contributedContextMenu); + this._register(this.contributedContextMenu); // Create scheduler to prevent unnecessary flashing of tree when reacting to changes this.onCallStackChangeScheduler = new RunOnceScheduler(() => { @@ -149,8 +149,8 @@ export class CallStackView extends ViewletPanel { this.tree.setInput(this.debugService.getModel()).then(undefined, onUnexpectedError); const callstackNavigator = new TreeResourceNavigator2(this.tree); - this.disposables.push(callstackNavigator); - this.disposables.push(callstackNavigator.onDidOpenResource(e => { + this._register(callstackNavigator); + this._register(callstackNavigator.onDidOpenResource(e => { if (this.ignoreSelectionChangedEvent) { return; } @@ -189,7 +189,7 @@ export class CallStackView extends ViewletPanel { } })); - this.disposables.push(this.debugService.getModel().onDidChangeCallStack(() => { + this._register(this.debugService.getModel().onDidChangeCallStack(() => { if (!this.isBodyVisible()) { this.needsRefresh = true; return; @@ -200,7 +200,7 @@ export class CallStackView extends ViewletPanel { } })); const onCallStackChange = Event.any(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getViewModel().onDidFocusSession); - this.disposables.push(onCallStackChange(() => { + this._register(onCallStackChange(() => { if (this.ignoreFocusStackFrameEvent) { return; } @@ -211,20 +211,20 @@ export class CallStackView extends ViewletPanel { this.updateTreeSelection(); })); - this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); // Schedule the update of the call stack tree if the viewlet is opened after a session started #14684 if (this.debugService.state === State.Stopped) { this.onCallStackChangeScheduler.schedule(0); } - this.disposables.push(this.onDidChangeBodyVisibility(visible => { + this._register(this.onDidChangeBodyVisibility(visible => { if (visible && this.needsRefresh) { this.onCallStackChangeScheduler.schedule(); } })); - this.disposables.push(this.debugService.onDidNewSession(s => { + this._register(this.debugService.onDidNewSession(s => { if (s.parentSession) { // Auto expand sessions that have sub sessions this.parentSessionToExpand.add(s.parentSession); diff --git a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts index 0e0b1adca8d7..d2b02188be3f 100644 --- a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts +++ b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts @@ -5,12 +5,14 @@ import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { RGBA, Color } from 'vs/base/common/color'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; /** * @param text The content to stylize. * @returns An {@link HTMLSpanElement} that contains the potentially stylized text. */ -export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTMLSpanElement { +export function handleANSIOutput(text: string, linkDetector: LinkDetector, themeService: IThemeService): HTMLSpanElement { const root: HTMLSpanElement = document.createElement('span'); const textLength: number = text.length; @@ -105,23 +107,20 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML /** * Change the foreground or background color by clearing the current color * and adding the new one. - * @param newClass If string or number, new class will be - * `code-(foreground or background)-newClass`. If `undefined`, no new class - * will be added. * @param colorType If `'foreground'`, will change the foreground color, if * `'background'`, will change the background color. - * @param customColor If provided, this custom color will be used instead of - * a class-defined color. + * @param color Color to change to. If `undefined` or not provided, + * will clear current color without adding a new one. */ - function changeColor(newClass: string | number | undefined, colorType: 'foreground' | 'background', customColor?: RGBA): void { - styleNames = styleNames.filter(style => !style.match(new RegExp(`^code-${colorType}-(\\d+|custom)$`))); - if (newClass) { - styleNames.push(`code-${colorType}-${newClass}`); - } + function changeColor(colorType: 'foreground' | 'background', color?: RGBA | undefined): void { if (colorType === 'foreground') { - customFgColor = customColor; - } else { - customBgColor = customColor; + customFgColor = color; + } else if (colorType === 'background') { + customBgColor = color; + } + styleNames = styleNames.filter(style => style !== `code-${colorType}-colored`); + if (color !== undefined) { + styleNames.push(`code-${colorType}-colored`); } } @@ -137,22 +136,37 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML */ function setBasicFormatters(styleCodes: number[]): void { for (let code of styleCodes) { - if (code === 0) { - styleNames = []; - } else if (code === 1) { - styleNames.push('code-bold'); - } else if (code === 3) { - styleNames.push('code-italic'); - } else if (code === 4) { - styleNames.push('code-underline'); - } else if ((code >= 30 && code <= 37) || (code >= 90 && code <= 97)) { - changeColor(code, 'foreground'); - } else if ((code >= 40 && code <= 47) || (code >= 100 && code <= 107)) { - changeColor(code, 'background'); - } else if (code === 39) { - changeColor(undefined, 'foreground'); - } else if (code === 49) { - changeColor(undefined, 'background'); + switch (code) { + case 0: { + styleNames = []; + customFgColor = undefined; + customBgColor = undefined; + break; + } + case 1: { + styleNames.push('code-bold'); + break; + } + case 3: { + styleNames.push('code-italic'); + break; + } + case 4: { + styleNames.push('code-underline'); + break; + } + case 39: { + changeColor('foreground', undefined); + break; + } + case 49: { + changeColor('background', undefined); + break; + } + default: { + setBasicColor(code); + break; + } } } } @@ -171,7 +185,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML styleCodes[3] >= 0 && styleCodes[3] <= 255 && styleCodes[4] >= 0 && styleCodes[4] <= 255) { const customColor = new RGBA(styleCodes[2], styleCodes[3], styleCodes[4]); - changeColor('custom', colorType, customColor); + changeColor(colorType, customColor); } } @@ -188,7 +202,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML const color = calcANSI8bitColor(colorNumber); if (color) { - changeColor('custom', colorType, color); + changeColor(colorType, color); } else if (colorNumber >= 0 && colorNumber <= 15) { // Need to map to one of the four basic color ranges (30-37, 90-97, 40-47, 100-107) colorNumber += 30; @@ -199,7 +213,43 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTML if (colorType === 'background') { colorNumber += 10; } - changeColor(colorNumber, colorType); + setBasicColor(colorNumber); + } + } + + /** + * Calculate and set styling for basic bright and dark ANSI color codes. Uses + * theme colors if available. Automatically distinguishes between foreground + * and background colors; does not support color-clearing codes 39 and 49. + * @param styleCode Integer color code on one of the following ranges: + * [30-37, 90-97, 40-47, 100-107]. If not on one of these ranges, will do + * nothing. + */ + function setBasicColor(styleCode: number): void { + const theme = themeService.getTheme(); + let colorType: 'foreground' | 'background' | undefined; + let colorIndex: number | undefined; + + if (styleCode >= 30 && styleCode <= 37) { + colorIndex = styleCode - 30; + colorType = 'foreground'; + } else if (styleCode >= 90 && styleCode <= 97) { + colorIndex = (styleCode - 90) + 8; // High-intensity (bright) + colorType = 'foreground'; + } else if (styleCode >= 40 && styleCode <= 47) { + colorIndex = styleCode - 40; + colorType = 'background'; + } else if (styleCode >= 100 && styleCode <= 107) { + colorIndex = (styleCode - 100) + 8; // High-intensity (bright) + colorType = 'background'; + } + + if (colorIndex !== undefined && colorType) { + const colorName = ansiColorIdentifiers[colorIndex]; + const color = theme.getColor(colorName); + if (color) { + changeColor(colorType, color.rgba); + } } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 733225171847..4ac8043eeb3c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -199,9 +199,9 @@ export class FocusSessionActionViewItem extends SelectActionViewItem { ) { super(null, action, [], -1, contextViewService, { ariaLabel: nls.localize('debugSession', 'Debug Session') }); - this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); + this._register(attachSelectBoxStyler(this.selectBox, themeService)); - this.toDispose.push(this.debugService.getViewModel().onDidFocusSession(() => { + this._register(this.debugService.getViewModel().onDidFocusSession(() => { const session = this.debugService.getViewModel().focusedSession; if (session) { const index = this.getSessions().indexOf(session); @@ -209,8 +209,8 @@ export class FocusSessionActionViewItem extends SelectActionViewItem { } })); - this.toDispose.push(this.debugService.onDidNewSession(() => this.update())); - this.toDispose.push(this.debugService.onDidEndSession(() => this.update())); + this._register(this.debugService.onDidNewSession(() => this.update())); + this._register(this.debugService.onDidEndSession(() => this.update())); this.update(); } diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 0086a958e84d..e370222b0eef 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -53,6 +53,11 @@ function getThreadAndRun(accessor: ServicesAccessor, thread: IThread | undefined const debugService = accessor.get(IDebugService); if (!(thread instanceof Thread)) { thread = debugService.getViewModel().focusedThread; + if (!thread) { + const focusedSession = debugService.getViewModel().focusedSession; + const threads = focusedSession ? focusedSession.getAllThreads() : undefined; + thread = threads && threads.length ? threads[0] : undefined; + } } if (thread) { @@ -407,24 +412,18 @@ export function registerCommands(): void { const widget = editorService.activeTextEditorWidget; if (isCodeEditor(widget)) { const position = widget.getPosition(); - if (!position || !widget.hasModel()) { - return undefined; - } + if (position && widget.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(widget.getModel())) { + const modelUri = widget.getModel().uri; + const breakpointAlreadySet = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri }) + .some(bp => (bp.sessionAgnosticData.column === position.column || (!bp.column && position.column <= 1))); - const modelUri = widget.getModel().uri; - const bp = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri }) - .filter(bp => (bp.column === position.column || !bp.column && position.column <= 1)).pop(); - - if (bp) { - return undefined; - } - if (debugService.getConfigurationManager().canSetBreakpointsIn(widget.getModel())) { - return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber, column: position.column > 1 ? position.column : undefined }], 'debugCommands.inlineBreakpointCommand'); + if (!breakpointAlreadySet) { + debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber, column: position.column > 1 ? position.column : undefined }], 'debugCommands.inlineBreakpointCommand'); + } } } - - return undefined; }; + KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F9, diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts index 1360355d9aa1..fb099601f1ef 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts @@ -180,7 +180,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { return; } - const data: { [id: string]: IBreakpointUpdateData } = Object.create(null); + const data = new Map(); const breakpoints = this.debugService.getModel().getBreakpoints(); const modelUri = modelData.model.uri; for (let i = 0, len = modelData.breakpointDecorations.length; i < len; i++) { @@ -191,10 +191,10 @@ export class DebugEditorModelManager implements IWorkbenchContribution { const breakpoint = breakpoints.filter(bp => bp.getId() === breakpointDecoration.modelId).pop(); // since we know it is collapsed, it cannot grow to multiple lines if (breakpoint) { - data[breakpoint.getId()] = { + data.set(breakpoint.getId(), { lineNumber: decorationRange.startLineNumber, column: breakpoint.column ? decorationRange.startColumn : undefined, - }; + }); } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 120e40e5d07b..994f93fa49ac 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -16,7 +16,7 @@ import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPosit import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; import { Expression } from 'vs/workbench/contrib/debug/common/debugModel'; -import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderExpressionValue, replaceWhitespace } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -237,7 +237,7 @@ export class DebugHoverWidget implements IContentWidget { this.complexValueContainer.hidden = false; return this.tree.setInput(expression).then(() => { - this.complexValueTitle.textContent = expression.value; + this.complexValueTitle.textContent = replaceWhitespace(expression.value); this.complexValueTitle.title = expression.value; this.layoutTreeAndContainer(); this.editor.layoutContentWidget(this); diff --git a/src/vs/workbench/contrib/debug/browser/debugStatus.ts b/src/vs/workbench/contrib/debug/browser/debugStatus.ts index f0b7891e8f14..2ae12585b4c3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugStatus.ts +++ b/src/vs/workbench/contrib/debug/browser/debugStatus.ts @@ -4,100 +4,81 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as dom from 'vs/base/browser/dom'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDebugService, State, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; -import { Themable, STATUS_BAR_FOREGROUND } from 'vs/workbench/common/theme'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { STATUS_BAR_DEBUGGING_FOREGROUND, isStatusbarInDebugMode } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; +import { IStatusbarEntry, IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -const $ = dom.$; -export class DebugStatus extends Themable implements IStatusbarItem { - private container: HTMLElement; - private statusBarItem: HTMLElement; - private label: HTMLElement; - private icon: HTMLElement; +export class DebugStatusContribution implements IWorkbenchContribution { + private showInStatusBar: 'never' | 'always' | 'onFirstSessionStart'; + private toDispose: IDisposable[] = []; + private entryAccessor: IStatusbarEntryAccessor | undefined; constructor( - @IQuickOpenService private readonly quickOpenService: IQuickOpenService, - @IDebugService private readonly debugService: IDebugService, - @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService + @IStatusbarService private readonly statusBarService: IStatusbarService, + @IDebugService readonly debugService: IDebugService, + @IConfigurationService readonly configurationService: IConfigurationService ) { - super(themeService); - this._register(this.debugService.getConfigurationManager().onDidSelectConfiguration(e => { - this.setLabel(); - })); - this._register(this.debugService.onDidChangeState(state => { - if (state !== State.Inactive && this.showInStatusBar === 'onFirstSessionStart') { - this.doRender(); - } else { - if (this.showInStatusBar !== 'never') { - this.updateStyles(); - } + + const addStatusBarEntry = () => { + this.entryAccessor = this.statusBarService.addEntry(this.entry, 'status.debug', nls.localize('status.debug', "Debug"), StatusbarAlignment.LEFT, 30 /* Low Priority */); + }; + + const setShowInStatusBar = () => { + this.showInStatusBar = configurationService.getValue('debug').showInStatusBar; + if (this.showInStatusBar === 'always' && !this.entryAccessor) { + addStatusBarEntry(); + } + }; + setShowInStatusBar(); + + this.toDispose.push(this.debugService.onDidChangeState(state => { + if (state !== State.Inactive && this.showInStatusBar === 'onFirstSessionStart' && !this.entryAccessor) { + addStatusBarEntry(); } })); - this.showInStatusBar = configurationService.getValue('debug').showInStatusBar; - this._register(configurationService.onDidChangeConfiguration(e => { + this.toDispose.push(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('debug.showInStatusBar')) { - this.showInStatusBar = configurationService.getValue('debug').showInStatusBar; - if (this.showInStatusBar === 'always') { - this.doRender(); - } - if (this.statusBarItem) { - dom.toggleClass(this.statusBarItem, 'hidden', this.showInStatusBar === 'never'); + setShowInStatusBar(); + if (this.entryAccessor && this.showInStatusBar === 'never') { + this.entryAccessor.dispose(); + this.entryAccessor = undefined; } } })); - } - - protected updateStyles(): void { - if (this.icon) { - if (isStatusbarInDebugMode(this.debugService)) { - this.icon.style.backgroundColor = this.getColor(STATUS_BAR_DEBUGGING_FOREGROUND); - } else { - this.icon.style.backgroundColor = this.getColor(STATUS_BAR_FOREGROUND); + this.toDispose.push(this.debugService.getConfigurationManager().onDidSelectConfiguration(e => { + if (this.entryAccessor) { + this.entryAccessor.update(this.entry); } - } + })); } - public render(container: HTMLElement): IDisposable { - this.container = container; - if (this.showInStatusBar === 'always') { - this.doRender(); + private getText(): string { + const manager = this.debugService.getConfigurationManager(); + const name = manager.selectedConfiguration.name || ''; + const nameAndLaunchPresent = name && manager.selectedConfiguration.launch; + if (nameAndLaunchPresent) { + return '$(play) ' + (manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch!.name})` : name); } - // noop, we render when we decide is best - return this; - } - private doRender(): void { - if (!this.statusBarItem && this.container) { - this.statusBarItem = dom.append(this.container, $('.debug-statusbar-item')); - this._register(dom.addDisposableListener(this.statusBarItem, 'click', () => this.quickOpenService.show('debug '))); - this.statusBarItem.title = nls.localize('selectAndStartDebug', "Select and start debug configuration"); - const a = dom.append(this.statusBarItem, $('a')); - this.icon = dom.append(a, $('.icon')); - this.label = dom.append(a, $('span.label')); - this.setLabel(); - } + return ''; + } - this.updateStyles(); + private get entry(): IStatusbarEntry { + return { + text: this.getText(), + tooltip: nls.localize('selectAndStartDebug', "Select and start debug configuration"), + command: 'workbench.action.debug.selectandstart' + }; } - private setLabel(): void { - if (this.label && this.statusBarItem) { - const manager = this.debugService.getConfigurationManager(); - const name = manager.selectedConfiguration.name || ''; - const nameAndLaunchPresent = name && manager.selectedConfiguration.launch; - dom.toggleClass(this.statusBarItem, 'hidden', this.showInStatusBar === 'never' || !nameAndLaunchPresent); - if (nameAndLaunchPresent) { - this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch!.name})` : name; - } + dispose(): void { + if (this.entryAccessor) { + this.entryAccessor.dispose(); } + dispose(this.toDispose); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 005a4590f334..2685a94ba59a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -81,7 +81,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { const actionBarContainer = dom.append(this.$el, dom.$('div.action-bar-container')); this.debugToolBarMenu = menuService.createMenu(MenuId.DebugToolBar, contextKeyService); - this.toDispose.push(this.debugToolBarMenu); + this._register(this.debugToolBarMenu); this.activeActions = []; this.actionBar = this._register(new ActionBar(actionBarContainer, { diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index a82347934987..8ee288b6820c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -14,7 +14,7 @@ import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; +import { IProgressService } from 'vs/platform/progress/common/progress'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -37,7 +37,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; export class DebugViewlet extends ViewContainerViewlet { private startDebugActionViewItem: StartDebugActionViewItem; - private progressRunner: IProgressRunner; + private progressResolve: (() => void) | undefined; private breakpointView: ViewletPanel; private panelListeners = new Map(); private debugToolBarMenu: IMenu; @@ -112,7 +112,7 @@ export class DebugViewlet extends ViewContainerViewlet { if (!this.debugToolBarMenu) { this.debugToolBarMenu = this.menuService.createMenu(MenuId.DebugToolBar, this.contextKeyService); - this.toDispose.push(this.debugToolBarMenu); + this._register(this.debugToolBarMenu); } return DebugToolBar.getActions(this.debugToolBarMenu, this.debugService, this.instantiationService); } @@ -153,12 +153,15 @@ export class DebugViewlet extends ViewContainerViewlet { } private onDebugServiceStateChange(state: State): void { - if (this.progressRunner) { - this.progressRunner.done(); + if (this.progressResolve) { + this.progressResolve(); + this.progressResolve = undefined; } if (state === State.Initializing) { - this.progressRunner = this.progressService.show(true); + this.progressService.withProgress({ location: VIEWLET_ID }, _progress => { + return new Promise(resolve => this.progressResolve = resolve); + }); } this.updateToolBar(); diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index cbe90c637785..c1a59834aae1 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -35,12 +35,12 @@ export class ExceptionWidget extends ZoneWidget { this._backgroundColor = Color.white; this._applyTheme(themeService.getTheme()); - this._disposables.push(themeService.onThemeChange(this._applyTheme.bind(this))); + this._disposables.add(themeService.onThemeChange(this._applyTheme.bind(this))); this.create(); const onDidLayoutChangeScheduler = new RunOnceScheduler(() => this._doLayout(undefined, undefined), 50); - this._disposables.push(this.editor.onDidLayoutChange(() => onDidLayoutChangeScheduler.schedule())); - this._disposables.push(onDidLayoutChangeScheduler); + this._disposables.add(this.editor.onDidLayoutChange(() => onDidLayoutChangeScheduler.schedule())); + this._disposables.add(onDidLayoutChangeScheduler); } private _applyTheme(theme: ITheme): void { diff --git a/src/vs/workbench/contrib/debug/browser/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts index df8c8ea403b7..318895d585d3 100644 --- a/src/vs/workbench/contrib/debug/browser/linkDetector.ts +++ b/src/vs/workbench/contrib/debug/browser/linkDetector.ts @@ -19,7 +19,7 @@ export class LinkDetector { // group 2: drive letter on windows with trailing backslash or leading slash on mac/linux // group 3: line number, matched by (:(\d+)) // group 4: column number, matched by ((?::(\d+))?) - // eg: at Context. (c:\Users\someone\Desktop\mocha-runner\test\test.js:26:11) + // e.g.: at Context. (c:\Users\someone\Desktop\mocha-runner\test\test.js:26:11) /(?![\(])(?:file:\/\/)?((?:([a-zA-Z]+:)|[^\(\)<>\'\"\[\]:\s]+)(?:[\\/][^\(\)<>\'\"\[\]:]*)?\.[a-zA-Z]+[0-9]*):(\d+)(?::(\d+))?/g ]; diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 298a7604a1af..7a7913087387 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -45,16 +45,15 @@ type LoadedScriptsItem = BaseTreeItem; class BaseTreeItem { private _showedMoreThanOne: boolean; - private _children: { [key: string]: BaseTreeItem; }; + private _children = new Map(); private _source: Source; constructor(private _parent: BaseTreeItem | undefined, private _label: string) { - this._children = {}; this._showedMoreThanOne = false; } isLeaf(): boolean { - return Object.keys(this._children).length === 0; + return this._children.size === 0; } getSession(): IDebugSession | undefined { @@ -66,12 +65,12 @@ class BaseTreeItem { setSource(session: IDebugSession, source: Source): void { this._source = source; - this._children = {}; + this._children.clear(); if (source.raw && source.raw.sources) { for (const src of source.raw.sources) { if (src.name && src.path) { const s = new BaseTreeItem(this, src.name); - this._children[src.path] = s; + this._children.set(src.path, s); const ss = session.getSource(src); s.setSource(session, ss); } @@ -80,26 +79,26 @@ class BaseTreeItem { } createIfNeeded(key: string, factory: (parent: BaseTreeItem, label: string) => T): T { - let child = this._children[key]; + let child = this._children.get(key); if (!child) { child = factory(this, key); - this._children[key] = child; + this._children.set(key, child); } return child; } - getChild(key: string): BaseTreeItem { - return this._children[key]; + getChild(key: string): BaseTreeItem | undefined { + return this._children.get(key); } remove(key: string): void { - delete this._children[key]; + this._children.delete(key); } removeFromParent(): void { if (this._parent) { this._parent.remove(this._label); - if (Object.keys(this._parent._children).length === 0) { + if (this._parent._children.size === 0) { this._parent.removeFromParent(); } } @@ -142,7 +141,7 @@ class BaseTreeItem { if (child) { return child.hasChildren(); } - return Object.keys(this._children).length > 0; + return this._children.size > 0; } // skips intermediate single-child nodes @@ -151,7 +150,10 @@ class BaseTreeItem { if (child) { return child.getChildren(); } - const array = Object.keys(this._children).map(key => this._children[key]); + const array: BaseTreeItem[] = []; + for (let child of this._children.values()) { + array.push(child); + } return Promise.resolve(array.sort((a, b) => this.compare(a, b))); } @@ -199,12 +201,11 @@ class BaseTreeItem { private oneChild(): BaseTreeItem | undefined { if (SMART && !this._source && !this._showedMoreThanOne && !(this instanceof RootFolderTreeItem) && !(this instanceof SessionTreeItem)) { - const keys = Object.keys(this._children); - if (keys.length === 1) { - return this._children[keys[0]]; + if (this._children.size === 1) { + return this._children.values().next().value; } // if a node had more than one child once, it will never be skipped again - if (keys.length > 1) { + if (this._children.size > 1) { this._showedMoreThanOne = true; } } @@ -243,7 +244,7 @@ class SessionTreeItem extends BaseTreeItem { private _session: IDebugSession; private _initialized: boolean; - private _map: Map; + private _map = new Map(); private _labelService: ILabelService; constructor(labelService: ILabelService, parent: BaseTreeItem, session: IDebugSession, private _environmentService: IEnvironmentService, private rootProvider: IWorkspaceContextService) { @@ -251,7 +252,6 @@ class SessionTreeItem extends BaseTreeItem { this._labelService = labelService; this._initialized = false; this._session = session; - this._map = new Map(); } getSession(): IDebugSession { @@ -417,7 +417,7 @@ export class LoadedScriptsView extends ViewletPanel { const root = new RootTreeItem(this.debugService.getModel(), this.environmentService, this.contextService, this.labelService); this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); - this.disposables.push(this.treeLabels); + this._register(this.treeLabels); this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, this.treeContainer, new LoadedScriptsDelegate(), [new LoadedScriptsRenderer(this.treeLabels)], @@ -443,11 +443,11 @@ export class LoadedScriptsView extends ViewletPanel { this.tree.updateChildren(); } }, 300); - this.disposables.push(this.changeScheduler); + this._register(this.changeScheduler); const loadedScriptsNavigator = new TreeResourceNavigator2(this.tree); - this.disposables.push(loadedScriptsNavigator); - this.disposables.push(loadedScriptsNavigator.onDidOpenResource(e => { + this._register(loadedScriptsNavigator); + this._register(loadedScriptsNavigator.onDidOpenResource(e => { if (e.element instanceof BaseTreeItem) { const source = e.element.getSource(); if (source && source.available) { @@ -457,7 +457,7 @@ export class LoadedScriptsView extends ViewletPanel { } })); - this.disposables.push(this.tree.onDidChangeFocus(() => { + this._register(this.tree.onDidChangeFocus(() => { const focus = this.tree.getFocus(); if (focus instanceof SessionTreeItem) { this.loadedScriptsItemType.set('session'); @@ -467,7 +467,7 @@ export class LoadedScriptsView extends ViewletPanel { })); const registerLoadedSourceListener = (session: IDebugSession) => { - this.disposables.push(session.onDidLoadedSource(event => { + this._register(session.onDidLoadedSource(event => { let sessionRoot: SessionTreeItem; switch (event.reason) { case 'new': @@ -501,17 +501,17 @@ export class LoadedScriptsView extends ViewletPanel { })); }; - this.disposables.push(this.debugService.onDidNewSession(registerLoadedSourceListener)); + this._register(this.debugService.onDidNewSession(registerLoadedSourceListener)); this.debugService.getModel().getSessions().forEach(registerLoadedSourceListener); - this.disposables.push(this.debugService.onDidEndSession(session => { + this._register(this.debugService.onDidEndSession(session => { root.remove(session.getId()); this.changeScheduler.schedule(); })); this.changeScheduler.schedule(0); - this.disposables.push(this.onDidChangeBodyVisibility(visible => { + this._register(this.onDidChangeBodyVisibility(visible => { if (visible && this.treeNeedsRefreshOnVisible) { this.changeScheduler.schedule(); } diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css index 1d3e15fbefab..b680b2806eef 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/breakpointWidget.css @@ -18,6 +18,5 @@ .monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .inputContainer { flex: 1; - margin-top: 6px; - margin-bottom: 6px; + margin-top: 8px; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 36af227a4d21..f42d0408e4d1 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -110,25 +110,6 @@ box-sizing: border-box; } -/* Debug status */ -/* A very precise css rule to overwrite the display set in statusbar.css */ -.monaco-workbench .part.statusbar > .statusbar-item > .debug-statusbar-item > a { - display: flex; - padding: 0 5px 0 5px; -} - -.monaco-workbench .part.statusbar .debug-statusbar-item.hidden { - display: none; -} - -.monaco-workbench .part.statusbar .debug-statusbar-item .icon { - -webkit-mask: url('start.svg') no-repeat 50% 50%; - -webkit-mask-size: 16px; - display: inline-block; - padding-right: 2px; - width: 16px; -} - .monaco-workbench .debug-view-content .monaco-list-row .monaco-tl-contents { overflow: hidden; text-overflow: ellipsis; @@ -142,6 +123,7 @@ overflow: hidden; text-overflow: ellipsis; font-family: var(--monaco-monospace-font); + white-space: pre; } .monaco-workbench.mac .debug-viewlet .monaco-list-row .expression, diff --git a/src/vs/workbench/contrib/debug/browser/media/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css index 45bc2bebefc3..74cbf744049b 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -26,6 +26,7 @@ text-overflow: ellipsis; height: 18px; overflow: hidden; + white-space: pre; border-bottom: 1px solid rgba(128, 128, 128, 0.35); } @@ -60,10 +61,6 @@ max-height: 500px; } -.monaco-editor .debug-hover-widget .monaco-tl-contents .value { - white-space: nowrap; -} - .monaco-editor .debug-hover-widget .error { color: #E51400; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 4c4f832c407f..7c8678076644 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -246,10 +246,6 @@ opacity: 0.35; } -.monaco-workbench .debug-viewlet .monaco-list-row .expression { - white-space: pre; -} - .debug-viewlet .debug-call-stack .error { font-style: italic; text-overflow: ellipsis; diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 38c5d28b1da5..3a3e6b50a546 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -127,61 +127,6 @@ .monaco-workbench .repl .repl-tree .output.expression .code-italic { font-style: italic; } .monaco-workbench .repl .repl-tree .output.expression .code-underline { text-decoration: underline; } -/* Regular and bright color codes are currently treated the same. */ -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-30, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-90 { color: gray; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-31, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-91 { color: #BE1717; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-32, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-92 { color: #338A2F; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-33, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-93 { color: #BEB817; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-34, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-94 { color: darkblue; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-35, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-95 { color: darkmagenta; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-36, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-96 { color: darkcyan; } -.monaco-workbench .repl .repl-tree .output.expression .code-foreground-37, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-97 { color: #BDBDBD; } - -.monaco-workbench .repl .repl-tree .output.expression .code-background-40, .monaco-workbench .repl .repl-tree .output.expression .code-background-100 { background-color: gray; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-41, .monaco-workbench .repl .repl-tree .output.expression .code-background-101 { background-color: #BE1717; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-42, .monaco-workbench .repl .repl-tree .output.expression .code-background-102 { background-color: #338A2F; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-43, .monaco-workbench .repl .repl-tree .output.expression .code-background-103 { background-color: #BEB817; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-44, .monaco-workbench .repl .repl-tree .output.expression .code-background-104 { background-color: darkblue; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-45, .monaco-workbench .repl .repl-tree .output.expression .code-background-105 { background-color: darkmagenta; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-46, .monaco-workbench .repl .repl-tree .output.expression .code-background-106 { background-color: darkcyan; } -.monaco-workbench .repl .repl-tree .output.expression .code-background-47, .monaco-workbench .repl .repl-tree .output.expression .code-background-107 { background-color: #BDBDBD; } - -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-30, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-90 { color: #A0A0A0; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-31, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-91 { color: #A74747; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-32, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-92 { color: #348F34; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-33, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-93 { color: #5F4C29; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-34, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-94 { color: #6286BB; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-35, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-95 { color: #914191; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-36, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-96 { color: #218D8D; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-37, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-97 { color: #707070; } - -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-40, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-100 { background-color: #A0A0A0; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-41, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-101 { background-color: #A74747; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-42, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-102 { background-color: #348F34; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-43, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-103 { background-color: #5F4C29; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-44, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-104 { background-color: #6286BB; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-45, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-105 { background-color: #914191; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-46, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-106 { background-color: #218D8D; } -.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-47, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-107 { background-color: #707070; } - -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-30, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-90 { color: gray; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-31, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-91 { color: #A74747; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-32, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-92 { color: #348F34; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-33, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-93 { color: #5F4C29; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-34, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-94 { color: #6286BB; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-35, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-95 { color: #914191; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-36, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-96 { color: #218D8D; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-37, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-97 { color: #707070; } - -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-40, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-100 { background-color: gray; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-41, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-101 { background-color: #A74747; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-42, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-102 { background-color: #348F34; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-43, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-103 { background-color: #5F4C29; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-44, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-104 { background-color: #6286BB; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-45, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-105 { background-color: #914191; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-46, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-106 { background-color: #218D8D; } -.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-47, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-107 { background-color: #707070; } - /* Links */ .monaco-workbench .repl .repl-tree .output.expression a { text-decoration: underline; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index ca293d8b2e75..58716135f9d7 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -11,7 +11,7 @@ import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import severity from 'vs/base/common/severity'; import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { ITextModel } from 'vs/editor/common/model'; @@ -76,6 +76,7 @@ interface IPrivateReplService { getVisibleContent(): string; selectSession(session?: IDebugSession): void; clearRepl(): void; + focusRepl(): void; } function revealLastElement(tree: WorkbenchAsyncDataTree) { @@ -133,15 +134,16 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } this.selectSession(); })); - this._register(this.debugService.onWillNewSession(() => { + this._register(this.debugService.onWillNewSession(newSession => { // Need to listen to output events for sessions which are not yet fully initialised const input = this.tree.getInput(); if (!input || input.state === State.Inactive) { - this.selectSession(); + this.selectSession(newSession); } this.updateTitleArea(); })); this._register(this.themeService.onThemeChange(() => { + this.refreshReplElements(false); if (this.isVisible()) { this.updateInputDecoration(); } @@ -181,6 +183,10 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.navigateHistory(false); } + focusRepl(): void { + this.tree.domFocus(); + } + private onDidFontChange(): void { if (this.styleElement) { const debugConsole = this.configurationService.getValue('debug').console; @@ -282,7 +288,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati const lineDelimiter = this.textResourcePropertiesService.getEOL(this.model.uri); const traverseAndAppend = (node: ITreeNode) => { node.children.forEach(child => { - text += child.element.toString() + lineDelimiter; + text += child.element.toString().trimRight() + lineDelimiter; if (!child.collapsed && child.children.length) { traverseAndAppend(child); } @@ -381,7 +387,16 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati supportDynamicHeights: true }) as WorkbenchAsyncDataTree; - this.toDispose.push(this.tree.onContextMenu(e => this.onContextMenu(e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); + let lastSelectedString: string; + this._register(this.tree.onMouseClick(() => { + const selection = window.getSelection(); + if (!selection || selection.type !== 'Range' || lastSelectedString === selection.toString()) { + // only focus the input if the user is not currently selecting. + this.replInput.focus(); + } + lastSelectedString = selection ? selection.toString() : ''; + })); // Make sure to select the session if debugging is already active this.selectSession(); this.styleElement = dom.createStyleSheet(this.container); @@ -606,7 +621,8 @@ class ReplSimpleElementsRenderer implements ITreeRenderer { + SuggestController.get(editor).acceptSelectedSuggestion(); + accessor.get(IPrivateReplService).focusRepl(); + } +} + class ReplCopyAllAction extends EditorAction { constructor() { @@ -851,6 +889,7 @@ class ReplCopyAllAction extends EditorAction { registerEditorAction(AcceptReplInputAction); registerEditorAction(ReplCopyAllAction); +registerEditorAction(FilterReplAction); class SelectReplActionViewItem extends FocusSessionActionViewItem { diff --git a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index 79cbd0b3d6b6..ea4b795364b6 100644 --- a/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -79,7 +79,7 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri this.styleElement = createStyleSheet(container); } - this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor} !important; }`; + this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .items-container > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor} !important; }`; } private getColorKey(noFolderColor: string, debuggingColor: string, normalColor: string): string { @@ -115,6 +115,6 @@ export function isStatusbarInDebugMode(debugService: IDebugService): boolean { registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const statusBarItemDebuggingForeground = theme.getColor(STATUS_BAR_DEBUGGING_FOREGROUND); if (statusBarItemDebuggingForeground) { - collector.addRule(`.monaco-workbench .part.statusbar.debugging > .statusbar-item .mask-icon { background-color: ${statusBarItemDebuggingForeground} !important; }`); + collector.addRule(`.monaco-workbench .part.statusbar.debugging > .items-container > .statusbar-item .mask-icon { background-color: ${statusBarItemDebuggingForeground} !important; }`); } }); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index b1da22b52636..8c39f0132445 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -88,7 +88,7 @@ export class VariablesView extends ViewletPanel { this.toolbar.setActions([collapseAction])(); this.tree.updateChildren(); - this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(sf => { + this._register(this.debugService.getViewModel().onDidFocusStackFrame(sf => { if (!this.isBodyVisible()) { this.needsRefresh = true; return; @@ -99,16 +99,16 @@ export class VariablesView extends ViewletPanel { const timeout = sf.explicit ? 0 : undefined; this.onFocusStackFrameScheduler.schedule(timeout); })); - this.disposables.push(variableSetEmitter.event(() => this.tree.updateChildren())); - this.disposables.push(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); - this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e))); + this._register(variableSetEmitter.event(() => this.tree.updateChildren())); + this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); - this.disposables.push(this.onDidChangeBodyVisibility(visible => { + this._register(this.onDidChangeBodyVisibility(visible => { if (visible && this.needsRefresh) { this.onFocusStackFrameScheduler.schedule(); } })); - this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(e => { + this._register(this.debugService.getViewModel().onDidSelectExpression(e => { if (e instanceof Variable) { this.tree.rerender(e); } diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index e5e2707bbe0d..d60223c4eb13 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -76,16 +76,16 @@ export class WatchExpressionsView extends ViewletPanel { const removeAllWatchExpressionsAction = new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService); this.toolbar.setActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])(); - this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e))); - this.disposables.push(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); - this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => { + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); + this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); + this._register(this.debugService.getModel().onDidChangeWatchExpressions(we => { if (!this.isBodyVisible()) { this.needsRefresh = true; } else { this.tree.updateChildren(); } })); - this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => { + this._register(this.debugService.getViewModel().onDidFocusStackFrame(() => { if (!this.isBodyVisible()) { this.needsRefresh = true; return; @@ -95,14 +95,14 @@ export class WatchExpressionsView extends ViewletPanel { this.onWatchExpressionsUpdatedScheduler.schedule(); } })); - this.disposables.push(variableSetEmitter.event(() => this.tree.updateChildren())); + this._register(variableSetEmitter.event(() => this.tree.updateChildren())); - this.disposables.push(this.onDidChangeBodyVisibility(visible => { + this._register(this.onDidChangeBodyVisibility(visible => { if (visible && this.needsRefresh) { this.onWatchExpressionsUpdatedScheduler.schedule(); } })); - this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(e => { + this._register(this.debugService.getViewModel().onDidSelectExpression(e => { if (e instanceof Expression && e.name) { this.tree.rerender(e); } diff --git a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts index 772a9d368a0f..238064428c39 100644 --- a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts +++ b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts @@ -13,7 +13,7 @@ import { IDebugAdapter } from 'vs/workbench/contrib/debug/common/debug'; export abstract class AbstractDebugAdapter implements IDebugAdapter { private sequence: number; - private pendingRequests: Map void>; + private pendingRequests = new Map void>(); private requestCallback: (request: DebugProtocol.Request) => void; private eventCallback: (request: DebugProtocol.Event) => void; private messageCallback: (message: DebugProtocol.ProtocolMessage) => void; @@ -22,7 +22,6 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { constructor() { this.sequence = 1; - this.pendingRequests = new Map(); this._onError = new Emitter(); this._onExit = new Emitter(); } @@ -139,7 +138,7 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { protected cancelPending() { const pending = this.pendingRequests; - this.pendingRequests = new Map(); + this.pendingRequests.clear(); setTimeout(_ => { pending.forEach((callback, request_seq) => { const err: DebugProtocol.Response = { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 24e67506137b..dc9cdb8af4a8 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -11,7 +11,7 @@ import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel as EditorIModel } from 'vs/editor/common/model'; -import { IEditor } from 'vs/workbench/common/editor'; +import { IEditor, ITextEditor } from 'vs/workbench/common/editor'; import { Position } from 'vs/editor/common/core/position'; import { CompletionItem } from 'vs/editor/common/modes'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; @@ -298,7 +298,7 @@ export interface IStackFrame extends ITreeElement { getSpecificSourceName(): string; restart(): Promise; toString(): string; - openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise; + openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise; } export interface IEnablement extends ITreeElement { @@ -339,6 +339,7 @@ export interface IBreakpoint extends IBaseBreakpoint { readonly endColumn?: number; readonly message?: string; readonly adapterData: any; + readonly sessionAgnosticData: { lineNumber: number, column: number | undefined }; } export interface IFunctionBreakpoint extends IBaseBreakpoint { @@ -717,7 +718,7 @@ export interface IDebugService { /** * Updates the breakpoints. */ - updateBreakpoints(uri: uri, data: { [id: string]: IBreakpointUpdateData }, sendOnResourceSaved: boolean): void; + updateBreakpoints(uri: uri, data: Map, sendOnResourceSaved: boolean): void; /** * Enables or disables all breakpoints. If breakpoint is passed only enables or disables the passed breakpoint. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 507cc0682ace..1f8f75979225 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -23,6 +23,7 @@ import { commonSuffixLength } from 'vs/base/common/strings'; import { posix } from 'vs/base/common/path'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextEditor } from 'vs/workbench/common/editor'; export class SimpleReplElement implements IReplElement { constructor( @@ -89,7 +90,7 @@ export class RawObjectReplElement implements IExpression { export class ExpressionContainer implements IExpressionContainer { - public static allValues: Map = new Map(); + public static allValues = new Map(); // Use chunks to support variable paging #9537 private static readonly BASE_CHUNK_SIZE = 100; @@ -302,7 +303,7 @@ export class Scope extends ExpressionContainer implements IScope { indexedVariables?: number, public range?: IRange ) { - super(stackFrame.thread.session, reference, `scope:${stackFrame.getId()}:${name}:${index}`, namedVariables, indexedVariables); + super(stackFrame.thread.session, reference, `scope:${name}:${index}`, namedVariables, indexedVariables); } toString(): string { @@ -387,7 +388,7 @@ export class StackFrame implements IStackFrame { return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`; } - openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { return !this.source.available ? Promise.resolve(null) : this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned); } @@ -659,6 +660,13 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return data ? data.endColumn : undefined; } + get sessionAgnosticData(): { lineNumber: number, column: number | undefined } { + return { + lineNumber: this._lineNumber, + column: this._column + }; + } + setSessionData(sessionId: string, data: DebugProtocol.Breakpoint): void { super.setSessionData(sessionId, data); if (!this._adapterData) { @@ -954,10 +962,10 @@ export class DebugModel implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed: toRemove }); } - updateBreakpoints(data: { [id: string]: IBreakpointUpdateData }): void { + updateBreakpoints(data: Map): void { const updated: IBreakpoint[] = []; this.breakpoints.forEach(bp => { - const bpData = data[bp.getId()]; + const bpData = data.get(bp.getId()); if (bpData) { bp.update(bpData); updated.push(bp); @@ -967,15 +975,15 @@ export class DebugModel implements IDebugModel { this._onDidChangeBreakpoints.fire({ changed: updated }); } - setBreakpointSessionData(sessionId: string, data: { [id: string]: DebugProtocol.Breakpoint }): void { + setBreakpointSessionData(sessionId: string, data: Map): void { this.breakpoints.forEach(bp => { - const bpData = data[bp.getId()]; + const bpData = data.get(bp.getId()); if (bpData) { bp.setSessionData(sessionId, bpData); } }); this.functionBreakpoints.forEach(fbp => { - const fbpData = data[fbp.getId()]; + const fbpData = data.get(fbp.getId()); if (fbpData) { fbp.setSessionData(sessionId, fbpData); } diff --git a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index 8920ff9a2486..513d0383d18f 100644 --- a/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -240,6 +240,8 @@ declare module DebugProtocol { 'attachForSuspendedLaunch': A project launcher component has launched a new process in a suspended state and then asked the debugger to attach. */ startMethod?: 'launch' | 'attach' | 'attachForSuspendedLaunch'; + /** The size of a pointer or address for this process, in bits. This value may be used by clients when formatting addresses for display. */ + pointerSize?: number; }; } @@ -323,6 +325,8 @@ declare module DebugProtocol { supportsVariablePaging?: boolean; /** Client supports the runInTerminal request. */ supportsRunInTerminalRequest?: boolean; + /** Client supports memory references. */ + supportsMemoryReferences?: boolean; } /** Response to 'initialize' request. */ @@ -1047,6 +1051,8 @@ declare module DebugProtocol { The client can use this optional information to present the variables in a paged UI and fetch them in chunks. */ indexedVariables?: number; + /** Memory reference to a location appropriate for this result. For pointer type eval results, this is generally a reference to the memory address contained in the pointer. */ + memoryReference?: string; }; } @@ -1202,6 +1208,66 @@ declare module DebugProtocol { }; } + /** ReadMemory request; value of command field is 'readMemory'. + Reads bytes from memory at the provided location. + */ + export interface ReadMemoryRequest extends Request { + // command: 'readMemory'; + arguments: ReadMemoryArguments; + } + + /** Arguments for 'readMemory' request. */ + export interface ReadMemoryArguments { + /** Memory reference to the base location from which data should be read. */ + memoryReference: string; + /** Optional offset (in bytes) to be applied to the reference location before reading data. Can be negative. */ + offset?: number; + /** Number of bytes to read at the specified location and offset. */ + count: number; + } + + /** Response to 'readMemory' request. */ + export interface ReadMemoryResponse extends Response { + body?: { + /** The address of the first byte of data returned. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */ + address: string; + /** The number of unreadable bytes encountered after the last successfully read byte. This can be used to determine the number of bytes that must be skipped before a subsequent 'readMemory' request will succeed. */ + unreadableBytes?: number; + /** The bytes read from memory, encoded using base64. */ + data?: string; + }; + } + + /** Disassemble request; value of command field is 'disassemble'. + Disassembles code stored at the provided location. + */ + export interface DisassembleRequest extends Request { + // command: 'disassemble'; + arguments: DisassembleArguments; + } + + /** Arguments for 'disassemble' request. */ + export interface DisassembleArguments { + /** Memory reference to the base location containing the instructions to disassemble. */ + memoryReference: string; + /** Optional offset (in bytes) to be applied to the reference location before disassembling. Can be negative. */ + offset?: number; + /** Optional offset (in instructions) to be applied after the byte offset (if any) before disassembling. Can be negative. */ + instructionOffset?: number; + /** Number of instructions to disassemble starting at the specified location and offset. An adapter must return exactly this number of instructions - any unavailable instructions should be replaced with an implementation-defined 'invalid instruction' value. */ + instructionCount: number; + /** If true, the adapter should attempt to resolve memory addresses and other values to symbolic names. */ + resolveSymbols?: boolean; + } + + /** Response to 'disassemble' request. */ + export interface DisassembleResponse extends Response { + body?: { + /** The list of disassembled instructions. */ + instructions: DisassembledInstruction[]; + }; + } + /** Information about the capabilities of a debug adapter. */ export interface Capabilities { /** The debug adapter supports the 'configurationDone' request. */ @@ -1258,6 +1324,10 @@ declare module DebugProtocol { supportsTerminateRequest?: boolean; /** The debug adapter supports data breakpoints. */ supportsDataBreakpoints?: boolean; + /** The debug adapter supports the 'readMemory' request. */ + supportsReadMemoryRequest?: boolean; + /** The debug adapter supports the 'disassemble' request. */ + supportsDisassembleRequest?: boolean; } /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ @@ -1393,6 +1463,8 @@ declare module DebugProtocol { endLine?: number; /** An optional end column of the range covered by the stack frame. */ endColumn?: number; + /** Optional memory reference for the current instruction pointer in this frame. */ + instructionPointerReference?: string; /** The module associated with this frame, if any. */ moduleId?: number | string; /** An optional hint for how to present this frame in the UI. A value of 'label' can be used to indicate that the frame is an artificial frame that is used as a visual label or separator. A value of 'subtle' can be used to change the appearance of a frame in a 'subtle' way. */ @@ -1401,8 +1473,16 @@ declare module DebugProtocol { /** A Scope is a named container for variables. Optionally a scope can map to a source or a range within a source. */ export interface Scope { - /** Name of the scope such as 'Arguments', 'Locals'. */ + /** Name of the scope such as 'Arguments', 'Locals', or 'Registers'. This string is shown in the UI as is and can be translated. */ name: string; + /** An optional hint for how to present this scope in the UI. If this attribute is missing, the scope is shown with a generic UI. + Values: + 'arguments': Scope contains method arguments. + 'locals': Scope contains local variables. + 'registers': Scope contains registers. Only a single 'registers' scope should be returned from a 'scopes' request. + etc. + */ + presentationHint?: string; /** The variables of this scope can be retrieved by passing the value of variablesReference to the VariablesRequest. */ variablesReference: number; /** The number of named variables in this scope. @@ -1455,6 +1535,8 @@ declare module DebugProtocol { The client can use this optional information to present the children in a paged UI and fetch them in chunks. */ indexedVariables?: number; + /** Optional memory reference for the variable if the variable represents executable code, such as a function pointer. */ + memoryReference?: string; } /** Optional properties of a variable that can be used to determine how to render the variable in the UI. */ @@ -1576,6 +1658,8 @@ declare module DebugProtocol { endLine?: number; /** An optional end column of the range covered by the goto target. */ endColumn?: number; + /** Optional memory reference for the instruction pointer value represented by this target. */ + instructionPointerReference?: string; } /** CompletionItems are the suggestions returned from the CompletionsRequest. */ @@ -1673,5 +1757,27 @@ declare module DebugProtocol { /** Details of the exception contained by this exception, if any. */ innerException?: ExceptionDetails[]; } + + /** Represents a single disassembled instruction. */ + export interface DisassembledInstruction { + /** The address of the instruction. Treated as a hex value if prefixed with '0x', or as a decimal value otherwise. */ + address: string; + /** Optional raw bytes representing the instruction and its operands, in an implementation-defined format. */ + instructionBytes?: string; + /** Text representing the instruction and its operands, in an implementation-defined format. */ + instruction: string; + /** Name of the symbol that correponds with the location of this instruction, if any. */ + symbol?: string; + /** Source location that corresponds to this instruction, if any. Should always be set (if available) on the first instruction returned, but can be omitted afterwards if this instruction maps to the same source file as the previous instruction. */ + location?: Source; + /** The line within the source location that corresponds to this instruction, if any. */ + line?: number; + /** The column within the line that corresponds to this instruction, if any. */ + column?: number; + /** The end line of the range that corresponds to this instruction, if any. */ + endLine?: number; + /** The end column of the range that corresponds to this instruction, if any. */ + endColumn?: number; + } } diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index 34bf2951341b..27390162b1d2 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -47,7 +47,7 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE type: 'array' }, variables: { - description: nls.localize('vscode.extension.contributes.debuggers.variables', "Mapping from interactive variables (e.g ${action.pickProcess}) in `launch.json` to a command."), + description: nls.localize('vscode.extension.contributes.debuggers.variables', "Mapping from interactive variables (e.g. ${action.pickProcess}) in `launch.json` to a command."), type: 'object' }, initialConfigurations: { diff --git a/src/vs/workbench/contrib/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts index bf53f2ca905e..fe4fd729d497 100644 --- a/src/vs/workbench/contrib/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -12,6 +12,7 @@ import { IRange } from 'vs/editor/common/core/range'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { Schemas } from 'vs/base/common/network'; import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { ITextEditor } from 'vs/workbench/common/editor'; export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source"); @@ -25,7 +26,6 @@ export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Sourc * | | | | * scheme source.path session id source.reference * - * the arbitrary_path and the session id are encoded with 'encodeURIComponent' * */ @@ -48,7 +48,11 @@ export class Source { } if (typeof this.raw.sourceReference === 'number' && this.raw.sourceReference > 0) { - this.uri = uri.parse(`${DEBUG_SCHEME}:${encodeURIComponent(path)}?session=${encodeURIComponent(sessionId)}&ref=${this.raw.sourceReference}`); + this.uri = uri.from({ + scheme: DEBUG_SCHEME, + path, + query: `session=${sessionId}&ref=${this.raw.sourceReference}` + }); } else { if (isUri(path)) { // path looks like a uri this.uri = uri.parse(path); @@ -59,7 +63,11 @@ export class Source { } else { // path is relative: since VS Code cannot deal with this by itself // create a debug url that will result in a DAP 'source' request when the url is resolved. - this.uri = uri.parse(`${DEBUG_SCHEME}:${encodeURIComponent(path)}?session=${encodeURIComponent(sessionId)}`); + this.uri = uri.from({ + scheme: DEBUG_SCHEME, + path, + query: `session=${sessionId}` + }); } } } @@ -85,14 +93,13 @@ export class Source { return this.uri.scheme === DEBUG_SCHEME; } - openInEditor(editorService: IEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + openInEditor(editorService: IEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { return !this.available ? Promise.resolve(null) : editorService.openEditor({ resource: this.uri, description: this.origin, options: { preserveFocus, selection, - revealIfVisible: true, revealIfOpened: true, revealInCenterIfOutsideViewport: true, pinned: pinned || (!preserveFocus && !this.inMemory) @@ -118,7 +125,7 @@ export class Source { if (pair.length === 2) { switch (pair[0]) { case 'session': - sessionId = decodeURIComponent(pair[1]); + sessionId = pair[1]; break; case 'ref': sourceReference = parseInt(pair[1]); diff --git a/src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts index 78a2a0171f0a..a59072a40adb 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts @@ -15,8 +15,6 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v import { IWorkbenchActionRegistry, Extensions as WorkbenchActionRegistryExtensions } from 'vs/workbench/common/actions'; import { ShowViewletAction, Extensions as ViewletExtensions, ViewletRegistry, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { TogglePanelAction, Extensions as PanelExtensions, PanelRegistry, PanelDescriptor } from 'vs/workbench/browser/panel'; -import { StatusbarItemDescriptor, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { BreakpointsView } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; @@ -40,7 +38,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; import { DebugViewlet } from 'vs/workbench/contrib/debug/browser/debugViewlet'; import { DebugQuickOpenHandler } from 'vs/workbench/contrib/debug/browser/debugQuickOpen'; -import { DebugStatus } from 'vs/workbench/contrib/debug/browser/debugStatus'; +import { 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'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -259,8 +257,7 @@ configurationRegistry.registerConfiguration({ }); // Register Debug Status -const statusBar = Registry.as(StatusExtensions.Statusbar); -statusBar.registerStatusbarItem(new StatusbarItemDescriptor(DebugStatus, StatusbarAlignment.LEFT, 30 /* Low Priority */)); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugStatusContribution, LifecyclePhase.Eventually); // Debug toolbar @@ -537,7 +534,9 @@ if (isMacintosh) { const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpr, icon: string) => { MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { command: { - id, title, iconLocation: { dark: URI.parse(require.toUrl(`vs/workbench/contrib/debug/electron-browser/media/${icon}`)) } + id, + title, + iconLocation: { dark: URI.parse(require.toUrl(`vs/workbench/contrib/debug/electron-browser/media/${icon}`)) } }, when, group: '9_debug', @@ -549,9 +548,9 @@ if (isMacintosh) { registerTouchBarEntry(RunAction.ID, RunAction.LABEL, 1, CONTEXT_IN_DEBUG_MODE.toNegated(), 'continue-without-debugging-tb.png'); registerTouchBarEntry(CONTINUE_ID, continueLabel, 0, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), 'continue-tb.png'); registerTouchBarEntry(PAUSE_ID, pauseLabel, 1, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.notEquals('debugState', 'stopped')), 'pause-tb.png'); - registerTouchBarEntry(STEP_OVER_ID, stepOverLabel, 2, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), 'stepover-tb.png'); - registerTouchBarEntry(STEP_INTO_ID, stepIntoLabel, 3, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), 'stepinto-tb.png'); - registerTouchBarEntry(STEP_OUT_ID, stepOutLabel, 4, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), 'stepout-tb.png'); + registerTouchBarEntry(STEP_OVER_ID, stepOverLabel, 2, CONTEXT_IN_DEBUG_MODE, 'stepover-tb.png'); + registerTouchBarEntry(STEP_INTO_ID, stepIntoLabel, 3, CONTEXT_IN_DEBUG_MODE, 'stepinto-tb.png'); + registerTouchBarEntry(STEP_OUT_ID, stepOutLabel, 4, CONTEXT_IN_DEBUG_MODE, 'stepout-tb.png'); registerTouchBarEntry(RESTART_SESSION_ID, restartLabel, 5, CONTEXT_IN_DEBUG_MODE, 'restart-tb.png'); registerTouchBarEntry(STOP_ID, stopLabel, 6, CONTEXT_IN_DEBUG_MODE, 'stop-tb.png'); } diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts index 58f4edafec58..fdc8e29d175b 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts @@ -52,7 +52,7 @@ export class ConfigurationManager implements IConfigurationManager { private _onDidSelectConfigurationName = new Emitter(); private configProviders: IDebugConfigurationProvider[]; private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[]; - private debugAdapterFactories: Map; + private debugAdapterFactories = new Map(); private terminalLauncher: ITerminalLauncher; private debugConfigurationTypeContext: IContextKey; @@ -78,7 +78,6 @@ export class ConfigurationManager implements IConfigurationManager { const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); const previousSelectedLaunch = this.launches.filter(l => l.uri.toString() === previousSelectedRoot).pop(); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); - this.debugAdapterFactories = new Map(); if (previousSelectedLaunch) { this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE)); } diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugService.ts b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts index d3ce1ebe6b8d..5d7fb630eed8 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts @@ -46,6 +46,7 @@ import { isExtensionHostDebugging } from 'vs/workbench/contrib/debug/common/debu import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint'; const DEBUG_BREAKPOINTS_ACTIVATED_KEY = 'debug.breakpointactivated'; @@ -431,9 +432,6 @@ export class DebugService implements IDebugService { return this.launchOrAttachToSession(session).then(() => { - // since the initialized response has arrived announce the new Session (including extensions) - this._onDidNewSession.fire(session); - const internalConsoleOptions = session.configuration.internalConsoleOptions || this.configurationService.getValue('debug').internalConsoleOptions; if (internalConsoleOptions === 'openOnSessionStart' || (this.viewModel.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) { this.panelService.openPanel(REPL_ID, false); @@ -447,6 +445,9 @@ export class DebugService implements IDebugService { this.viewModel.setMultiSessionView(true); } + // since the initialized response has arrived announce the new Session (including extensions) + this._onDidNewSession.fire(session); + return this.telemetryDebugSessionStart(root, session.configuration.type); }).then(() => true, (error: Error | string) => { @@ -658,9 +659,9 @@ export class DebugService implements IDebugService { return Promise.resolve(config); } - private showError(message: string, actions: IAction[] = []): Promise { + private showError(message: string, errorActions: ReadonlyArray = []): Promise { const configureAction = this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL); - actions.push(configureAction); + const actions = [...errorActions, configureAction]; return this.dialogService.show(severity.Error, message, actions.map(a => a.label).concat(nls.localize('cancel', "Cancel")), { cancelId: actions.length }).then(choice => { if (choice < actions.length) { return actions[choice].run(); @@ -789,8 +790,15 @@ export class DebugService implements IDebugService { } if (stackFrame) { - stackFrame.openInEditor(this.editorService, true); - aria.alert(nls.localize('debuggingPaused', "Debugging paused {0}, {1} {2}", thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber)); + stackFrame.openInEditor(this.editorService, true).then(editor => { + if (editor) { + const control = editor.getControl(); + if (stackFrame && isCodeEditor(control) && control.hasModel()) { + const lineContent = control.getModel().getLineContent(stackFrame.range.startLineNumber); + aria.alert(nls.localize('debuggingPaused', "Debugging paused {0}, {1} {2} {3}", thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber, lineContent)); + } + } + }); } if (session) { this.debugType.set(session.configuration.type); @@ -846,7 +854,7 @@ export class DebugService implements IDebugService { return this.sendBreakpoints(uri).then(() => breakpoints); } - updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }, sendOnResourceSaved: boolean): void { + updateBreakpoints(uri: uri, data: Map, sendOnResourceSaved: boolean): void { this.model.updateBreakpoints(data); if (sendOnResourceSaved) { this.breakpointsToSendOnResourceSaved.add(uri.toString()); diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts b/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts index 52790f47301d..96279c3b9e07 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts @@ -282,14 +282,14 @@ export class DebugSession implements IDebugSession { return this.raw.setBreakpoints({ source: rawSource, - lines: breakpointsToSend.map(bp => bp.lineNumber), - breakpoints: breakpointsToSend.map(bp => ({ line: bp.lineNumber, column: bp.column, condition: bp.condition, hitCondition: bp.hitCondition, logMessage: bp.logMessage })), + lines: breakpointsToSend.map(bp => bp.sessionAgnosticData.lineNumber), + breakpoints: breakpointsToSend.map(bp => ({ line: bp.sessionAgnosticData.lineNumber, column: bp.sessionAgnosticData.column, condition: bp.condition, hitCondition: bp.hitCondition, logMessage: bp.logMessage })), sourceModified }).then(response => { if (response && response.body) { - const data: { [id: string]: DebugProtocol.Breakpoint } = Object.create(null); + const data = new Map(); for (let i = 0; i < breakpointsToSend.length; i++) { - data[breakpointsToSend[i].getId()] = response.body.breakpoints[i]; + data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]); } this.model.setBreakpointSessionData(this.getId(), data); @@ -302,9 +302,9 @@ export class DebugSession implements IDebugSession { if (this.raw.readyForBreakpoints) { return this.raw.setFunctionBreakpoints({ breakpoints: fbpts }).then(response => { if (response && response.body) { - const data: { [id: string]: DebugProtocol.Breakpoint } = Object.create(null); + const data = new Map(); for (let i = 0; i < fbpts.length; i++) { - data[fbpts[i].getId()] = response.body.breakpoints[i]; + data.set(fbpts[i].getId(), response.body.breakpoints[i]); } this.model.setBreakpointSessionData(this.getId(), data); } @@ -753,7 +753,8 @@ export class DebugSession implements IDebugSession { lineNumber: event.body.breakpoint.line, }], false); if (bps.length === 1) { - this.model.setBreakpointSessionData(this.getId(), { [bps[0].getId()]: event.body.breakpoint }); + const data = new Map([[bps[0].getId(), event.body.breakpoint]]); + this.model.setBreakpointSessionData(this.getId(), data); } } @@ -771,10 +772,12 @@ export class DebugSession implements IDebugSession { if (!breakpoint.column) { event.body.breakpoint.column = undefined; } - this.model.setBreakpointSessionData(this.getId(), { [breakpoint.getId()]: event.body.breakpoint }); + const data = new Map([[breakpoint.getId(), event.body.breakpoint]]); + this.model.setBreakpointSessionData(this.getId(), data); } if (functionBreakpoint) { - this.model.setBreakpointSessionData(this.getId(), { [functionBreakpoint.getId()]: event.body.breakpoint }); + const data = new Map([[functionBreakpoint.getId(), event.body.breakpoint]]); + this.model.setBreakpointSessionData(this.getId(), data); } } })); diff --git a/src/vs/workbench/contrib/debug/electron-browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/electron-browser/rawDebugSession.ts index 02c7022392ac..efd61225ab8d 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/rawDebugSession.ts @@ -487,7 +487,7 @@ export class RawDebugSession { } } - private dispatchRequest(request: DebugProtocol.Request, dbgr: IDebugger): void { + private async dispatchRequest(request: DebugProtocol.Request, dbgr: IDebugger): Promise { const response: DebugProtocol.Response = { type: 'response', @@ -528,7 +528,7 @@ export class RawDebugSession { break; case 'handshake': try { - const vsda = require.__$__nodeRequire('vsda'); + const vsda = await import('vsda'); const obj = new vsda.signer(); const sig = obj.sign(request.arguments.value); response.body = { diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 46d052be42ae..81fff85edf08 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -348,8 +348,10 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments quote = (s: string) => { s = s.replace(/\'/g, '\'\''); + if (s.length > 0 && s.charAt(s.length - 1) === '\\') { + return `'${s}\\'`; + } return `'${s}'`; - //return s.indexOf(' ') >= 0 || s.indexOf('\'') >= 0 || s.indexOf('"') >= 0 ? `'${s}'` : s; }; if (args.cwd) { @@ -407,8 +409,8 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments case ShellType.bash: quote = (s: string) => { - s = s.replace(/\"/g, '\\"'); - return (s.indexOf(' ') >= 0 || s.indexOf('\\') >= 0) ? `"${s}"` : s; + s = s.replace(/([\"\\])/g, '\\$1'); + return s.indexOf(' ') >= 0 ? `"${s}"` : s; }; const hardQuote = (s: string) => { diff --git a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts index 804611228c43..f83e631ca899 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -11,6 +11,9 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { Color, RGBA } from 'vs/base/common/color'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TestThemeService, TestTheme } from 'vs/platform/theme/test/common/testThemeService'; +import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; suite('Debug - ANSI Handling', () => { test('appendStylizedStringToContainer', () => { diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 3b5db8c9c653..71077188b23a 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -51,7 +51,7 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } - public updateBreakpoints(uri: uri, data: { [id: string]: IBreakpointUpdateData }, sendOnResourceSaved: boolean): void { } + public updateBreakpoints(uri: uri, data: Map, sendOnResourceSaved: boolean): void { } public enableOrDisableBreakpoints(enabled: boolean): Promise { throw new Error('not implemented'); diff --git a/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts index 0cf96e3e6463..7fe5c3d54e42 100644 --- a/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts @@ -12,6 +12,7 @@ import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugSession } from 'vs/workbench/contrib/debug/electron-browser/debugSession'; import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; +import { IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; function createMockSession(model: DebugModel, name = 'mockSession', parentSession?: DebugSession | undefined): DebugSession { return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); @@ -63,8 +64,8 @@ suite('Debug - Model', () => { assert.equal(model.getBreakpoints().length, 5); const bp = model.getBreakpoints()[0]; - const update: any = {}; - update[bp.getId()] = { lineNumber: 100 }; + const update = new Map(); + update.set(bp.getId(), { lineNumber: 100 }); model.updateBreakpoints(update); assert.equal(bp.lineNumber, 100); diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts b/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts index 28ee86712a81..bae5a5c968db 100644 --- a/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts +++ b/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts @@ -9,11 +9,10 @@ import { IExperimentService, IExperiment, ExperimentActionType, IExperimentActio import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { language } from 'vs/base/common/platform'; export class ExperimentalPrompts extends Disposable implements IWorkbenchContribution { - private _disposables: IDisposable[] = []; constructor( @IExperimentService private readonly experimentService: IExperimentService, @@ -23,11 +22,11 @@ export class ExperimentalPrompts extends Disposable implements IWorkbenchContrib ) { super(); - this.experimentService.onExperimentEnabled(e => { + this._register(this.experimentService.onExperimentEnabled(e => { if (e.action && e.action.type === ExperimentActionType.Prompt && e.state === ExperimentState.Run) { this.showExperimentalPrompts(e); } - }, this, this._disposables); + }, this)); } private showExperimentalPrompts(experiment: IExperiment): void { @@ -91,10 +90,6 @@ export class ExperimentalPrompts extends Disposable implements IWorkbenchContrib }); } - dispose() { - this._disposables = dispose(this._disposables); - } - static getLocalizedText(text: string | { [key: string]: string }, displayLanguage: string): string { if (typeof text === 'string') { return text; diff --git a/src/vs/workbench/contrib/experiments/node/experimentService.ts b/src/vs/workbench/contrib/experiments/node/experimentService.ts index d4fc6203c973..ab2b50e18a47 100644 --- a/src/vs/workbench/contrib/experiments/node/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/node/experimentService.ts @@ -115,9 +115,9 @@ export class ExperimentService extends Disposable implements IExperimentService private _curatedMapping = Object.create(null); private _disposables: IDisposable[] = []; - private readonly _onExperimentEnabled = new Emitter(); - + private readonly _onExperimentEnabled = this._register(new Emitter()); onExperimentEnabled: Event = this._onExperimentEnabled.event; + constructor( @IStorageService private readonly storageService: IStorageService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 7b6d5847593e..23c082d8edbe 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -255,7 +255,7 @@ export class ExtensionData implements IExtensionData { toQuery.push(id); } } - const galleryResult = await this.extensionsWorkbenchService.queryGallery({ names: this.childrenExtensionIds, pageSize: this.childrenExtensionIds.length }, CancellationToken.None); + const galleryResult = await this.extensionsWorkbenchService.queryGallery({ names: toQuery, pageSize: toQuery.length }, CancellationToken.None); result.push(...galleryResult.firstPage); return result.map(extension => new ExtensionData(extension, this, this.getChildrenExtensionIds, this.extensionsWorkbenchService)); } diff --git a/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts index d33787e72c73..d227d18ba2e9 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts @@ -7,7 +7,7 @@ import * as arrays from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService, IExtensionTipsService, IExtensionIdentifier, EnablementState, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -22,9 +22,7 @@ export interface IExtensionStatus { globallyEnabled: boolean; } -export class KeymapExtensions implements IWorkbenchContribution { - - private disposables: IDisposable[] = []; +export class KeymapExtensions extends Disposable implements IWorkbenchContribution { constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -34,13 +32,12 @@ export class KeymapExtensions implements IWorkbenchContribution { @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, ) { - this.disposables.push( - lifecycleService.onShutdown(() => this.dispose()), - instantiationService.invokeFunction(onExtensionChanged)((identifiers => { - Promise.all(identifiers.map(identifier => this.checkForOtherKeymaps(identifier))) - .then(undefined, onUnexpectedError); - })) - ); + super(); + this._register(lifecycleService.onShutdown(() => this.dispose())); + this._register(instantiationService.invokeFunction(onExtensionChanged)((identifiers => { + Promise.all(identifiers.map(identifier => this.checkForOtherKeymaps(identifier))) + .then(undefined, onUnexpectedError); + }))); } private checkForOtherKeymaps(extensionIdentifier: IExtensionIdentifier): Promise { @@ -87,10 +84,6 @@ export class KeymapExtensions implements IWorkbenchContribution { }] ); } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } export function onExtensionChanged(accessor: ServicesAccessor): Event { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts index 2ea647db3a24..68151f1f27b2 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts @@ -13,7 +13,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { Cache, CacheResult } from 'vs/base/common/cache'; import { Action } from 'vs/base/common/actions'; import { isPromiseCanceledError } from 'vs/base/common/errors'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { domEvent } from 'vs/base/browser/event'; import { append, $, addClass, removeClass, finalHandler, join, toggleClass, hide, show } from 'vs/base/browser/dom'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -89,9 +89,9 @@ function removeEmbeddedSVGs(documentContent: string): string { return newDocument.documentElement.outerHTML; } -class NavBar { +class NavBar extends Disposable { - private _onChange = new Emitter<{ id: string | null, focus: boolean }>(); + private _onChange = this._register(new Emitter<{ id: string | null, focus: boolean }>()); get onChange(): Event<{ id: string | null, focus: boolean }> { return this._onChange.event; } private currentId: string | null = null; @@ -99,9 +99,10 @@ class NavBar { private actionbar: ActionBar; constructor(container: HTMLElement) { + super(); const element = append(container, $('.navbar')); this.actions = []; - this.actionbar = new ActionBar(element, { animated: false }); + this.actionbar = this._register(new ActionBar(element, { animated: false })); } push(id: string, label: string, tooltip: string): void { @@ -132,10 +133,6 @@ class NavBar { this.actions.forEach(a => a.enabled = a.id !== id); return Promise.resolve(undefined); } - - dispose(): void { - dispose(this.actionbar); - } } const NavbarSection = { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index f9c4ebac5426..597733b167f7 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -5,14 +5,11 @@ import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionHostProfile, ProfileSession, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { append, $, addDisposableListener } from 'vs/base/browser/dom'; -import { IStatusbarRegistry, StatusbarItemDescriptor, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; import { IExtensionHostProfileService, ProfileSessionState } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWindowsService } from 'vs/platform/windows/common/windows'; @@ -22,10 +19,11 @@ import product from 'vs/platform/product/node/product'; import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private readonly _onDidChangeState: Emitter = this._register(new Emitter()); public readonly onDidChangeState: Event = this._onDidChangeState.event; @@ -38,6 +36,9 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio private _profileSession: ProfileSession | null; private _state: ProfileSessionState; + private profilingStatusBarIndicator: IStatusbarEntryAccessor | undefined; + private profilingStatusBarIndicatorLabelUpdater: IDisposable | undefined; + public get state() { return this._state; } public get lastProfile() { return this._profile; } @@ -46,12 +47,18 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio @IEditorService private readonly _editorService: IEditorService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IWindowsService private readonly _windowsService: IWindowsService, - @IDialogService private readonly _dialogService: IDialogService + @IDialogService private readonly _dialogService: IDialogService, + @IStatusbarService private readonly _statusbarService: IStatusbarService, ) { super(); this._profile = null; this._profileSession = null; this._setState(ProfileSessionState.None); + + CommandsRegistry.registerCommand('workbench.action.extensionHostProfilder.stop', () => { + this.stopProfiling(); + this._editorService.openEditor(this._instantiationService.createInstance(RuntimeExtensionsInput), { revealIfOpened: true }); + }); } private _setState(state: ProfileSessionState): void { @@ -61,17 +68,48 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio this._state = state; if (this._state === ProfileSessionState.Running) { - ProfileExtHostStatusbarItem.instance.show(() => { - this.stopProfiling(); - this._editorService.openEditor(this._instantiationService.createInstance(RuntimeExtensionsInput), { revealIfOpened: true }); - }); + this.updateProfilingStatusBarIndicator(true); } else if (this._state === ProfileSessionState.Stopping) { - ProfileExtHostStatusbarItem.instance.hide(); + this.updateProfilingStatusBarIndicator(false); } this._onDidChangeState.fire(undefined); } + private updateProfilingStatusBarIndicator(visible: boolean): void { + if (this.profilingStatusBarIndicatorLabelUpdater) { + this.profilingStatusBarIndicatorLabelUpdater.dispose(); + this.profilingStatusBarIndicatorLabelUpdater = undefined; + } + + if (visible) { + const indicator: IStatusbarEntry = { + text: nls.localize('profilingExtensionHost', "$(sync~spin) Profiling Extension Host"), + tooltip: nls.localize('selectAndStartDebug', "Click to stop profiling."), + command: 'workbench.action.extensionHostProfilder.stop' + }; + + const timeStarted = Date.now(); + const handle = setInterval(() => { + if (this.profilingStatusBarIndicator) { + this.profilingStatusBarIndicator.update({ ...indicator, text: nls.localize('profilingExtensionHostTime', "$(sync~spin) Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), }); + } + }, 1000); + this.profilingStatusBarIndicatorLabelUpdater = toDisposable(() => clearInterval(handle)); + + if (!this.profilingStatusBarIndicator) { + this.profilingStatusBarIndicator = this._statusbarService.addEntry(indicator, 'status.profiler', nls.localize('status.profiler', "Extension Profiler"), StatusbarAlignment.RIGHT); + } else { + this.profilingStatusBarIndicator.update(indicator); + } + } else { + if (this.profilingStatusBarIndicator) { + this.profilingStatusBarIndicator.dispose(); + this.profilingStatusBarIndicator = undefined; + } + } + } + public startProfiling(): Promise | null { if (this._state !== ProfileSessionState.None) { return null; @@ -134,76 +172,3 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio } } - -export class ProfileExtHostStatusbarItem implements IStatusbarItem { - - public static instance: ProfileExtHostStatusbarItem; - - private toDispose: IDisposable[]; - private statusBarItem: HTMLElement; - private label: HTMLElement; - private timeStarted: number; - private labelUpdater: any; - private clickHandler: (() => void) | null; - - constructor() { - ProfileExtHostStatusbarItem.instance = this; - this.toDispose = []; - this.timeStarted = 0; - } - - public show(clickHandler: () => void) { - this.clickHandler = clickHandler; - if (this.timeStarted === 0) { - this.timeStarted = new Date().getTime(); - this.statusBarItem.hidden = false; - this.labelUpdater = setInterval(() => { - this.updateLabel(); - }, 1000); - this.updateLabel(); - } - } - - public hide() { - this.clickHandler = null; - this.statusBarItem.hidden = true; - this.timeStarted = 0; - clearInterval(this.labelUpdater); - this.labelUpdater = null; - } - - public render(container: HTMLElement): IDisposable { - if (!this.statusBarItem && container) { - this.statusBarItem = append(container, $('.profileExtHost-statusbar-item')); - this.toDispose.push(addDisposableListener(this.statusBarItem, 'click', () => { - if (this.clickHandler) { - this.clickHandler(); - } - })); - this.statusBarItem.title = nls.localize('selectAndStartDebug', "Click to stop profiling."); - const a = append(this.statusBarItem, $('a')); - append(a, $('.icon')); - this.label = append(a, $('span.label')); - this.updateLabel(); - this.statusBarItem.hidden = true; - } - return this; - } - - private updateLabel() { - let label = 'Profiling Extension Host'; - if (this.timeStarted > 0) { - let secondsRecoreded = (new Date().getTime() - this.timeStarted) / 1000; - label = `Profiling Extension Host (${Math.round(secondsRecoreded)} sec)`; - } - this.label.textContent = label; - } - - public dispose(): void { - this.toDispose = dispose(this.toDispose); - } -} - -Registry.as(Extensions.Statusbar).registerStatusbarItem( - new StatusbarItemDescriptor(ProfileExtHostStatusbarItem, StatusbarAlignment.RIGHT) -); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts index c3e300e55f2c..0cb5f57fdd41 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts @@ -11,7 +11,7 @@ import { match } from 'vs/base/common/glob'; import * as json from 'vs/base/common/json'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, ExtensionRecommendationReason, EXTENSION_IDENTIFIER_PATTERN, - IExtensionsConfigContent, RecommendationChangeNotification, IExtensionRecommendation, ExtensionRecommendationSource, InstallOperation + IExtensionsConfigContent, RecommendationChangeNotification, IExtensionRecommendation, ExtensionRecommendationSource, InstallOperation, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModel } from 'vs/editor/common/model'; @@ -90,7 +90,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe public loadWorkspaceConfigPromise: Promise; private proactiveRecommendationsFetched: boolean = false; - private readonly _onRecommendationChange = new Emitter(); + private readonly _onRecommendationChange = this._register(new Emitter()); onRecommendationChange: Event = this._onRecommendationChange.event; private sessionSeed: number; @@ -597,8 +597,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe * or prompt to search the marketplace if it has extensions that can support the file type */ private promptFiletypeBasedRecommendations(model: ITextModel): void { - let hasSuggestion = false; - const uri = model.uri; if (!uri || !this.fileService.canHandleResource(uri)) { return; @@ -612,231 +610,244 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe processedFileExtensions.push(fileExtension); } - // re-schedule this bit of the operation to be off - // the critical path - in case glob-match is slow - setImmediate(() => { - - let recommendationsToSuggest: string[] = []; - const now = Date.now(); - forEach(this._availableRecommendations, entry => { - let { key: pattern, value: ids } = entry; - if (match(pattern, uri.path)) { - for (let id of ids) { - if (caseInsensitiveGet(product.extensionImportantTips, id)) { - recommendationsToSuggest.push(id); - } - const filedBasedRecommendation = this._fileBasedRecommendations[id.toLowerCase()] || { recommendedTime: now, sources: [] }; - filedBasedRecommendation.recommendedTime = now; - if (!filedBasedRecommendation.sources.some(s => s instanceof URI && s.toString() === uri.toString())) { - filedBasedRecommendation.sources.push(uri); - } - this._fileBasedRecommendations[id.toLowerCase()] = filedBasedRecommendation; - } - } - }); + // re-schedule this bit of the operation to be off the critical path - in case glob-match is slow + setImmediate(async () => { + const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); + if (await this.promptRecommendedExtensionForFileType(model, installed)) { + return; + } - this.storageService.store( - 'extensionsAssistant/recommendations', - JSON.stringify(Object.keys(this._fileBasedRecommendations).reduce((result, key) => { result[key] = this._fileBasedRecommendations[key].recommendedTime; return result; }, {})), - StorageScope.GLOBAL - ); + if (fileExtension) { + fileExtension = fileExtension.substr(1); // Strip the dot + } + if (!fileExtension) { + return; + } - const config = this.configurationService.getValue(ConfigurationKey); - if (config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand) { + await this.extensionService.whenInstalledExtensionsRegistered(); + const mimeTypes = await guessMimeTypes(uri.fsPath); + if (mimeTypes.length !== 1 || mimeTypes[0] !== MIME_UNKNOWN) { return; } - const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); - recommendationsToSuggest = recommendationsToSuggest.filter(id => importantRecommendationsIgnoreList.indexOf(id) === -1 && this.isExtensionAllowedToBeRecommended(id)); + this.promptRecommendedExtensionForFileExtension(fileExtension, installed); + }); + } - const importantTipsPromise = recommendationsToSuggest.length === 0 ? Promise.resolve(null) : this.extensionWorkbenchService.queryLocal().then(local => { - const localExtensions = local.map(e => e.identifier); - recommendationsToSuggest = recommendationsToSuggest.filter(id => localExtensions.every(local => !areSameExtensions(local, { id }))); - if (!recommendationsToSuggest.length) { - return; - } - const id = recommendationsToSuggest[0]; - const entry = caseInsensitiveGet(product.extensionImportantTips, id); - if (!entry) { - return; + private async promptRecommendedExtensionForFileType(model: ITextModel, installed: ILocalExtension[]): Promise { + let recommendationsToSuggest: string[] = []; + const now = Date.now(); + forEach(this._availableRecommendations, entry => { + let { key: pattern, value: ids } = entry; + if (match(pattern, model.uri.path)) { + for (let id of ids) { + if (caseInsensitiveGet(product.extensionImportantTips, id)) { + recommendationsToSuggest.push(id); + } + const filedBasedRecommendation = this._fileBasedRecommendations[id.toLowerCase()] || { recommendedTime: now, sources: [] }; + filedBasedRecommendation.recommendedTime = now; + if (!filedBasedRecommendation.sources.some(s => s instanceof URI && s.toString() === model.uri.toString())) { + filedBasedRecommendation.sources.push(model.uri); + } + this._fileBasedRecommendations[id.toLowerCase()] = filedBasedRecommendation; } - const name = entry['name']; - - // Indicates we have a suggested extension via the whitelist - hasSuggestion = true; + } + }); - let message = localize('reallyRecommended2', "The '{0}' extension is recommended for this file type.", name); - // Temporary fix for the only extension pack we recommend. See https://github.com/Microsoft/vscode/issues/35364 - if (id === 'vscjava.vscode-java-pack') { - message = localize('reallyRecommendedExtensionPack', "The '{0}' extension pack is recommended for this file type.", name); - } + this.storageService.store( + 'extensionsAssistant/recommendations', + JSON.stringify(Object.keys(this._fileBasedRecommendations).reduce((result, key) => { result[key] = this._fileBasedRecommendations[key].recommendedTime; return result; }, {})), + StorageScope.GLOBAL + ); - const setIgnoreRecommendationsConfig = (configVal: boolean) => { - this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER); - if (configVal) { - const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; - this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE); - } - }; + const config = this.configurationService.getValue(ConfigurationKey); + if (config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand) { + return false; + } - this.notificationService.prompt(Severity.Info, message, - [{ - label: localize('install', 'Install'), - run: () => { - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'install', extensionId: name }); - this.instantiationService.createInstance(InstallRecommendedExtensionAction, id).run(); - } - }, { - label: localize('showRecommendations', "Show Recommendations"), - run: () => { - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'show', extensionId: name }); + const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); + const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set()); + recommendationsToSuggest = recommendationsToSuggest.filter(id => { + if (importantRecommendationsIgnoreList.indexOf(id) !== -1) { + return false; + } + if (!this.isExtensionAllowedToBeRecommended(id)) { + return false; + } + if (installedExtensionsIds.has(id.toLowerCase())) { + return false; + } + return true; + }); - const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); - recommendationsAction.run(); - recommendationsAction.dispose(); - } - }, { - label: choiceNever, - isSecondary: true, - run: () => { - importantRecommendationsIgnoreList.push(id); - this.storageService.store( - 'extensionsAssistant/importantRecommendationsIgnore', - JSON.stringify(importantRecommendationsIgnoreList), - StorageScope.GLOBAL - ); - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: name }); - this.notificationService.prompt( - Severity.Info, - localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), - [{ - label: localize('ignoreAll', "Yes, Ignore All"), - run: () => setIgnoreRecommendationsConfig(true) - }, { - label: localize('no', "No"), - run: () => setIgnoreRecommendationsConfig(false) - }] - ); - } - }], - { - sticky: true, - onCancel: () => { - /* __GDPR__ - "extensionRecommendations:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: name }); - } - } - ); - }); + if (recommendationsToSuggest.length === 0) { + return false; + } - const mimeTypesPromise = this.extensionService.whenInstalledExtensionsRegistered() - .then(() => { - return guessMimeTypes(uri.fsPath); - }); + const id = recommendationsToSuggest[0]; + const entry = caseInsensitiveGet(product.extensionImportantTips, id); + if (!entry) { + return false; + } + const name = entry['name']; - Promise.all([importantTipsPromise, mimeTypesPromise]).then(result => { + let message = localize('reallyRecommended2', "The '{0}' extension is recommended for this file type.", name); + // Temporary fix for the only extension pack we recommend. See https://github.com/Microsoft/vscode/issues/35364 + if (id === 'vscjava.vscode-java-pack') { + message = localize('reallyRecommendedExtensionPack', "The '{0}' extension pack is recommended for this file type.", name); + } - const fileExtensionSuggestionIgnoreList = JSON.parse(this.storageService.get - ('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]')); - const mimeTypes = result[1]; + const setIgnoreRecommendationsConfig = (configVal: boolean) => { + this.configurationService.updateValue('extensions.ignoreRecommendations', configVal, ConfigurationTarget.USER); + if (configVal) { + const ignoreWorkspaceRecommendationsStorageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; + this.storageService.store(ignoreWorkspaceRecommendationsStorageKey, true, StorageScope.WORKSPACE); + } + }; - if (fileExtension) { - fileExtension = fileExtension.substr(1); // Strip the dot + this.notificationService.prompt(Severity.Info, message, + [{ + label: localize('install', 'Install'), + run: () => { + /* __GDPR__ + "extensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'install', extensionId: name }); + this.instantiationService.createInstance(InstallRecommendedExtensionAction, id).run(); } + }, { + label: localize('showRecommendations', "Show Recommendations"), + run: () => { + /* __GDPR__ + "extensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'show', extensionId: name }); - if (hasSuggestion || - !fileExtension || - mimeTypes.length !== 1 || - mimeTypes[0] !== MIME_UNKNOWN || - fileExtensionSuggestionIgnoreList.indexOf(fileExtension) > -1 - ) { - return; + const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations")); + recommendationsAction.run(); + recommendationsAction.dispose(); } - - const lookup = product.extensionKeywords || {}; - const keywords = lookup[fileExtension] || []; - this._galleryService.query({ text: `tag:"__ext_${fileExtension}" ${keywords.map(tag => `tag:"${tag}"`)}` }, CancellationToken.None).then(pager => { - if (!pager || !pager.firstPage || !pager.firstPage.length) { - return; - } - + }, { + label: choiceNever, + isSecondary: true, + run: () => { + importantRecommendationsIgnoreList.push(id); + this.storageService.store( + 'extensionsAssistant/importantRecommendationsIgnore', + JSON.stringify(importantRecommendationsIgnoreList), + StorageScope.GLOBAL + ); + /* __GDPR__ + "extensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: name }); this.notificationService.prompt( Severity.Info, - localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension), + localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?"), [{ - label: searchMarketplace, - run: () => { - /* __GDPR__ - "fileExtensionSuggestion:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'ok', fileExtension: fileExtension }); - this.viewletService.openViewlet('workbench.view.extensions', true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search(`ext:${fileExtension}`); - viewlet.focus(); - }); - } + label: localize('ignoreAll', "Yes, Ignore All"), + run: () => setIgnoreRecommendationsConfig(true) }, { - label: localize('dontShowAgainExtension', "Don't Show Again for '.{0}' files", fileExtension), - run: () => { - fileExtensionSuggestionIgnoreList.push(fileExtension); - this.storageService.store( - 'extensionsAssistant/fileExtensionsSuggestionIgnore', - JSON.stringify(fileExtensionSuggestionIgnoreList), - StorageScope.GLOBAL - ); - /* __GDPR__ - "fileExtensionSuggestion:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension: fileExtension }); - } - }], - { - sticky: true, - onCancel: () => { - /* __GDPR__ - "fileExtensionSuggestion:popup" : { - "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension: fileExtension }); - } + label: localize('no', "No"), + run: () => setIgnoreRecommendationsConfig(false) + }] + ); + } + }], + { + sticky: true, + onCancel: () => { + /* __GDPR__ + "extensionRecommendations:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } } + */ + this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: name }); + } + } + ); + + return true; + } + + private async promptRecommendedExtensionForFileExtension(fileExtension: string, installed: ILocalExtension[]): Promise { + const fileExtensionSuggestionIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]')); + if (fileExtensionSuggestionIgnoreList.indexOf(fileExtension) > -1) { + return; + } + + const text = `ext:${fileExtension}`; + const pager = await this.extensionWorkbenchService.queryGallery({ text, pageSize: 100 }, CancellationToken.None); + if (pager.firstPage.length === 0) { + return; + } + + const installedExtensionsIds = installed.reduce((result, i) => { result.add(i.identifier.id.toLowerCase()); return result; }, new Set()); + if (pager.firstPage.some(e => installedExtensionsIds.has(e.identifier.id.toLowerCase()))) { + return; + } + + this.notificationService.prompt( + Severity.Info, + localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension), + [{ + label: searchMarketplace, + run: () => { + /* __GDPR__ + "fileExtensionSuggestion:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'ok', fileExtension: fileExtension }); + this.viewletService.openViewlet('workbench.view.extensions', true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search(`ext:${fileExtension}`); + viewlet.focus(); + }); + } + }, { + label: localize('dontShowAgainExtension', "Don't Show Again for '.{0}' files", fileExtension), + run: () => { + fileExtensionSuggestionIgnoreList.push(fileExtension); + this.storageService.store( + 'extensionsAssistant/fileExtensionsSuggestionIgnore', + JSON.stringify(fileExtensionSuggestionIgnoreList), + StorageScope.GLOBAL ); - }); - }); - }); + /* __GDPR__ + "fileExtensionSuggestion:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension: fileExtension }); + } + }], + { + sticky: true, + onCancel: () => { + /* __GDPR__ + "fileExtensionSuggestion:popup" : { + "userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "fileExtension": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension: fileExtension }); + } + } + ); } //#endregion @@ -1001,8 +1012,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }), StorageScope.WORKSPACE); /* __GDPR__ "dynamicWorkspaceRecommendations" : { - "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "cache" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "cache" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ this.telemetryService.publicLog('dynamicWorkspaceRecommendations', { count: this._dynamicWorkspaceRecommendations.length, cache: 0 }); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index 19b1f6890b89..6270836f0bc3 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -50,6 +50,7 @@ import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -421,3 +422,34 @@ CommandsRegistry.registerCommand({ } } }); + +CommandsRegistry.registerCommand({ + id: 'workbench.extensions.uninstallExtension', + description: { + description: localize('workbench.extensions.uninstallExtension.description', "Uninstall the given extension"), + args: [ + { + name: localize('workbench.extensions.uninstallExtension.arg.name', "Id of the extension to uninstall"), + schema: { + 'type': 'string' + } + } + ] + }, + handler: async (accessor, id: string) => { + if (!id) { + throw new Error(localize('id required', "Extension id required.")); + } + const extensionManagementService = accessor.get(IExtensionManagementService); + try { + const installed = await extensionManagementService.getInstalled(ExtensionType.User); + const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); + if (!extensionToUninstall) { + return Promise.reject(new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", id))); + } + await extensionManagementService.uninstall(extensionToUninstall, true); + } catch (e) { + onUnexpectedError(e); + } + } +}); \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index 3da5e438f0e6..0472c07b0d1f 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -12,7 +12,7 @@ import { Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { ActionViewItem, Separator, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { dispose, Disposable } from 'vs/base/common/lifecycle'; // {{SQL CARBON EDIT}} import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; @@ -58,10 +58,11 @@ import { coalesce } from 'vs/base/common/arrays'; import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IFileIconTheme, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ILabelService } from 'vs/platform/label/common/label'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IProductService } from 'vs/platform/product/common/product'; // {{SQL CARBON EDIT}} import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -151,7 +152,6 @@ export class InstallAction extends ExtensionAction { private static readonly Class = 'extension-action prominent install'; private static readonly InstallingClass = 'extension-action install installing'; - private disposables: IDisposable[] = []; private _manifest: IExtensionManifest | null; set manifest(manifest: IExtensionManifest) { @@ -168,11 +168,12 @@ export class InstallAction extends ExtensionAction { @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, @ILabelService private readonly labelService: ILabelService ) { super(`extensions.install`, InstallAction.INSTALL_LABEL, InstallAction.Class, false); this.update(); - this.labelService.onDidChangeFormatters(() => this.updateLabel(), this, this.disposables); + this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); } update(): void { @@ -197,7 +198,7 @@ export class InstallAction extends ExtensionAction { this.tooltip = InstallAction.INSTALLING_LABEL; } else { if (this._manifest && this.workbenchEnvironmentService.configuration.remoteAuthority) { - if (isUIExtension(this._manifest, this.configurationService)) { + if (isUIExtension(this._manifest, this.productService, this.configurationService)) { this.label = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`; this.tooltip = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`; } else { @@ -281,11 +282,6 @@ export class InstallAction extends ExtensionAction { } return null; } - - dispose(): void { - this.disposables = dispose(this.disposables); - super.dispose(); - } } export class RemoteInstallAction extends ExtensionAction { @@ -297,7 +293,6 @@ export class RemoteInstallAction extends ExtensionAction { private static readonly InstallingClass = 'extension-action install installing'; updateWhenCounterExtensionChanges: boolean = true; - private disposables: IDisposable[] = []; private installing: boolean = false; constructor( @@ -306,9 +301,10 @@ export class RemoteInstallAction extends ExtensionAction { @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, ) { super(`extensions.remoteinstall`, RemoteInstallAction.INSTALL_LABEL, RemoteInstallAction.Class, false); - this.labelService.onDidChangeFormatters(() => this.updateLabel(), this, this.disposables); + this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); this.updateLabel(); this.update(); } @@ -341,7 +337,7 @@ export class RemoteInstallAction extends ExtensionAction { // Installed User Extension && this.extension && this.extension.local && this.extension.type === ExtensionType.User && this.extension.state === ExtensionState.Installed // Local Workspace Extension - && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || !isUIExtension(this.extension.local.manifest, this.configurationService)) + && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || !isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) // Extension does not exist in remote && !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer) && this.extensionsWorkbenchService.canInstall(this.extension) @@ -365,11 +361,6 @@ export class RemoteInstallAction extends ExtensionAction { } } } - - dispose(): void { - this.disposables = dispose(this.disposables); - super.dispose(); - } } export class LocalInstallAction extends ExtensionAction { @@ -381,7 +372,6 @@ export class LocalInstallAction extends ExtensionAction { private static readonly InstallingClass = 'extension-action install installing'; updateWhenCounterExtensionChanges: boolean = true; - private disposables: IDisposable[] = []; private installing: boolean = false; constructor( @@ -390,9 +380,10 @@ export class LocalInstallAction extends ExtensionAction { @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, ) { super(`extensions.localinstall`, LocalInstallAction.INSTALL_LABEL, LocalInstallAction.Class, false); - this.labelService.onDidChangeFormatters(() => this.updateLabel(), this, this.disposables); + this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this)); this.updateLabel(); this.update(); } @@ -420,7 +411,7 @@ export class LocalInstallAction extends ExtensionAction { // Installed User Extension && this.extension && this.extension.local && this.extension.type === ExtensionType.User && this.extension.state === ExtensionState.Installed // Remote UI or Language pack Extension - && this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || isUIExtension(this.extension.local.manifest, this.configurationService)) + && this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) // Extension does not exist in local && !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.localExtensionManagementServer) && this.extensionsWorkbenchService.canInstall(this.extension) @@ -444,11 +435,6 @@ export class LocalInstallAction extends ExtensionAction { } } } - - dispose(): void { - this.disposables = dispose(this.disposables); - super.dispose(); - } } export class UninstallAction extends ExtensionAction { @@ -483,6 +469,7 @@ export class UninstallAction extends ExtensionAction { this.label = UninstallAction.UninstallLabel; this.class = UninstallAction.UninstallClass; + this.tooltip = UninstallAction.UninstallLabel; if (state !== ExtensionState.Installed) { this.enabled = false; @@ -512,16 +499,14 @@ export class CombinedInstallAction extends ExtensionAction { private static readonly NoExtensionClass = 'extension-action prominent install no-extension'; private installAction: InstallAction; private uninstallAction: UninstallAction; - private disposables: IDisposable[] = []; constructor( @IInstantiationService instantiationService: IInstantiationService ) { super('extensions.combinedInstall', '', '', false); - this.installAction = instantiationService.createInstance(InstallAction); - this.uninstallAction = instantiationService.createInstance(UninstallAction); - this.disposables.push(this.installAction, this.uninstallAction); + this.installAction = this._register(instantiationService.createInstance(InstallAction)); + this.uninstallAction = this._register(instantiationService.createInstance(UninstallAction)); this.update(); } @@ -574,11 +559,6 @@ export class CombinedInstallAction extends ExtensionAction { return Promise.resolve(); } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } } export class UpdateAction extends ExtensionAction { @@ -688,8 +668,6 @@ export class ExtensionActionViewItem extends ActionViewItem { export abstract class ExtensionDropDownAction extends ExtensionAction { - protected disposables: IDisposable[] = []; - constructor( id: string, label: string, @@ -713,17 +691,10 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { } return Promise.resolve(); } - - dispose(): void { - dispose(this.disposables); - super.dispose(); - } } export class DropDownMenuActionViewItem extends ExtensionActionViewItem { - private disposables: IDisposable[] = []; - constructor(action: ExtensionDropDownAction, tabOnlyOnFocus: boolean, @IContextMenuService private readonly contextMenuService: IContextMenuService @@ -752,11 +723,6 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem { } return actions.length ? actions.slice(0, actions.length - 1) : actions; } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } } export class ManageExtensionAction extends ExtensionDropDownAction { @@ -1208,8 +1174,6 @@ export class UpdateAllAction extends Action { static readonly ID = 'workbench.extensions.action.updateAllExtensions'; static LABEL = localize('updateAll', "Update All Extensions"); - private disposables: IDisposable[] = []; - constructor( id = UpdateAllAction.ID, label = UpdateAllAction.LABEL, @@ -1220,7 +1184,7 @@ export class UpdateAllAction extends Action { ) { super(id, label, '', false); - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this._register(this.extensionsWorkbenchService.onChange(() => this.update())); this.update(); } @@ -1243,11 +1207,6 @@ export class UpdateAllAction extends Action { return promptDownloadManually(extension.gallery, localize('failedToUpdate', "Failed to update \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService); }); } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } } export class ReloadAction extends ExtensionAction { @@ -1256,7 +1215,6 @@ export class ReloadAction extends ExtensionAction { private static readonly DisabledClass = `${ReloadAction.EnabledClass} disabled`; updateWhenCounterExtensionChanges: boolean = true; - private disposables: IDisposable[] = []; private _runningExtensions: IExtensionDescription[] | null = null; constructor( @@ -1266,10 +1224,11 @@ export class ReloadAction extends ExtensionAction { @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, ) { super('extensions.reload', localize('reloadAction', "Reload"), ReloadAction.DisabledClass, false); - this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this, this.disposables); + this._register(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); this.updateRunningExtensions(); } @@ -1350,7 +1309,7 @@ export class ReloadAction extends ExtensionAction { return; } if (this.workbenchEnvironmentService.configuration.remoteAuthority) { - const uiExtension = isUIExtension(this.extension.local.manifest, this.configurationService); + const uiExtension = isUIExtension(this.extension.local.manifest, this.productService, this.configurationService); // Local Workspace Extension if (!uiExtension && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer) { const remoteExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer)[0]; @@ -1383,11 +1342,6 @@ export class ReloadAction extends ExtensionAction { run(): Promise { return Promise.resolve(this.windowService.reloadWindow()); } - - dispose(): void { - dispose(this.disposables); - super.dispose(); - } } export class SetColorThemeAction extends ExtensionAction { @@ -1399,7 +1353,6 @@ export class SetColorThemeAction extends ExtensionAction { private static readonly EnabledClass = 'extension-action theme'; private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`; - private disposables: IDisposable[] = []; constructor( private readonly colorThemes: IColorTheme[], @@ -1409,7 +1362,7 @@ export class SetColorThemeAction extends ExtensionAction { @IConfigurationService private readonly configurationService: IConfigurationService ) { super(`extensions.colorTheme`, localize('color theme', "Set Color Theme"), SetColorThemeAction.DisabledClass, false); - Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this, this.disposables); + this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this)); this.update(); } @@ -1455,11 +1408,6 @@ export class SetColorThemeAction extends ExtensionAction { const target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; return this.workbenchThemeService.setColorTheme(pickedTheme ? pickedTheme.id : currentTheme.id, target); } - - dispose() { - this.disposables = dispose(this.disposables); - super.dispose(); - } } export class SetFileIconThemeAction extends ExtensionAction { @@ -1467,7 +1415,6 @@ export class SetFileIconThemeAction extends ExtensionAction { private static readonly EnabledClass = 'extension-action theme'; private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`; - private disposables: IDisposable[] = []; static getFileIconThemes(fileIconThemes: IFileIconTheme[], extension: IExtension): IFileIconTheme[] { return fileIconThemes.filter(c => c.extensionData && ExtensionIdentifier.equals(c.extensionData.extensionId, extension.identifier.id)); @@ -1481,7 +1428,7 @@ export class SetFileIconThemeAction extends ExtensionAction { @IConfigurationService private readonly configurationService: IConfigurationService ) { super(`extensions.fileIconTheme`, localize('file icon theme', "Set File Icon Theme"), SetFileIconThemeAction.DisabledClass, false); - Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this, this.disposables); + this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this)); this.update(); } @@ -1527,11 +1474,6 @@ export class SetFileIconThemeAction extends ExtensionAction { const target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; return this.workbenchThemeService.setFileIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, target); } - - dispose() { - this.disposables = dispose(this.disposables); - super.dispose(); - } } export class OpenExtensionsViewletAction extends ShowViewletAction { @@ -1629,8 +1571,6 @@ export class ClearExtensionsInputAction extends Action { static readonly ID = 'workbench.extensions.action.clearExtensionsInput'; static LABEL = localize('clearExtensionsInput', "Clear Extensions Input"); - private disposables: IDisposable[] = []; - constructor( id: string, label: string, @@ -1640,7 +1580,7 @@ export class ClearExtensionsInputAction extends Action { ) { super(id, label, 'clear-extensions', true); this.onSearchChange(value); - onSearchChange(this.onSearchChange, this, this.disposables); + this._register(onSearchChange(this.onSearchChange, this)); } private onSearchChange(value: string): void { @@ -1655,10 +1595,6 @@ export class ClearExtensionsInputAction extends Action { viewlet.focus(); }); } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } export class ShowBuiltInExtensionsAction extends Action { @@ -1772,7 +1708,8 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { @IOpenerService private readonly openerService: IOpenerService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IProductService private readonly productService: IProductService, ) { super(id, label, 'extension-action'); this.recommendations = recommendations; @@ -1799,7 +1736,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { private async installExtension(extension: IExtension): Promise { try { if (extension.local && extension.gallery) { - if (isUIExtension(extension.local.manifest, this.configurationService)) { + if (isUIExtension(extension.local.manifest, this.productService, this.configurationService)) { await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(extension.gallery); return; } else if (this.extensionManagementServerService.remoteExtensionManagementServer) { @@ -1862,7 +1799,6 @@ export class IgnoreExtensionRecommendationAction extends Action { private static readonly Class = 'extension-action ignore'; - private disposables: IDisposable[] = []; extension: IExtension; constructor( @@ -1879,11 +1815,6 @@ export class IgnoreExtensionRecommendationAction extends Action { this.extensionsTipsService.toggleIgnoredRecommendation(this.extension.identifier.id, true); return Promise.resolve(); } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } } export class UndoIgnoreExtensionRecommendationAction extends Action { @@ -1892,7 +1823,6 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { private static readonly Class = 'extension-action undo-ignore'; - private disposables: IDisposable[] = []; extension: IExtension; constructor( @@ -1909,11 +1839,6 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { this.extensionsTipsService.toggleIgnoredRecommendation(this.extension.identifier.id, false); return Promise.resolve(); } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } } @@ -1989,7 +1914,6 @@ export class ShowAzureExtensionsAction extends Action { export class ChangeSortAction extends Action { private query: Query; - private disposables: IDisposable[] = []; constructor( id: string, @@ -2006,7 +1930,7 @@ export class ChangeSortAction extends Action { this.query = Query.parse(''); this.enabled = false; - onSearchChange(this.onSearchChange, this, this.disposables); + this._register(onSearchChange(this.onSearchChange, this)); } private onSearchChange(value: string): void { @@ -2311,7 +2235,6 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi static readonly ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions'; static LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)"); - private disposables: IDisposable[] = []; constructor( id: string, @@ -2324,7 +2247,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi @ITextModelService textModelResolverService: ITextModelService ) { super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService); - this.contextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables); + this._register(this.contextService.onDidChangeWorkbenchState(() => this.update(), this)); this.update(); } @@ -2341,11 +2264,6 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi } return Promise.resolve(); } - - dispose(): void { - this.disposables = dispose(this.disposables); - super.dispose(); - } } export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction { @@ -2353,7 +2271,6 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac static readonly ID = 'workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions'; static LABEL = localize('configureWorkspaceFolderRecommendedExtensions', "Configure Recommended Extensions (Workspace Folder)"); - private disposables: IDisposable[] = []; constructor( id: string, @@ -2367,7 +2284,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac @ICommandService private readonly commandService: ICommandService ) { super(id, label, contextService, fileService, textFileService, editorService, jsonEditingService, textModelResolverService); - this.contextService.onDidChangeWorkspaceFolders(() => this.update(), this, this.disposables); + this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.update(), this)); this.update(); } @@ -2386,11 +2303,6 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac return null; }); } - - dispose(): void { - this.disposables = dispose(this.disposables); - super.dispose(); - } } export class AddToWorkspaceFolderRecommendationsAction extends AbstractConfigureRecommendedExtensionsAction { @@ -2679,7 +2591,6 @@ export class DisabledLabelAction extends ExtensionAction { private static readonly Class = 'disable-status'; updateWhenCounterExtensionChanges: boolean = true; - private disposables: IDisposable[] = []; private _runningExtensions: IExtensionDescription[] | null = null; constructor( @@ -2688,8 +2599,8 @@ export class DisabledLabelAction extends ExtensionAction { @IExtensionService private readonly extensionService: IExtensionService, ) { super('extensions.disabledLabel', warningAction.tooltip, `${DisabledLabelAction.Class} hide`, false); - warningAction.onDidChange(() => this.update(), this, this.disposables); - this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this, this.disposables); + this._register(warningAction.onDidChange(() => this.update(), this)); + this._register(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); this.updateRunningExtensions(); } @@ -2722,11 +2633,6 @@ export class DisabledLabelAction extends ExtensionAction { run(): Promise { return Promise.resolve(null); } - - dispose(): void { - dispose(this.disposables); - super.dispose(); - } } export class SystemDisabledWarningAction extends ExtensionAction { @@ -2736,20 +2642,20 @@ export class SystemDisabledWarningAction extends ExtensionAction { private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} info`; updateWhenCounterExtensionChanges: boolean = true; - private disposables: IDisposable[] = []; private _runningExtensions: IExtensionDescription[] | null = null; constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, @ILabelService private readonly labelService: ILabelService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionService private readonly extensionService: IExtensionService, ) { super('extensions.install', '', `${SystemDisabledWarningAction.CLASS} hide`, false); - this.labelService.onDidChangeFormatters(() => this.update(), this, this.disposables); - this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this, this.disposables); + this._register(this.labelService.onDidChangeFormatters(() => this.update(), this)); + this._register(this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this)); this.updateRunningExtensions(); this.update(); } @@ -2785,7 +2691,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null; const localExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0]; const localExtensionServer = localExtension ? localExtension.server : null; - if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.configurationService)) { + if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) { if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; this.tooltip = localize('disabled locally', "Extension is enabled on '{0}' and disabled locally.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer)); @@ -2797,7 +2703,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { return; } } - if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && isUIExtension(this.extension.local.manifest, this.configurationService)) { + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) { if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; this.tooltip = localize('disabled remotely', "Extension is enabled locally and disabled on '{0}'.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer)); @@ -2820,11 +2726,6 @@ export class SystemDisabledWarningAction extends ExtensionAction { run(): Promise { return Promise.resolve(null); } - - dispose(): void { - dispose(this.disposables); - super.dispose(); - } } export class DisableAllAction extends Action { @@ -2832,7 +2733,6 @@ export class DisableAllAction extends Action { static readonly ID = 'workbench.extensions.action.disableAll'; static LABEL = localize('disableAll', "Disable All Installed Extensions"); - private disposables: IDisposable[] = []; constructor( id: string = DisableAllAction.ID, label: string = DisableAllAction.LABEL, @@ -2841,7 +2741,7 @@ export class DisableAllAction extends Action { ) { super(id, label); this.update(); - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this._register(this.extensionsWorkbenchService.onChange(() => this.update())); } private update(): void { @@ -2851,11 +2751,6 @@ export class DisableAllAction extends Action { run(): Promise { return this.extensionsWorkbenchService.setEnablement(this.extensionsWorkbenchService.local.filter(e => e.type === ExtensionType.User), EnablementState.Disabled); } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } } export class DisableAllWorkpsaceAction extends Action { @@ -2863,7 +2758,6 @@ export class DisableAllWorkpsaceAction extends Action { static readonly ID = 'workbench.extensions.action.disableAllWorkspace'; static LABEL = localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"); - private disposables: IDisposable[] = []; constructor( id: string = DisableAllWorkpsaceAction.ID, label: string = DisableAllWorkpsaceAction.LABEL, @@ -2872,8 +2766,8 @@ export class DisableAllWorkpsaceAction extends Action { ) { super(id, label); this.update(); - this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables); - this.extensionsWorkbenchService.onChange(() => this.update(), this, this.disposables); + this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this)); + this._register(this.extensionsWorkbenchService.onChange(() => this.update(), this)); } private update(): void { @@ -2883,11 +2777,6 @@ export class DisableAllWorkpsaceAction extends Action { run(): Promise { return this.extensionsWorkbenchService.setEnablement(this.extensionsWorkbenchService.local.filter(e => e.type === ExtensionType.User), EnablementState.WorkspaceDisabled); } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } } export class EnableAllAction extends Action { @@ -2895,7 +2784,6 @@ export class EnableAllAction extends Action { static readonly ID = 'workbench.extensions.action.enableAll'; static LABEL = localize('enableAll', "Enable All Extensions"); - private disposables: IDisposable[] = []; constructor( id: string = EnableAllAction.ID, label: string = EnableAllAction.LABEL, @@ -2904,7 +2792,7 @@ export class EnableAllAction extends Action { ) { super(id, label); this.update(); - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this._register(this.extensionsWorkbenchService.onChange(() => this.update())); } private update(): void { @@ -2914,11 +2802,6 @@ export class EnableAllAction extends Action { run(): Promise { return this.extensionsWorkbenchService.setEnablement(this.extensionsWorkbenchService.local, EnablementState.Enabled); } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } } export class EnableAllWorkpsaceAction extends Action { @@ -2926,7 +2809,6 @@ export class EnableAllWorkpsaceAction extends Action { static readonly ID = 'workbench.extensions.action.enableAllWorkspace'; static LABEL = localize('enableAllWorkspace', "Enable All Extensions for this Workspace"); - private disposables: IDisposable[] = []; constructor( id: string = EnableAllWorkpsaceAction.ID, label: string = EnableAllWorkpsaceAction.LABEL, @@ -2936,8 +2818,8 @@ export class EnableAllWorkpsaceAction extends Action { ) { super(id, label); this.update(); - this.extensionsWorkbenchService.onChange(() => this.update(), this, this.disposables); - this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables); + this._register(this.extensionsWorkbenchService.onChange(() => this.update(), this)); + this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this)); } private update(): void { @@ -2947,11 +2829,6 @@ export class EnableAllWorkpsaceAction extends Action { run(): Promise { return this.extensionsWorkbenchService.setEnablement(this.extensionsWorkbenchService.local, EnablementState.WorkspaceEnabled); } - - dispose(): void { - super.dispose(); - this.disposables = dispose(this.disposables); - } } export class OpenExtensionsFolderAction extends Action { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts index d7cbb6bf448c..7917a1b699d7 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts @@ -5,7 +5,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { localize } from 'vs/nls'; import { IDisposable } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; @@ -17,7 +17,7 @@ export class ExtensionActivationProgress implements IWorkbenchContribution { constructor( @IExtensionService extensionService: IExtensionService, - @IProgressService2 progressService: IProgressService2, + @IProgressService progressService: IProgressService, @ILogService logService: ILogService, ) { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts index facf24f9c799..f41ad19ebb8f 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { append, $, addClass, removeClass, toggleClass } from 'vs/base/browser/dom'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { Action } from 'vs/base/common/actions'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -118,11 +118,11 @@ export class Renderer implements IPagedRenderer { const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets, disabledLabelAction]); actionbar.push(actions, actionOptions); - const disposables = [...actions, ...widgets, actionbar, extensionContainers]; + const disposables = combinedDisposable(...actions, ...widgets, actionbar, extensionContainers); return { // {{SQL CARBON EDIT}} - root, element, icon, name, /*installCount, ratings,*/ author, description, disposables, actionbar, + root, element, icon, name, /*installCount, ratings,*/ author, description, disposables: [disposables], actionbar, extensionDisposables: [], set extension(extension: IExtension) { extensionContainers.extension = extension; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts index 3eb62666d22b..f559cf94c101 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts @@ -28,7 +28,7 @@ import { IExtensionManagementService, IExtensionManagementServerService, IExtens import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView } from './extensionsViews'; import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; -import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +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'; import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; @@ -58,6 +58,7 @@ import { RemoteAuthorityContext } from 'vs/workbench/browser/contextkeys'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { ILabelService } from 'vs/platform/label/common/label'; +import { MementoObject } from 'vs/workbench/common/memento'; interface SearchInputEvent extends Event { target: HTMLInputElement; @@ -343,12 +344,12 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private primaryActions: IAction[]; private secondaryActions: IAction[] | null; private disposables: IDisposable[] = []; - private searchViewletState: object; + private readonly searchViewletState: MementoObject; constructor( @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, - @IProgressService2 private readonly progressService: IProgressService2, + @IProgressService private readonly progressService: IProgressService, @IInstantiationService instantiationService: IInstantiationService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts index a3b4a9113a59..9144182701c5 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { dispose, Disposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { Event, Emitter } from 'vs/base/common/event'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { isPromiseCanceledError, getErrorMessage } from 'vs/base/common/errors'; import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from 'vs/base/common/paging'; import { SortBy, SortOrder, IQueryOptions, IExtensionTipsService, IExtensionRecommendation, IExtensionManagementServer, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -24,14 +24,13 @@ 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 { IModeService } from 'vs/editor/common/services/modeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { distinct, coalesce } from 'vs/base/common/arrays'; @@ -43,9 +42,10 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IAction } 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 product from 'vs/platform/product/node/product'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { IProductService } from 'vs/platform/product/common/product'; +import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -68,9 +68,13 @@ export interface ExtensionsListViewOptions extends IViewletViewOptions { server?: IExtensionManagementServer; } +class ExtensionListViewWarning extends Error { } + export class ExtensionsListView extends ViewletPanel { private readonly server: IExtensionManagementServer | undefined; + private messageContainer: HTMLElement; + private messageSeverityIcon: HTMLElement; private messageBox: HTMLElement; private extensionsList: HTMLElement; private badge: CountBadge; @@ -89,13 +93,13 @@ export class ExtensionsListView extends ViewletPanel { @IExtensionsWorkbenchService protected extensionsWorkbenchService: IExtensionsWorkbenchService, @IEditorService private readonly editorService: IEditorService, @IExtensionTipsService protected tipsService: IExtensionTipsService, - @IModeService private readonly modeService: IModeService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IExperimentService private readonly experimentService: IExperimentService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, + @IProductService protected readonly productService: IProductService, ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService); this.server = options.server; @@ -110,12 +114,14 @@ export class ExtensionsListView extends ViewletPanel { this.badgeContainer = append(container, $('.count-badge-wrapper')); this.badge = new CountBadge(this.badgeContainer); - this.disposables.push(attachBadgeStyler(this.badge, this.themeService)); + this._register(attachBadgeStyler(this.badge, this.themeService)); } renderBody(container: HTMLElement): void { this.extensionsList = append(container, $('.extensions-list')); - this.messageBox = append(container, $('.message')); + this.messageContainer = append(container, $('.message-container')); + this.messageSeverityIcon = append(this.messageContainer, $('')); + this.messageBox = append(this.messageContainer, $('.message')); const delegate = new Delegate(); const extensionsViewState = new ExtensionsViewState(); const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState); @@ -125,20 +131,20 @@ export class ExtensionsListView extends ViewletPanel { setRowLineHeight: false, horizontalScrolling: false }) as WorkbenchPagedList; - this.list.onContextMenu(e => this.onContextMenu(e), this, this.disposables); - this.list.onFocusChange(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this, this.disposables); - this.disposables.push(this.list); - this.disposables.push(extensionsViewState); + this._register(this.list.onContextMenu(e => this.onContextMenu(e), this)); + this._register(this.list.onFocusChange(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this)); + this._register(this.list); + this._register(extensionsViewState); - Event.chain(this.list.onOpen) + this._register(Event.chain(this.list.onOpen) .map(e => e.elements[0]) .filter(e => !!e) - .on(this.openExtension, this, this.disposables); + .on(this.openExtension, this)); - Event.chain(this.list.onPin) + this._register(Event.chain(this.list.onPin) .map(e => e.elements[0]) .filter(e => !!e) - .on(this.pin, this, this.disposables); + .on(this.pin, this)); } protected layoutBody(height: number, width: number): void { @@ -176,12 +182,11 @@ export class ExtensionsListView extends ViewletPanel { }; - const errorCallback = (e: Error) => { + const errorCallback = (e: any) => { const model = new PagedModel([]); if (!isPromiseCanceledError(e)) { this.queryRequest = null; - console.warn('Error querying extensions gallery', e); - this.setModel(model, true); + this.setModel(model, e); } return this.list ? this.list.model : model; }; @@ -236,7 +241,11 @@ export class ExtensionsListView extends ViewletPanel { if (ExtensionsListView.isLocalExtensionsQuery(query.value) || /@builtin/.test(query.value)) { return this.queryLocal(query, options); } - return this.queryGallery(query, options, token); + return this.queryGallery(query, options, token) + .then(null, e => { + console.warn('Error querying extensions gallery', getErrorMessage(e)); + return Promise.reject(new ExtensionListViewWarning(localize('galleryError', "We cannot connect to the Extensions Marketplace at this time, please try again later."))); + }); } private async queryByIds(ids: string[], options: IQueryOptions, token: CancellationToken): Promise> { @@ -431,29 +440,11 @@ export class ExtensionsListView extends ViewletPanel { return this.getCuratedModel(query, options, token); } - let text = query.value; - const extensionRegex = /\bext:([^\s]+)\b/g; - - if (extensionRegex.test(query.value)) { - text = query.value.replace(extensionRegex, (m, ext) => { - - // Get curated keywords - const lookup = product.extensionKeywords || {}; - const keywords = lookup[ext] || []; - - // Get mode name - const modeId = this.modeService.getModeIdByFilepathOrFirstLine(`.${ext}`); - const languageName = modeId && this.modeService.getLanguageName(modeId); - const languageTag = languageName ? ` tag:"${languageName}"` : ''; - - // Construct a rich query - return `tag:"__ext_${ext}" tag:"__ext_.${ext}" ${keywords.map(tag => `tag:"${tag}"`).join(' ')}${languageTag} tag:"${ext}"`; - }); + const text = query.value; - if (text !== query.value) { - options = assign(options, { text: text.substr(0, 350), source: 'file-extension-tags' }); - return this.extensionsWorkbenchService.queryGallery(options, token).then(pager => this.getPagedModel(pager)); - } + if (/\bext:([^\s]+)\b/g.test(text)) { + options = assign(options, { text, source: 'file-extension-tags' }); + return this.extensionsWorkbenchService.queryGallery(options, token).then(pager => this.getPagedModel(pager)); } let preferredResults: string[] = []; @@ -733,28 +724,36 @@ export class ExtensionsListView extends ViewletPanel { }); } - private setModel(model: IPagedModel, isGalleryError?: boolean) { + private setModel(model: IPagedModel, error?: any) { if (this.list) { this.list.model = new DelayedPagedModel(model); this.list.scrollTop = 0; const count = this.count(); toggleClass(this.extensionsList, 'hidden', count === 0); - toggleClass(this.messageBox, 'hidden', count > 0); + toggleClass(this.messageContainer, 'hidden', count > 0); this.badge.setCount(count); if (count === 0 && this.isBodyVisible()) { - this.messageBox.textContent = isGalleryError ? localize('galleryError', "We cannot connect to the Extensions Marketplace at this time, please try again later.") : localize('no extensions found', "No extensions found."); - if (isGalleryError) { - alert(this.messageBox.textContent); + if (error) { + if (error instanceof ExtensionListViewWarning) { + this.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning); + this.messageBox.textContent = getErrorMessage(error); + } else { + this.messageSeverityIcon.className = SeverityIcon.className(Severity.Error); + this.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error)); + } + } else { + this.messageSeverityIcon.className = ''; + this.messageBox.textContent = localize('no extensions found', "No extensions found."); } - } else { - this.messageBox.textContent = ''; + alert(this.messageBox.textContent); } } } private openExtension(extension: IExtension): void { + extension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, extension.identifier))[0] || extension; this.extensionsWorkbenchService.open(extension).then(undefined, err => this.onError(err)); } @@ -806,7 +805,6 @@ export class ExtensionsListView extends ViewletPanel { this.queryRequest.request.cancel(); this.queryRequest = null; } - this.disposables = dispose(this.disposables); this.list = null; } @@ -886,18 +884,18 @@ export class ServerExtensionsView extends ExtensionsListView { @IExtensionService extensionService: IExtensionService, @IEditorService editorService: IEditorService, @IExtensionTipsService tipsService: IExtensionTipsService, - @IModeService modeService: IModeService, @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IExperimentService experimentService: IExperimentService, @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, + @IProductService productService: IProductService, ) { options.server = server; - super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, modeService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService); - this.disposables.push(onDidChangeTitle(title => this.updateTitle(title))); + super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService); + this._register(onDidChangeTitle(title => this.updateTitle(title))); } async show(query: string): Promise> { @@ -950,7 +948,7 @@ export class DefaultRecommendedExtensionsView extends ExtensionsListView { renderBody(container: HTMLElement): void { super.renderBody(container); - this.disposables.push(this.tipsService.onRecommendationChange(() => { + this._register(this.tipsService.onRecommendationChange(() => { this.show(''); })); } @@ -975,7 +973,7 @@ export class RecommendedExtensionsView extends ExtensionsListView { renderBody(container: HTMLElement): void { super.renderBody(container); - this.disposables.push(this.tipsService.onRecommendationChange(() => { + this._register(this.tipsService.onRecommendationChange(() => { this.show(''); })); } @@ -993,9 +991,9 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { renderBody(container: HTMLElement): void { super.renderBody(container); - this.disposables.push(this.tipsService.onRecommendationChange(() => this.update())); - this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.setRecommendationsToInstall())); - this.disposables.push(this.contextService.onDidChangeWorkbenchState(() => this.update())); + this._register(this.tipsService.onRecommendationChange(() => this.update())); + this._register(this.extensionsWorkbenchService.onChange(() => this.setRecommendationsToInstall())); + this._register(this.contextService.onDidChangeWorkbenchState(() => this.update())); } renderHeader(container: HTMLElement): void { @@ -1004,21 +1002,19 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { const listActionBar = $('.list-actionbar-container'); container.insertBefore(listActionBar, this.badgeContainer); - const actionbar = new ActionBar(listActionBar, { + const actionbar = this._register(new ActionBar(listActionBar, { animated: false - }); + })); actionbar.onDidRun(({ error }) => error && this.notificationService.error(error)); - this.installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, []); - const configureWorkspaceFolderAction = this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL); + this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, [])); + const configureWorkspaceFolderAction = this._register(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL)); this.installAllAction.class = 'octicon octicon-cloud-download'; configureWorkspaceFolderAction.class = 'octicon octicon-pencil'; actionbar.push([this.installAllAction], { icon: true, label: false }); actionbar.push([configureWorkspaceFolderAction], { icon: true, label: false }); - - this.disposables.push(...[this.installAllAction, configureWorkspaceFolderAction, actionbar]); } async show(query: string): Promise> { @@ -1045,7 +1041,7 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { if (!extension || !extension.local || extension.state !== ExtensionState.Installed) { return true; } - return isUIExtension(extension.local.manifest, this.configurationService) ? extension.server !== this.extensionManagementServerService.localExtensionManagementServer : extension.server !== this.extensionManagementServerService.remoteExtensionManagementServer; + return isUIExtension(extension.local.manifest, this.productService, this.configurationService) ? extension.server !== this.extensionManagementServerService.localExtensionManagementServer : extension.server !== this.extensionManagementServerService.remoteExtensionManagementServer; })); } } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css index a6fc8034194c..a9bed9f79d8e 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css @@ -81,22 +81,6 @@ width: 10px; } -.monaco-action-bar .action-item .action-label.system-disable.warning.icon { - background: url('status-warning.svg') center center no-repeat; -} - -.vs-dark .monaco-action-bar .action-item .action-label.system-disable.warning.icon { - background: url('status-warning-inverse.svg') center center no-repeat; -} - -.monaco-action-bar .action-item .action-label.system-disable.info.icon { - background: url('status-info.svg') center center no-repeat; -} - -.vs-dark .monaco-action-bar .action-item .action-label.system-disable.info.icon { - background: url('status-info-inverse.svg') center center no-repeat; -} - .extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.extension-status-label, .extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.disable-status, .extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.malicious-status { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsViewlet.css index 38d72359d35b..31ef0ba3e0ad 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsViewlet.css @@ -38,7 +38,7 @@ } .extensions-viewlet > .extensions .extensions-list.hidden, -.extensions-viewlet > .extensions .message.hidden { +.extensions-viewlet > .extensions .message-container.hidden { display: none; visibility: hidden; } @@ -51,9 +51,14 @@ flex: 1; } -.extensions-viewlet > .extensions .message { +.extensions-viewlet > .extensions .message-container { padding: 5px 9px 5px 16px; cursor: default; + display: flex; +} + +.extensions-viewlet > .extensions .message-container .message { + padding-left: 5px; } .extensions-viewlet > .extensions .monaco-list-row > .bookmark { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css index 84a2d36bc169..18a6e43b20d0 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css @@ -36,20 +36,3 @@ .runtime-extensions-editor .monaco-action-bar .actions-container { justify-content: left; } - -.monaco-workbench .part.statusbar .profileExtHost-statusbar-item .icon { - background: url('profile-stop.svg') no-repeat; - display: inline-block; - padding-right: 2px; - padding-bottom: 2px; - width: 16px; - height: 16px; - vertical-align: middle; - animation:fade 1000ms infinite; -} - -@keyframes fade { - from { opacity: 1.0; } - 50% { opacity: 0.5; } - to { opacity: 1.0; } -} diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/status-info-inverse.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/status-info-inverse.svg deleted file mode 100644 index d38c363e0e4c..000000000000 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/status-info-inverse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/status-info.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/status-info.svg deleted file mode 100644 index 6e2e22f67bc9..000000000000 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/status-info.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/status-warning-inverse.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/status-warning-inverse.svg deleted file mode 100644 index df44e61b3265..000000000000 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/status-warning-inverse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/status-warning.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/status-warning.svg deleted file mode 100644 index f4e2a84b0af4..000000000000 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/status-warning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts index 071714d31f3a..e9651cdc656a 100644 --- a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { index, distinct } from 'vs/base/common/arrays'; import { ThrottledDelayer } from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; // {{SQL CARBON EDIT}} @@ -17,7 +17,7 @@ import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, INSTALL_ERROR_INCOMPATIBLE } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -29,13 +29,14 @@ import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import product from 'vs/platform/product/node/product'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { INotificationService } from 'vs/platform/notification/common/notification'; import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IFileService } from 'vs/platform/files/common/files'; -import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion, IExtension as IPlatformExtension, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { IModeService } from 'vs/editor/common/services/modeService'; // {{SQL CARBON EDIT}} import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator'; @@ -497,7 +498,6 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension private readonly remoteExtensions: Extensions | null; private syncDelayer: ThrottledDelayer; private autoUpdateDelayer: ThrottledDelayer; - private disposables: IDisposable[] = []; private readonly _onChange: Emitter = new Emitter(); get onChange(): Event { return this._onChange.event; } @@ -516,10 +516,11 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @IWindowService private readonly windowService: IWindowService, @ILogService private readonly logService: ILogService, - @IProgressService2 private readonly progressService: IProgressService2, + @IProgressService private readonly progressService: IProgressService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IStorageService private readonly storageService: IStorageService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @IModeService private readonly modeService: IModeService ) { super(); this.localExtensions = this._register(instantiationService.createInstance(Extensions, extensionManagementServerService.localExtensionManagementServer)); @@ -536,7 +537,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension urlService.registerHandler(this); - this.configurationService.onDidChangeConfiguration(e => { + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AutoUpdateConfigurationKey)) { // {{SQL CARBON EDIT}} // if (this.isAutoUpdateEnabled()) { @@ -548,7 +549,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension this.checkForUpdates(); } } - }, this, this.disposables); + }, this)); this.queryLocal().then(() => { this.resetIgnoreAutoUpdateExtensions(); @@ -602,6 +603,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension queryGallery(arg1: any, arg2?: any): Promise> { const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1; const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2; + options.text = options.text ? this.resolveQueryText(options.text) : options.text; return this.extensionService.getExtensionsReport() .then(report => { const maliciousSet = getMaliciousExtensionsSet(report); @@ -618,6 +620,27 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension }); } + private resolveQueryText(text: string): string { + const extensionRegex = /\bext:([^\s]+)\b/g; + if (extensionRegex.test(text)) { + text = text.replace(extensionRegex, (m, ext) => { + + // Get curated keywords + const lookup = product.extensionKeywords || {}; + const keywords = lookup[ext] || []; + + // Get mode name + const modeId = this.modeService.getModeIdByFilepathOrFirstLine(`.${ext}`); + const languageName = modeId && this.modeService.getLanguageName(modeId); + const languageTag = languageName ? ` tag:"${languageName}"` : ''; + + // Construct a rich query + return `tag:"__ext_${ext}" tag:"__ext_.${ext}" ${keywords.map(tag => `tag:"${tag}"`).join(' ')}${languageTag} tag:"${ext}"`; + }); + } + return text.substr(0, 350); + } + open(extension: IExtension, sideByside: boolean = false): Promise { return Promise.resolve(this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), undefined, sideByside ? SIDE_GROUP : ACTIVE_GROUP)); } @@ -1116,7 +1139,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } dispose(): void { + super.dispose(); this.syncDelayer.cancel(); - this.disposables = dispose(this.disposables); } } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 5bf3b9489bc8..8a39d844bf33 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -113,7 +113,7 @@ suite('ExtensionsListView Tests', () => { instantiationService.stubPromise(IExtensionTipsService, 'getOtherRecommendations', [ { extensionId: otherRecommendationA.identifier.id } ]); - const reasons = {}; + const reasons: { [key: string]: any } = {}; reasons[workspaceRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace }; reasons[workspaceRecommendationB.identifier.id] = { reasonId: ExtensionRecommendationReason.Workspace }; reasons[fileBasedRecommendationA.identifier.id] = { reasonId: ExtensionRecommendationReason.File }; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index bae14fc8d2a1..2119a8da9122 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -30,8 +30,8 @@ import { TestContextService, TestWindowService, TestSharedProcessService } from import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IProgressService2 } from 'vs/platform/progress/common/progress'; -import { ProgressService2 } from 'vs/workbench/services/progress/browser/progressService2'; +import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { URLService } from 'vs/platform/url/common/urlService'; import { URI } from 'vs/base/common/uri'; @@ -61,7 +61,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILogService, NullLogService); instantiationService.stub(IWindowService, TestWindowService); - instantiationService.stub(IProgressService2, ProgressService2); + instantiationService.stub(IProgressService, ProgressService); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(IURLService, URLService); diff --git a/src/vs/workbench/contrib/feedback/electron-browser/feedback.contribution.ts b/src/vs/workbench/contrib/feedback/electron-browser/feedback.contribution.ts index a3248ca69bb9..fb3b9a36d539 100644 --- a/src/vs/workbench/contrib/feedback/electron-browser/feedback.contribution.ts +++ b/src/vs/workbench/contrib/feedback/electron-browser/feedback.contribution.ts @@ -8,28 +8,12 @@ import { IStatusbarRegistry, Extensions, StatusbarItemDescriptor } from 'vs/work import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { FeedbackStatusbarItem } from 'vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem'; import { localize } from 'vs/nls'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; // Register Statusbar item Registry.as(Extensions.Statusbar).registerStatusbarItem(new StatusbarItemDescriptor( FeedbackStatusbarItem, + 'status.feedback', + localize('status.feedback', "Tweet Feedback"), StatusbarAlignment.RIGHT, -100 /* towards the end of the right hand side */ )); - -// Configuration: Workbench -const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); - -configurationRegistry.registerConfiguration({ - 'id': 'workbench', - 'order': 7, - 'title': localize('workbenchConfigurationTitle', "Workbench"), - 'type': 'object', - 'properties': { - 'workbench.statusBar.feedback.visible': { - 'type': 'boolean', - 'default': true, - 'description': localize('feedbackVisibility', "Controls the visibility of the Twitter feedback (smiley) in the status bar at the bottom of the workbench.") - } - } -}); \ No newline at end of file diff --git a/src/vs/workbench/contrib/feedback/electron-browser/feedback.ts b/src/vs/workbench/contrib/feedback/electron-browser/feedback.ts index e17d5c32e1bd..5f157f683759 100644 --- a/src/vs/workbench/contrib/feedback/electron-browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/electron-browser/feedback.ts @@ -18,9 +18,8 @@ import { editorWidgetBackground, widgetShadow, inputBorder, inputForeground, inp import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { Button } from 'vs/base/browser/ui/button/button'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; - -export const FEEDBACK_VISIBLE_CONFIG = 'workbench.statusBar.feedback.visible'; +import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; +import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; export interface IFeedback { feedback: string; @@ -66,12 +65,13 @@ export class FeedbackDropdown extends Dropdown { @ITelemetryService private readonly telemetryService: ITelemetryService, @IIntegrityService private readonly integrityService: IIntegrityService, @IThemeService private readonly themeService: IThemeService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IStatusbarService private readonly statusbarService: IStatusbarService ) { super(container, { contextViewProvider: options.contextViewProvider, labelRenderer: (container: HTMLElement): IDisposable => { - dom.addClasses(container, 'send-feedback', 'mask-icon'); + const label = new OcticonLabel(container); + label.text = '$(smiley)'; return Disposable.None; } @@ -402,7 +402,7 @@ export class FeedbackDropdown extends Dropdown { } if (this.hideButton && !this.hideButton.checked) { - this.configurationService.updateValue(FEEDBACK_VISIBLE_CONFIG, false); + this.statusbarService.updateEntryVisibility('status.feedback', false); } super.hide(); diff --git a/src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts index 5d1b9da8e9cc..6868307d810a 100644 --- a/src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts @@ -5,17 +5,14 @@ import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { FeedbackDropdown, IFeedback, IFeedbackDelegate, FEEDBACK_VISIBLE_CONFIG } from 'vs/workbench/contrib/feedback/electron-browser/feedback'; -import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { FeedbackDropdown, IFeedback, IFeedbackDelegate } from 'vs/workbench/contrib/feedback/electron-browser/feedback'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import product from 'vs/platform/product/node/product'; -import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND } from 'vs/workbench/common/theme'; +import { Themable, STATUS_BAR_ITEM_HOVER_BACKGROUND } from 'vs/workbench/common/theme'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { clearNode, EventHelper, addClass, removeClass, addDisposableListener } from 'vs/base/browser/dom'; -import { localize } from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; class TwitterFeedbackService implements IFeedbackDelegate { @@ -52,75 +49,40 @@ class TwitterFeedbackService implements IFeedbackDelegate { export class FeedbackStatusbarItem extends Themable implements IStatusbarItem { private dropdown: FeedbackDropdown | undefined; - private enabled: boolean; private container: HTMLElement; - private hideAction: HideAction; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextViewService private readonly contextViewService: IContextViewService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeService themeService: IThemeService ) { super(themeService); - this.enabled = this.configurationService.getValue(FEEDBACK_VISIBLE_CONFIG); - - this.hideAction = this._register(this.instantiationService.createInstance(HideAction)); - this.registerListeners(); } private registerListeners(): void { this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles())); - this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); - } - - private onConfigurationUpdated(event: IConfigurationChangeEvent): void { - if (event.affectsConfiguration(FEEDBACK_VISIBLE_CONFIG)) { - this.enabled = this.configurationService.getValue(FEEDBACK_VISIBLE_CONFIG); - this.update(); - } - } - - protected updateStyles(): void { - super.updateStyles(); - - if (this.dropdown && this.dropdown.label) { - this.dropdown.label.style.backgroundColor = (this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND)); - } } render(element: HTMLElement): IDisposable { this.container = element; // Prevent showing dropdown on anything but left click - this.toDispose.push(addDisposableListener(this.container, 'mousedown', (e: MouseEvent) => { + this._register(addDisposableListener(this.container, 'mousedown', (e: MouseEvent) => { if (e.button !== 0) { EventHelper.stop(e, true); } }, true)); - // Offer context menu to hide status bar entry - this.toDispose.push(addDisposableListener(this.container, 'contextmenu', e => { - EventHelper.stop(e, true); - - this.contextMenuService.showContextMenu({ - getAnchor: () => this.container, - getActions: () => [this.hideAction] - }); - })); - return this.update(); } private update(): IDisposable { - const enabled = product.sendASmile && this.enabled; // Create - if (enabled) { + if (product.sendASmile) { if (!this.dropdown) { this.dropdown = this._register(this.instantiationService.createInstance(FeedbackDropdown, this.container, { contextViewProvider: this.contextViewService, @@ -151,22 +113,9 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem { } } -class HideAction extends Action { - - constructor( - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - super('feedback.hide', localize('hide', "Hide")); - } - - run(extensionId: string): Promise { - return this.configurationService.updateValue(FEEDBACK_VISIBLE_CONFIG, false); - } -} - registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND); if (statusBarItemHoverBackground) { - collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item .monaco-dropdown.send-feedback:hover { background-color: ${statusBarItemHoverBackground}; }`); + collector.addRule(`.monaco-workbench .part.statusbar > .items-container > .statusbar-item .monaco-dropdown.send-feedback:hover { background-color: ${statusBarItemHoverBackground}; }`); } }); diff --git a/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css b/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css index c9af14d735eb..5163f47b4007 100644 --- a/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css +++ b/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css @@ -114,13 +114,14 @@ } /* Statusbar */ -.monaco-workbench .statusbar-item > .monaco-dropdown.send-feedback { +.monaco-workbench .statusbar > .items-container > .statusbar-item > .monaco-dropdown.send-feedback { display: inline-block; + padding: 0 5px 0 5px; } -.monaco-workbench .statusbar-item > .monaco-dropdown.send-feedback > .dropdown-label.send-feedback { - -webkit-mask: url('smiley.svg') no-repeat 50% 50%; /* use mask to be able to change color dynamically */ - width: 26px; +.monaco-workbench .statusbar > .items-container > .statusbar-item > .monaco-dropdown.send-feedback span.octicon { + text-align: center; + font-size: 14px; } /* Theming */ @@ -128,7 +129,7 @@ font-family: inherit; border: 1px solid transparent; } - + .vs .monaco-workbench .feedback-form .cancel { background: url('close.svg') center center no-repeat; } diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 230fbed257cf..fa65027d658d 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -14,8 +14,8 @@ import { URI } from 'vs/base/common/uri'; import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IFileService } from 'vs/platform/files/common/files'; /** * An implementation of editor for binary files like images. @@ -30,7 +30,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { @IWindowsService private readonly windowsService: IWindowsService, @IEditorService private readonly editorService: IEditorService, @IStorageService storageService: IStorageService, - @ITextFileService textFileService: ITextFileService, + @IFileService fileService: IFileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, ) { super( @@ -41,7 +41,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { }, telemetryService, themeService, - textFileService, + fileService, environmentService, storageService, ); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 4aadc5bd6fda..e6db85cf5ce6 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -18,7 +18,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; @@ -34,7 +33,7 @@ import { IEditorInput, IEditor } from 'vs/workbench/common/editor'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -44,7 +43,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, - @IProgressService2 progressService: IProgressService2 + @IProgressService progressService: IProgressService ) { super(); @@ -187,7 +186,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { // We try to be smart and only use the delay if we recognize that the user action is likely to cause // a new entry in the opened editors view. const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService); - delegatingEditorService.setEditorOpenHandler((group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise => { + delegatingEditorService.setEditorOpenHandler(async (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise => { let openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { let delay = 0; @@ -203,16 +202,19 @@ export class ExplorerViewlet extends ViewContainerViewlet { openEditorsView.setStructuralRefreshDelay(delay); } - const onSuccessOrError = (editor: BaseEditor | null) => { + let openedEditor: IEditor | null = null; + try { + openedEditor = await this.editorService.openEditor(editor, options, group); + } catch (error) { + // ignore + } finally { const openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { openEditorsView.setStructuralRefreshDelay(0); } + } - return editor; - }; - - return this.editorService.openEditor(editor, options, group).then(onSuccessOrError, onSuccessOrError); + return openedEditor; }); const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IEditorService, delegatingEditorService])); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 34b7c1c19664..ebadf65cd1ab 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -44,7 +44,6 @@ import { coalesce } from 'vs/base/common/arrays'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { sequence } from 'vs/base/common/async'; // {{SQL CARBON EDIT}} import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; @@ -90,15 +89,13 @@ export class NewFileAction extends Action { static readonly ID = 'workbench.files.action.createFileFromExplorer'; static readonly LABEL = nls.localize('createNewFile', "New File"); - private toDispose: IDisposable[] = []; - constructor( @IExplorerService explorerService: IExplorerService, @ICommandService private commandService: ICommandService ) { super('explorer.newFile', NEW_FILE_LABEL); this.class = 'explorer-action new-file'; - this.toDispose.push(explorerService.onDidChangeEditable(e => { + this._register(explorerService.onDidChangeEditable(e => { const elementIsBeingEdited = explorerService.isEditable(e); this.enabled = !elementIsBeingEdited; })); @@ -107,11 +104,6 @@ export class NewFileAction extends Action { run(): Promise { return this.commandService.executeCommand(NEW_FILE_COMMAND_ID); } - - dispose(): void { - super.dispose(); - dispose(this.toDispose); - } } /* New Folder */ @@ -119,15 +111,13 @@ export class NewFolderAction extends Action { static readonly ID = 'workbench.files.action.createFolderFromExplorer'; static readonly LABEL = nls.localize('createNewFolder', "New Folder"); - private toDispose: IDisposable[] = []; - constructor( @IExplorerService explorerService: IExplorerService, @ICommandService private commandService: ICommandService ) { super('explorer.newFolder', NEW_FOLDER_LABEL); this.class = 'explorer-action new-folder'; - this.toDispose.push(explorerService.onDidChangeEditable(e => { + this._register(explorerService.onDidChangeEditable(e => { const elementIsBeingEdited = explorerService.isEditable(e); this.enabled = !elementIsBeingEdited; })); @@ -136,11 +126,6 @@ export class NewFolderAction extends Action { run(): Promise { return this.commandService.executeCommand(NEW_FOLDER_COMMAND_ID); } - - dispose(): void { - super.dispose(); - dispose(this.toDispose); - } } /* Create new file from anywhere: Open untitled */ @@ -520,7 +505,6 @@ export class ToggleAutoSaveAction extends Action { } export abstract class BaseSaveAllAction extends Action { - private toDispose: IDisposable[]; private lastIsDirty: boolean; constructor( @@ -533,7 +517,6 @@ export abstract class BaseSaveAllAction extends Action { ) { super(id, label); - this.toDispose = []; this.lastIsDirty = this.textFileService.isDirty(); this.enabled = this.lastIsDirty; @@ -546,13 +529,13 @@ export abstract class BaseSaveAllAction extends Action { private registerListeners(): void { // listen to files being changed locally - this.toDispose.push(this.textFileService.models.onModelsDirty(e => this.updateEnablement(true))); - this.toDispose.push(this.textFileService.models.onModelsSaved(e => this.updateEnablement(false))); - this.toDispose.push(this.textFileService.models.onModelsReverted(e => this.updateEnablement(false))); - this.toDispose.push(this.textFileService.models.onModelsSaveError(e => this.updateEnablement(true))); + this._register(this.textFileService.models.onModelsDirty(e => this.updateEnablement(true))); + this._register(this.textFileService.models.onModelsSaved(e => this.updateEnablement(false))); + this._register(this.textFileService.models.onModelsReverted(e => this.updateEnablement(false))); + this._register(this.textFileService.models.onModelsSaveError(e => this.updateEnablement(true))); if (this.includeUntitled()) { - this.toDispose.push(this.untitledEditorService.onDidChangeDirty(resource => this.updateEnablement(this.untitledEditorService.isDirty(resource)))); + this._register(this.untitledEditorService.onDidChangeDirty(resource => this.updateEnablement(this.untitledEditorService.isDirty(resource)))); } } @@ -569,12 +552,6 @@ export abstract class BaseSaveAllAction extends Action { return false; }); } - - public dispose(): void { - this.toDispose = dispose(this.toDispose); - - super.dispose(); - } } export class SaveAllAction extends BaseSaveAllAction { @@ -677,15 +654,19 @@ export class CollapseExplorerView extends Action { public static readonly ID = 'workbench.files.action.collapseExplorerFolders'; public static readonly LABEL = nls.localize('collapseExplorerFolders', "Collapse Folders in Explorer"); - constructor( - id: string, + constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService + @IViewletService private readonly viewletService: IViewletService, + @IExplorerService readonly explorerService: IExplorerService ) { - super(id, label); + super(id, label, 'explorer-action collapse-explorer'); + this._register(explorerService.onDidChangeEditable(e => { + const elementIsBeingEdited = explorerService.isEditable(e); + this.enabled = !elementIsBeingEdited; + })); } - public run(): Promise { + run(): Promise { return this.viewletService.openViewlet(VIEWLET_ID).then((viewlet: ExplorerViewlet) => { const explorerView = viewlet.getExplorerView(); if (explorerView) { @@ -700,13 +681,17 @@ export class RefreshExplorerView extends Action { public static readonly ID = 'workbench.files.action.refreshFilesExplorer'; public static readonly LABEL = nls.localize('refreshExplorer', "Refresh Explorer"); + constructor( - id: string, - label: string, + id: string, label: string, @IViewletService private readonly viewletService: IViewletService, @IExplorerService private readonly explorerService: IExplorerService ) { super(id, label, 'explorer-action refresh-explorer'); + this._register(explorerService.onDidChangeEditable(e => { + const elementIsBeingEdited = explorerService.isEditable(e); + this.enabled = !elementIsBeingEdited; + })); } public run(): Promise { @@ -778,14 +763,6 @@ export function validateFileName(item: ExplorerItem, name: string): string | nul return nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name)); } - // Max length restriction (on Windows) - if (isWindows) { - const fullPathLength = item.resource.fsPath.length + 1 /* path segment */; - if (fullPathLength > 255) { - return nls.localize('filePathTooLongError', "The name **{0}** results in a path that is too long. Please choose a shorter name.", trimLongName(name)); - } - } - return null; } @@ -909,10 +886,8 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole const textFileService = accessor.get(ITextFileService); const editorService = accessor.get(IEditorService); const viewletService = accessor.get(IViewletService); - const activeViewlet = viewletService.getActiveViewlet(); - if (!activeViewlet || activeViewlet.getId() !== VIEWLET_ID || !listService.lastFocusedList) { - await viewletService.openViewlet(VIEWLET_ID, true); - } + + await viewletService.openViewlet(VIEWLET_ID, true); const list = listService.lastFocusedList; if (list) { @@ -1055,6 +1030,7 @@ export const pasteFileHandler = (accessor: ServicesAccessor) => { const clipboardService = accessor.get(IClipboardService); const explorerService = accessor.get(IExplorerService); const fileService = accessor.get(IFileService); + const textFileService = accessor.get(ITextFileService); const notificationService = accessor.get(INotificationService); const editorService = accessor.get(IEditorService); @@ -1064,7 +1040,7 @@ export const pasteFileHandler = (accessor: ServicesAccessor) => { const element = explorerContext.stat || explorerService.roots[0]; // Check if target is ancestor of pasted folder - sequence(toPaste.map(fileToPaste => () => { + Promise.all(toPaste.map(fileToPaste => { if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste, !isLinux /* ignorecase */)) { throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder")); @@ -1082,8 +1058,8 @@ export const pasteFileHandler = (accessor: ServicesAccessor) => { const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwirte: pasteShouldMove }); - // Copy File - return pasteShouldMove ? fileService.move(fileToPaste, targetFile) : fileService.copy(fileToPaste, targetFile); + // Move/Copy File + return pasteShouldMove ? textFileService.move(fileToPaste, targetFile) : fileService.copy(fileToPaste, targetFile); }, error => { onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile"))); }); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index e0f227669150..1410c4446311 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -295,7 +295,7 @@ configurationRegistry.registerConfiguration({ nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onFocusChange' }, "A dirty file is automatically saved when the editor loses focus."), nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onWindowChange' }, "A dirty file is automatically saved when the window loses focus.") ], - 'default': AutoSaveConfiguration.OFF, + 'default': platform.isWeb ? AutoSaveConfiguration.AFTER_DELAY : AutoSaveConfiguration.OFF, 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls auto save of dirty files. Read more about autosave [here](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) }, 'files.autoSaveDelay': { diff --git a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts index f351dc57263a..af19d0a4929f 100644 --- a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { basename } from 'vs/base/common/resources'; -import { Action } from 'vs/base/common/actions'; +import { Action, IAction } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel, IResolvedTextFileEditorModel, IWriteTextFileOptions } from 'vs/workbench/services/textfile/common/textfiles'; @@ -106,7 +106,8 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I const resource = model.getResource(); let message: string; - const actions: INotificationActions = { primary: [], secondary: [] }; + const primaryActions: IAction[] = []; + const secondaryActions: IAction[] = []; // Dirty write prevention if (fileOperationError.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { @@ -119,15 +120,15 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I message = conflictEditorHelp; - actions.primary!.push(this.instantiationService.createInstance(ResolveConflictLearnMoreAction)); - actions.secondary!.push(this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction)); + primaryActions.push(this.instantiationService.createInstance(ResolveConflictLearnMoreAction)); + secondaryActions.push(this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction)); } // Otherwise show the message that will lead the user into the save conflict editor. else { message = nls.localize('staleSaveError', "Failed to save '{0}': The content of the file is newer. Please compare your version with the file contents.", basename(resource)); - actions.primary!.push(this.instantiationService.createInstance(ResolveSaveConflictAction, model)); + primaryActions.push(this.instantiationService.createInstance(ResolveSaveConflictAction, model)); } } @@ -140,24 +141,24 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I // Save Elevated if (canHandlePermissionOrReadonlyErrors && (isPermissionDenied || triedToMakeWriteable)) { - actions.primary!.push(this.instantiationService.createInstance(SaveElevatedAction, model, triedToMakeWriteable)); + primaryActions.push(this.instantiationService.createInstance(SaveElevatedAction, model, triedToMakeWriteable)); } // Overwrite else if (canHandlePermissionOrReadonlyErrors && isReadonly) { - actions.primary!.push(this.instantiationService.createInstance(OverwriteReadonlyAction, model)); + primaryActions.push(this.instantiationService.createInstance(OverwriteReadonlyAction, model)); } // Retry else { - actions.primary!.push(this.instantiationService.createInstance(ExecuteCommandAction, SAVE_FILE_COMMAND_ID, nls.localize('retry', "Retry"))); + primaryActions.push(this.instantiationService.createInstance(ExecuteCommandAction, SAVE_FILE_COMMAND_ID, nls.localize('retry', "Retry"))); } // Save As - actions.primary!.push(this.instantiationService.createInstance(ExecuteCommandAction, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL)); + primaryActions.push(this.instantiationService.createInstance(ExecuteCommandAction, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL)); // Discard - actions.primary!.push(this.instantiationService.createInstance(ExecuteCommandAction, REVERT_FILE_COMMAND_ID, nls.localize('discard', "Discard"))); + primaryActions.push(this.instantiationService.createInstance(ExecuteCommandAction, REVERT_FILE_COMMAND_ID, nls.localize('discard', "Discard"))); // Message if (canHandlePermissionOrReadonlyErrors && isReadonly) { @@ -174,8 +175,9 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I } // Show message and keep function to hide in case the file gets saved/reverted + const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; const handle = this.notificationService.notify({ severity: Severity.Error, message, actions }); - Event.once(handle.onDidClose)(() => dispose(...actions.primary!, ...actions.secondary!)); + Event.once(handle.onDidClose)(() => { dispose(primaryActions), dispose(secondaryActions); }); this.messages.set(model.getResource(), handle); } @@ -253,12 +255,16 @@ class ResolveSaveConflictAction extends Action { } // Show additional help how to resolve the save conflict - const actions: INotificationActions = { primary: [], secondary: [] }; - actions.primary!.push(this.instantiationService.createInstance(ResolveConflictLearnMoreAction)); - actions.secondary!.push(this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction)); - + const primaryActions: IAction[] = [ + this.instantiationService.createInstance(ResolveConflictLearnMoreAction) + ]; + const secondaryActions: IAction[] = [ + this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction) + ]; + + const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; const handle = this.notificationService.notify({ severity: Severity.Info, message: conflictEditorHelp, actions }); - Event.once(handle.onDidClose)(() => dispose(...actions.primary!, ...actions.secondary!)); + Event.once(handle.onDidClose)(() => { dispose(primaryActions); dispose(secondaryActions); }); pendingResolveSaveConflictMessages.push(handle); } diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 9edc63a22fbc..5885e6c1eda8 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -5,13 +5,12 @@ import * as nls from 'vs/nls'; import * as errors from 'vs/base/common/errors'; -import * as env from 'vs/base/common/platform'; import * as DOM from 'vs/base/browser/dom'; import { IAction } from 'vs/base/common/actions'; import { Button } from 'vs/base/browser/ui/button/button'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { OpenFolderAction, OpenFileFolderAction, AddRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { OpenFolderAction, AddRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -69,11 +68,11 @@ export class EmptyView extends ViewletPanel { this.button = new Button(messageContainer); attachButtonStyler(this.button, this.themeService); - this.disposables.push(this.button.onDidClick(() => { + this._register(this.button.onDidClick(() => { if (!this.actionRunner) { return; } - const actionClass = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? AddRootFolderAction : env.isMacintosh ? OpenFileFolderAction : OpenFolderAction; + const actionClass = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? AddRootFolderAction : OpenFolderAction; const action = this.instantiationService.createInstance(actionClass, actionClass.ID, actionClass.LABEL); this.actionRunner.run(action).then(() => { action.dispose(); @@ -83,7 +82,7 @@ export class EmptyView extends ViewletPanel { }); })); - this.disposables.push(new DragAndDropObserver(container, { + this._register(new DragAndDropObserver(container, { onDrop: e => { const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); container.style.backgroundColor = color ? color.toString() : ''; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 13edb5fef1a3..6c714650cfdc 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -9,18 +9,17 @@ import * as perf from 'vs/base/common/performance'; import { Action, IAction } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash } from 'vs/workbench/contrib/files/common/files'; -import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView } from 'vs/workbench/contrib/files/browser/fileActions'; +import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView, CollapseExplorerView } from 'vs/workbench/contrib/files/browser/fileActions'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import * as DOM from 'vs/base/browser/dom'; -import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ExplorerDecorationsProvider } from 'vs/workbench/contrib/files/browser/views/explorerDecorationsProvider'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ResourceContextKey } from 'vs/workbench/common/resources'; @@ -93,7 +92,7 @@ export class ExplorerView extends ViewletPanel { super({ ...(options as IViewletPanelOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService); this.resourceContext = instantiationService.createInstance(ResourceContextKey); - this.disposables.push(this.resourceContext); + this._register(this.resourceContext); this.folderContext = ExplorerFolderContext.bindTo(contextKeyService); this.readonlyContext = ExplorerResourceReadonlyContext.bindTo(contextKeyService); this.rootContext = ExplorerRootContext.bindTo(contextKeyService); @@ -101,8 +100,8 @@ export class ExplorerView extends ViewletPanel { const decorationProvider = new ExplorerDecorationsProvider(this.explorerService, contextService); decorationService.registerDecorationsProvider(decorationProvider); - this.disposables.push(decorationProvider); - this.disposables.push(this.resourceContext); + this._register(decorationProvider); + this._register(this.resourceContext); } get name(): string { @@ -120,7 +119,7 @@ export class ExplorerView extends ViewletPanel { // Memoized locals @memoize private get contributedContextMenu(): IMenu { const contributedContextMenu = this.menuService.createMenu(MenuId.ExplorerContext, this.tree.contextKeyService); - this.disposables.push(contributedContextMenu); + this._register(contributedContextMenu); return contributedContextMenu; } @@ -148,8 +147,8 @@ export class ExplorerView extends ViewletPanel { titleElement.title = title; }; - this.disposables.push(this.contextService.onDidChangeWorkspaceName(setHeader)); - this.disposables.push(this.labelService.onDidChangeFormatters(setHeader)); + this._register(this.contextService.onDidChangeWorkspaceName(setHeader)); + this._register(this.labelService.onDidChangeFormatters(setHeader)); setHeader(); } @@ -165,14 +164,13 @@ export class ExplorerView extends ViewletPanel { this.toolbar.setActions(this.getActions(), this.getSecondaryActions())(); } - this.disposables.push(this.labelService.onDidChangeFormatters(() => { + this._register(this.labelService.onDidChangeFormatters(() => { this._onDidChangeTitleArea.fire(); - this.refresh(true); })); - this.disposables.push(this.explorerService.onDidChangeRoots(() => this.setTreeInput())); - this.disposables.push(this.explorerService.onDidChangeItem(e => this.refresh(e.recursive, e.item))); - this.disposables.push(this.explorerService.onDidChangeEditable(async e => { + this._register(this.explorerService.onDidChangeRoots(() => this.setTreeInput())); + this._register(this.explorerService.onDidChangeItem(e => this.refresh(e.recursive, e.item))); + this._register(this.explorerService.onDidChangeEditable(async e => { const isEditing = !!this.explorerService.getEditableData(e); if (isEditing) { @@ -190,22 +188,22 @@ export class ExplorerView extends ViewletPanel { this.tree.domFocus(); } })); - this.disposables.push(this.explorerService.onDidSelectResource(e => this.onSelectResource(e.resource, e.reveal))); - this.disposables.push(this.explorerService.onDidCopyItems(e => this.onCopyItems(e.items, e.cut, e.previouslyCutItems))); + this._register(this.explorerService.onDidSelectResource(e => this.onSelectResource(e.resource, e.reveal))); + this._register(this.explorerService.onDidCopyItems(e => this.onCopyItems(e.items, e.cut, e.previouslyCutItems))); // Update configuration const configuration = this.configurationService.getValue(); this.onConfigurationUpdated(configuration); // When the explorer viewer is loaded, listen to changes to the editor input - this.disposables.push(this.editorService.onDidActiveEditorChange(() => { + this._register(this.editorService.onDidActiveEditorChange(() => { this.selectActiveFile(true); })); // Also handle configuration updates - this.disposables.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue(), e))); + this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(this.configurationService.getValue(), e))); - this.disposables.push(this.onDidChangeBodyVisibility(async visible => { + this._register(this.onDidChangeBodyVisibility(async visible => { if (visible) { // If a refresh was requested and we are now visible, run it if (this.shouldRefresh) { @@ -224,7 +222,7 @@ export class ExplorerView extends ViewletPanel { actions.push(this.instantiationService.createInstance(NewFileAction)); actions.push(this.instantiationService.createInstance(NewFolderAction)); actions.push(this.instantiationService.createInstance(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL)); - actions.push(this.instantiationService.createInstance(CollapseAction, this.tree, true, 'explorer-action collapse-explorer')); + actions.push(this.instantiationService.createInstance(CollapseExplorerView, CollapseExplorerView.ID, CollapseExplorerView.LABEL)); return actions; } @@ -266,15 +264,15 @@ export class ExplorerView extends ViewletPanel { private createTree(container: HTMLElement): void { this.filter = this.instantiationService.createInstance(FilesFilter); - this.disposables.push(this.filter); + this._register(this.filter); const explorerLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); - this.disposables.push(explorerLabels); + this._register(explorerLabels); const updateWidth = (stat: ExplorerItem) => this.tree.updateWidth(stat); const filesRenderer = this.instantiationService.createInstance(FilesRenderer, explorerLabels, updateWidth); - this.disposables.push(filesRenderer); + this._register(filesRenderer); - this.disposables.push(createFileIconThemableTreeContainerScope(container, this.themeService)); + this._register(createFileIconThemableTreeContainerScope(container, this.themeService)); this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, container, new ExplorerDelegate(), [filesRenderer], this.instantiationService.createInstance(ExplorerDataSource), { @@ -304,19 +302,19 @@ export class ExplorerView extends ViewletPanel { dnd: this.instantiationService.createInstance(FileDragAndDrop), autoExpandSingleChildren: true }) as WorkbenchAsyncDataTree; - this.disposables.push(this.tree); + this._register(this.tree); // Bind context keys FilesExplorerFocusedContext.bindTo(this.tree.contextKeyService); ExplorerFocusedContext.bindTo(this.tree.contextKeyService); // Update resource context based on focused element - this.disposables.push(this.tree.onDidChangeFocus(e => this.onFocusChanged(e.elements))); + this._register(this.tree.onDidChangeFocus(e => this.onFocusChanged(e.elements))); this.onFocusChanged([]); const explorerNavigator = new TreeResourceNavigator2(this.tree); - this.disposables.push(explorerNavigator); + this._register(explorerNavigator); // Open when selecting via keyboard - this.disposables.push(explorerNavigator.onDidOpenResource(e => { + this._register(explorerNavigator.onDidOpenResource(e => { const selection = this.tree.getSelection(); // Do not react if the user is expanding selection via keyboard. // Check if the item was previously also selected, if yes the user is simply expanding / collapsing current selection #66589. @@ -339,12 +337,12 @@ export class ExplorerView extends ViewletPanel { } })); - this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e))); + this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); // save view state on shutdown - this.storageService.onWillSaveState(() => { + this._register(this.storageService.onWillSaveState(() => { this.storageService.store(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, JSON.stringify(this.tree.getViewState()), StorageScope.WORKSPACE); - }, null, this.disposables); + }, null)); } // React on events @@ -369,11 +367,21 @@ export class ExplorerView extends ViewletPanel { } } + private setContextKeys(stat: ExplorerItem | null): void { + const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER; + const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : null; + this.resourceContext.set(resource); + this.folderContext.set((isSingleFolder && !stat) || !!stat && stat.isDirectory); + this.readonlyContext.set(!!stat && stat.isReadonly); + this.rootContext.set(!stat || (stat && stat.isRoot)); + } + private onContextMenu(e: ITreeContextMenuEvent): void { const stat = e.element; // update dynamic contexts this.fileCopiedContextKey.set(this.clipboardService.hasResources()); + this.setContextKeys(stat); const selection = this.tree.getSelection(); this.contextMenuService.showContextMenu({ @@ -398,13 +406,8 @@ export class ExplorerView extends ViewletPanel { } private onFocusChanged(elements: ExplorerItem[]): void { - const stat = elements && elements.length ? elements[0] : undefined; - const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER; - const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : null; - this.resourceContext.set(resource); - this.folderContext.set((isSingleFolder && !stat) || !!stat && stat.isDirectory); - this.readonlyContext.set(!!stat && stat.isReadonly); - this.rootContext.set(!stat || (stat && stat.isRoot)); + const stat = elements && elements.length ? elements[0] : null; + this.setContextKeys(stat); if (stat) { const enableTrash = this.configurationService.getValue().files.enableTrash; @@ -490,7 +493,11 @@ export class ExplorerView extends ViewletPanel { } }); - this.progressService.showWhile(promise, this.layoutService.isRestored() ? 800 : 1200 /* less ugly initial startup */); + this.progressService.withProgress({ + location: ProgressLocation.Explorer, + delay: this.layoutService.isRestored() ? 800 : 1200 // less ugly initial startup + }, _progress => promise); + return promise; } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index fdad5d3b4407..0437444c785d 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -7,7 +7,7 @@ import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import * as DOM from 'vs/base/browser/dom'; import * as glob from 'vs/base/common/glob'; import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileService, FileKind, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -99,7 +99,11 @@ export class ExplorerDataSource implements IAsyncDataSource promise); + return promise; } } @@ -218,21 +222,19 @@ export class FilesRenderer implements ITreeRenderer 0 && !stat.isDirectory ? lastDot : value.length }); - const done = once(async (success: boolean) => { + const done = once(async (success: boolean, finishEditing: boolean) => { label.element.style.display = 'none'; const value = inputBox.value; dispose(toDispose); container.removeChild(label.element); - // Timeout: once done rendering only then re-render #70902 - setTimeout(() => editableData.onFinish(value, success), 0); + if (finishEditing) { + // Timeout: once done rendering only then re-render #70902 + setTimeout(() => editableData.onFinish(value, success), 0); + } }); - let ignoreDisposeAndBlur = true; - setTimeout(() => ignoreDisposeAndBlur = false, 100); const blurDisposable = DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { - if (!ignoreDisposeAndBlur) { - done(inputBox.isInputValid()); - } + done(inputBox.isInputValid(), true); }); const toDispose = [ @@ -240,10 +242,10 @@ export class FilesRenderer implements ITreeRenderer { if (e.equals(KeyCode.Enter)) { if (inputBox.validate()) { - done(true); + done(true, true); } } else if (e.equals(KeyCode.Escape)) { - done(false); + done(false, true); } }), blurDisposable, @@ -252,10 +254,8 @@ export class FilesRenderer implements ITreeRenderer { - if (!ignoreDisposeAndBlur) { - blurDisposable.dispose(); - done(false); - } + blurDisposable.dispose(); + done(false, false); }); } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 06dc9f65da7e..9e15a775d95d 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -96,14 +96,14 @@ export class OpenEditorsView extends ViewletPanel { this.registerUpdateEvents(); // Also handle configuration updates - this.disposables.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(e))); + this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(e))); // Handle dirty counter - this.disposables.push(this.untitledEditorService.onDidChangeDirty(() => this.updateDirtyIndicator())); - this.disposables.push(this.textFileService.models.onModelsDirty(() => this.updateDirtyIndicator())); - this.disposables.push(this.textFileService.models.onModelsSaved(() => this.updateDirtyIndicator())); - this.disposables.push(this.textFileService.models.onModelsSaveError(() => this.updateDirtyIndicator())); - this.disposables.push(this.textFileService.models.onModelsReverted(() => this.updateDirtyIndicator())); + this._register(this.untitledEditorService.onDidChangeDirty(() => this.updateDirtyIndicator())); + this._register(this.textFileService.models.onModelsDirty(() => this.updateDirtyIndicator())); + this._register(this.textFileService.models.onModelsSaved(() => this.updateDirtyIndicator())); + this._register(this.textFileService.models.onModelsSaveError(() => this.updateDirtyIndicator())); + this._register(this.textFileService.models.onModelsReverted(() => this.updateDirtyIndicator())); } private registerUpdateEvents(): void { @@ -163,16 +163,16 @@ export class OpenEditorsView extends ViewletPanel { } } })); - this.disposables.push(groupDisposables.get(group.id)!); + this._register(groupDisposables.get(group.id)!); }; this.editorGroupService.groups.forEach(g => addGroupListener(g)); - this.disposables.push(this.editorGroupService.onDidAddGroup(group => { + this._register(this.editorGroupService.onDidAddGroup(group => { addGroupListener(group); updateWholeList(); })); - this.disposables.push(this.editorGroupService.onDidMoveGroup(() => updateWholeList())); - this.disposables.push(this.editorGroupService.onDidRemoveGroup(group => { + this._register(this.editorGroupService.onDidMoveGroup(() => updateWholeList())); + this._register(this.editorGroupService.onDidRemoveGroup(group => { dispose(groupDisposables.get(group.id)); updateWholeList(); })); @@ -184,7 +184,7 @@ export class OpenEditorsView extends ViewletPanel { const count = dom.append(container, $('.count')); this.dirtyCountElement = dom.append(count, $('.monaco-count-badge')); - this.disposables.push((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => { + this._register((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => { const background = colors.badgeBackground ? colors.badgeBackground.toString() : null; const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : null; const border = colors.contrastBorder ? colors.contrastBorder.toString() : null; @@ -220,11 +220,11 @@ export class OpenEditorsView extends ViewletPanel { identityProvider: { getId: (element: OpenEditor | IEditorGroup) => element instanceof OpenEditor ? element.getId() : element.id.toString() }, dnd: new OpenEditorsDragAndDrop(this.instantiationService, this.editorGroupService) }) as WorkbenchList; - this.disposables.push(this.list); - this.disposables.push(this.listLabels); + this._register(this.list); + this._register(this.listLabels); this.contributedContextMenu = this.menuService.createMenu(MenuId.OpenEditorsContext, this.list.contextKeyService); - this.disposables.push(this.contributedContextMenu); + this._register(this.contributedContextMenu); this.updateSize(); @@ -233,11 +233,11 @@ export class OpenEditorsView extends ViewletPanel { ExplorerFocusedContext.bindTo(this.list.contextKeyService); this.resourceContext = this.instantiationService.createInstance(ResourceContextKey); - this.disposables.push(this.resourceContext); + this._register(this.resourceContext); this.groupFocusedContext = OpenEditorsGroupContext.bindTo(this.contextKeyService); this.dirtyEditorFocusedContext = DirtyEditorContext.bindTo(this.contextKeyService); - this.disposables.push(this.list.onContextMenu(e => this.onListContextMenu(e))); + this._register(this.list.onContextMenu(e => this.onListContextMenu(e))); this.list.onFocusChange(e => { this.resourceContext.reset(); this.groupFocusedContext.reset(); @@ -252,12 +252,12 @@ export class OpenEditorsView extends ViewletPanel { }); // Open when selecting via keyboard - this.disposables.push(this.list.onMouseMiddleClick(e => { + this._register(this.list.onMouseMiddleClick(e => { if (e && e.element instanceof OpenEditor) { e.element.group.closeEditor(e.element.editor, { preserveFocus: true }); } })); - this.disposables.push(this.list.onDidOpen(e => { + this._register(this.list.onDidOpen(e => { const browserEvent = e.browserEvent; let openToSide = false; @@ -280,7 +280,7 @@ export class OpenEditorsView extends ViewletPanel { this.listRefreshScheduler.schedule(0); - this.disposables.push(this.onDidChangeBodyVisibility(visible => { + this._register(this.onDidChangeBodyVisibility(visible => { if (visible && this.needsRefresh) { this.listRefreshScheduler.schedule(0); } diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 67af5d3cc803..5ccaa5c27975 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -243,27 +243,23 @@ export class ExplorerItem { return this.children.get(this.getPlatformAwareName(name)); } - fetchChildren(fileService: IFileService, explorerService: IExplorerService): Promise { - let promise: Promise = Promise.resolve(undefined); + async fetchChildren(fileService: IFileService, explorerService: IExplorerService): Promise { if (!this._isDirectoryResolved) { // Resolve metadata only when the mtime is needed since this can be expensive // Mtime is only used when the sort order is 'modified' const resolveMetadata = explorerService.sortOrder === 'modified'; - promise = fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }).then(stat => { - const resolved = ExplorerItem.create(stat, this); - ExplorerItem.mergeLocalWithDisk(resolved, this); - this._isDirectoryResolved = true; - }); + const stat = await fileService.resolve(this.resource, { resolveSingleChildDescendants: true, resolveMetadata }); + const resolved = ExplorerItem.create(stat, this); + ExplorerItem.mergeLocalWithDisk(resolved, this); + this._isDirectoryResolved = true; } - return promise.then(() => { - const items: ExplorerItem[] = []; - this.children.forEach(child => { - items.push(child); - }); - - return items; + const items: ExplorerItem[] = []; + this.children.forEach(child => { + items.push(child); }); + + return items; } /** diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 45da0cdab093..f4633a246b55 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -147,7 +147,7 @@ export class ExplorerService implements IExplorerService { return !!this.editable && (this.editable.stat === stat || !stat); } - select(resource: URI, reveal?: boolean): Promise { + async select(resource: URI, reveal?: boolean): Promise { const fileStat = this.findClosest(resource); if (fileStat) { this._onDidSelectResource.fire({ resource: fileStat.resource, reveal }); @@ -159,7 +159,9 @@ export class ExplorerService implements IExplorerService { const workspaceFolder = this.contextService.getWorkspaceFolder(resource); const rootUri = workspaceFolder ? workspaceFolder.uri : this.roots[0].resource; const root = this.roots.filter(r => r.resource.toString() === rootUri.toString()).pop()!; - return this.fileService.resolve(rootUri, options).then(stat => { + + try { + const stat = await this.fileService.resolve(rootUri, options); // Convert to model const modelStat = ExplorerItem.create(stat, undefined, options.resolveTo); @@ -170,17 +172,19 @@ export class ExplorerService implements IExplorerService { // Select and Reveal this._onDidSelectResource.fire({ resource: item ? item.resource : undefined, reveal }); - }, () => { + } catch (error) { root.isError = true; this._onDidChangeItem.fire({ item: root, recursive: false }); - }); + } } refresh(): void { this.model.roots.forEach(r => r.forgetChildren()); this._onDidChangeItem.fire({ recursive: true }); const resource = this.editorService.activeEditor ? this.editorService.activeEditor.getResource() : undefined; - if (resource) { + const autoReveal = this.configurationService.getValue().explorer.autoReveal; + + if (resource && autoReveal) { // We did a top level refresh, reveal the active file #67118 this.select(resource, true); } @@ -281,7 +285,7 @@ export class ExplorerService implements IExplorerService { if (added.length) { // Check added: Refresh if added file/folder is not part of resolved root and parent is part of it - const ignoredPaths: { [resource: string]: boolean } = <{ [resource: string]: boolean }>{}; + const ignoredPaths: Set = new Set(); for (let i = 0; i < added.length; i++) { const change = added[i]; @@ -289,7 +293,7 @@ export class ExplorerService implements IExplorerService { const parent = dirname(change.resource); // Continue if parent was already determined as to be ignored - if (ignoredPaths[parent.toString()]) { + if (ignoredPaths.has(parent.toString())) { continue; } @@ -301,7 +305,7 @@ export class ExplorerService implements IExplorerService { // Keep track of path that can be ignored for faster lookup if (!parentStat || !parentStat.isDirectoryResolved) { - ignoredPaths[parent.toString()] = true; + ignoredPaths.add(parent.toString()); } } } diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts index bc8199aa4516..801c4423fa1b 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -26,7 +26,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ITextModel } from 'vs/editor/common/model'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { ILabelService } from 'vs/platform/label/common/label'; import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -46,7 +45,6 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { @INotificationService private readonly _notificationService: INotificationService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IModeService private readonly _modeService: IModeService, - @IStatusbarService private readonly _statusbarService: IStatusbarService, @ILabelService private readonly _labelService: ILabelService, ) { super(); @@ -97,7 +95,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution { // formatter does not target this file const label = this._labelService.getUriLabel(document.uri, { relative: true }); const message = nls.localize('miss', "Extension '{0}' cannot format '{1}'", extension.displayName || extension.name, label); - this._statusbarService.setStatusMessage(message, 4000); + this._notificationService.status(message, { hideAfter: 4000 }); return undefined; } } else if (formatter.length === 1) { diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index b15e07315ff2..33534fb32435 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -17,28 +17,30 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { OpenLogsFolderAction, SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; +import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; +import { dirname } from 'vs/base/common/resources'; class LogOutputChannels extends Disposable implements IWorkbenchContribution { constructor( @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @ILogService logService: ILogService + @ILogService logService: ILogService, + @IFileService private readonly fileService: IFileService ) { super(); - let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - outputChannelRegistry.registerChannel({ id: Constants.mainLogChannelId, label: nls.localize('mainLog', "Main"), file: URI.file(join(environmentService.logsPath, `main.log`)), log: true }); - outputChannelRegistry.registerChannel({ id: Constants.sharedLogChannelId, label: nls.localize('sharedLog', "Shared"), file: URI.file(join(environmentService.logsPath, `sharedprocess.log`)), log: true }); - outputChannelRegistry.registerChannel({ id: Constants.rendererLogChannelId, label: nls.localize('rendererLog', "Window"), file: URI.file(join(environmentService.logsPath, `renderer${environmentService.configuration.windowId}.log`)), log: true }); + this.registerLogChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Main"), URI.file(join(environmentService.logsPath, `main.log`))); + this.registerLogChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Shared"), URI.file(join(environmentService.logsPath, `sharedprocess.log`))); + this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), URI.file(join(environmentService.logsPath, `renderer${environmentService.configuration.windowId}.log`))); // {{SQL CARBON EDIT}} @anthonydresser 05/19/19 investigate, this should be in the extension let toolsServiceLogFile: string = join(environmentService.logsPath, '..', '..', 'mssql', `sqltools_${Date.now()}.log`); console.log(`SqlTools Log file is: ${toolsServiceLogFile}`); - outputChannelRegistry.registerChannel({ id: Constants.sqlToolsLogChannellId, label: nls.localize('sqlToolsLog', "Log (SqlTools)"), file: URI.file(toolsServiceLogFile), log: true }); + this.registerLogChannel(Constants.sqlToolsLogChannellId, nls.localize('sqlToolsLog', "Log (SqlTools)"), URI.file(toolsServiceLogFile)); // {{SQL CARBON EDIT}} - End const registerTelemetryChannel = (level: LogLevel) => { - if (level === LogLevel.Trace && !outputChannelRegistry.getChannel(Constants.telemetryLogChannelId)) { - outputChannelRegistry.registerChannel({ id: Constants.telemetryLogChannelId, label: nls.localize('telemetryLog', "Telemetry"), file: URI.file(join(environmentService.logsPath, `telemetry.log`)), log: true }); + if (level === LogLevel.Trace && !Registry.as(OutputExt.OutputChannels).getChannel(Constants.telemetryLogChannelId)) { + this.registerLogChannel(Constants.telemetryLogChannelId, nls.localize('telemetryLog', "Telemetry"), URI.file(join(environmentService.logsPath, `telemetry.log`))); } }; registerTelemetryChannel(logService.getLevel()); @@ -49,6 +51,25 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Log Folder', devCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level', devCategory); } + + private async registerLogChannel(id: string, label: string, file: URI): Promise { + const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); + const exists = await this.fileService.exists(file); + if (exists) { + outputChannelRegistry.registerChannel({ id, label, file, log: true }); + return; + } + + const watcher = this.fileService.watch(dirname(file)); + const disposable = this.fileService.onFileChanges(e => { + if (e.contains(file, FileChangeType.ADDED)) { + watcher.dispose(); + disposable.dispose(); + outputChannelRegistry.registerChannel({ id, label, file, log: true }); + } + }); + } + } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Eventually); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 3efc323eed61..44a427b079c9 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -19,12 +19,16 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ToggleMarkersPanelAction, ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IMarkersWorkbenchService, MarkersWorkbenchService, ActivityUpdater } from 'vs/workbench/contrib/markers/browser/markers'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ActivePanelContext } from 'vs/workbench/common/panel'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; +import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService, false); @@ -253,3 +257,86 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { }, order: 4 }); + +CommandsRegistry.registerCommand('workbench.actions.view.toggleProblems', accessor => { + const panelService = accessor.get(IPanelService); + const panel = accessor.get(IPanelService).getActivePanel(); + if (panel && panel.getId() === Constants.MARKERS_PANEL_ID) { + panelService.hideActivePanel(); + } else { + panelService.openPanel(Constants.MARKERS_PANEL_ID, true); + } +}); + +class MarkersStatusBarContributions extends Disposable implements IWorkbenchContribution { + + private markersStatusItem: IStatusbarEntryAccessor; + + constructor( + @IMarkerService private readonly markerService: IMarkerService, + @IStatusbarService private readonly statusbarService: IStatusbarService + ) { + super(); + this.markersStatusItem = this._register(this.statusbarService.addEntry(this.getMarkersItem(), 'status.problems', localize('status.problems', "Problems"), StatusbarAlignment.LEFT, 50 /* Medium Priority */)); + this.markerService.onMarkerChanged(() => this.markersStatusItem.update(this.getMarkersItem())); + } + + private getMarkersItem(): IStatusbarEntry { + const markersStatistics = this.markerService.getStatistics(); + return { + text: this.getMarkersText(markersStatistics), + tooltip: this.getMarkersTooltip(markersStatistics), + command: 'workbench.actions.view.toggleProblems' + }; + } + + private getMarkersTooltip(stats: MarkerStatistics): string { + const errorTitle = (n: number) => localize('totalErrors', "{0} Errors", n); + const warningTitle = (n: number) => localize('totalWarnings', "{0} Warnings", n); + const infoTitle = (n: number) => localize('totalInfos', "{0} Infos", n); + + const titles: string[] = []; + + if (stats.errors > 0) { + titles.push(errorTitle(stats.errors)); + } + + if (stats.warnings > 0) { + titles.push(warningTitle(stats.warnings)); + } + + if (stats.infos > 0) { + titles.push(infoTitle(stats.infos)); + } + + if (titles.length === 0) { + return localize('noProblems', "No Problems"); + } + + return titles.join(', '); + } + + private getMarkersText(stats: MarkerStatistics): string { + const problemsText: string[] = []; + + // Errors + problemsText.push('$(error) ' + this.packNumber(stats.errors)); + + // Warnings + problemsText.push('$(warning) ' + this.packNumber(stats.warnings)); + + // Info (only if any) + if (stats.infos > 0) { + problemsText.push('$(info) ' + this.packNumber(stats.infos)); + } + + return problemsText.join(' '); + } + + private packNumber(n: number): string { + const manyProblems = localize('manyProblems', "10K+"); + return n > 9999 ? manyProblems : n > 999 ? n.toString().charAt(0) + 'K' : n.toString(); + } +} + +workbenchRegistry.registerWorkbenchContribution(MarkersStatusBarContributions, LifecyclePhase.Restored); \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 08555275ac7b..c22e5c0ea6d8 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -44,6 +44,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IMarker } from 'vs/platform/markers/common/markers'; import { withUndefinedAsNull } from 'vs/base/common/types'; +import { MementoObject } from 'vs/workbench/common/memento'; function createModelIterator(model: MarkersModel): Iterator> { const resourcesIt = Iterator.fromArray(model.resourceMarkers); @@ -79,12 +80,12 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private treeContainer: HTMLElement; private messageBoxContainer: HTMLElement; private ariaLabelElement: HTMLElement; - private panelState: object; + private readonly panelState: MementoObject; private panelFoucusContextKey: IContextKey; private filter: Filter; - private _onDidFilter = new Emitter(); + private _onDidFilter = this._register(new Emitter()); readonly onDidFilter: Event = this._onDidFilter.event; private cachedFilterStats: { total: number; filtered: number; } | undefined = undefined; @@ -162,7 +163,11 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return; } - this.tree.getHTMLElement().focus(); + if (this.isEmpty()) { + this.messageBoxContainer.focus(); + } else { + this.tree.getHTMLElement().focus(); + } } public focusFilter(): void { @@ -348,7 +353,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } })); - this.tree.onContextMenu(this.onContextMenu, this, this._toDispose); + this._register(this.tree.onContextMenu(this.onContextMenu, this)); this._register(this.configurationService.onDidChangeConfiguration(e => { if (this.filterAction.useFilesExclude && e.affectsConfiguration('files.exclude')) { @@ -470,11 +475,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } } + private isEmpty(): boolean { + const { total, filtered } = this.getFilterStats(); + return total === 0 || filtered === 0; + } + private render(): void { this.cachedFilterStats = undefined; this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); - const { total, filtered } = this.getFilterStats(); - dom.toggleClass(this.treeContainer, 'hidden', total === 0 || filtered === 0); + dom.toggleClass(this.treeContainer, 'hidden', this.isEmpty()); this.renderMessage(); } diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index a2a402686903..f495beb41570 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -43,6 +43,7 @@ import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; +import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; export type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -255,7 +256,7 @@ class MarkerWidget extends Disposable { this.actionBar = this._register(new ActionBar(dom.append(parent, dom.$('.actions')), { actionViewItemProvider: (action) => action.id === QuickFixAction.ID ? instantiationService.createInstance(QuickFixActionViewItem, action) : undefined })); - this.icon = dom.append(parent, dom.$('.icon')); + this.icon = dom.append(parent, dom.$('')); this.multilineActionbar = this._register(new ActionBar(dom.append(parent, dom.$('.multiline-actions')))); this.messageAndDetailsContainer = dom.append(parent, dom.$('.marker-message-details')); this._register(toDisposable(() => this.disposables = dispose(this.disposables))); @@ -269,7 +270,7 @@ class MarkerWidget extends Disposable { } dom.clearNode(this.messageAndDetailsContainer); - this.icon.className = 'marker-icon ' + MarkerWidget.iconClassNameFor(element.marker); + this.icon.className = `marker-icon ${SeverityIcon.className(MarkerSeverity.toSeverity(element.marker.severity))}`; this.renderQuickfixActionbar(element); this.renderMultilineActionbar(element); @@ -345,20 +346,6 @@ class MarkerWidget extends Disposable { const lnCol = dom.append(parent, dom.$('span.marker-line')); lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(marker.startLineNumber, marker.startColumn); } - - private static iconClassNameFor(element: IMarker): string { - switch (element.severity) { - case MarkerSeverity.Hint: - return 'info'; - case MarkerSeverity.Info: - return 'info'; - case MarkerSeverity.Warning: - return 'warning'; - case MarkerSeverity.Error: - return 'error'; - } - return ''; - } } export class RelatedInformationRenderer implements ITreeRenderer { @@ -561,7 +548,9 @@ export class MarkerViewModel extends Disposable { if (model) { if (!this.codeActionsPromise) { this.codeActionsPromise = createCancelablePromise(cancellationToken => { - return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken); + return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken).then(actions => { + return this._register(actions); + }); }); } return this.codeActionsPromise; diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index 7ea45538c776..0d725b51a478 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -160,31 +160,6 @@ .markers-panel .monaco-tl-contents .marker-icon { height: 22px; - width: 16px; -} - -.markers-panel .marker-icon.warning { - background: url('status-warning.svg') center center no-repeat; -} - -.markers-panel .marker-icon.error { - background: url('status-error.svg') center center no-repeat; -} - -.markers-panel .marker-icon.info { - background: url('status-info.svg') center center no-repeat; -} - -.vs-dark .markers-panel .marker-icon.warning { - background: url('status-warning-inverse.svg') center center no-repeat; -} - -.vs-dark .markers-panel .marker-icon.error { - background: url('status-error-inverse.svg') center center no-repeat; -} - -.vs-dark .markers-panel .marker-icon.info { - background: url('status-info-inverse.svg') center center no-repeat; } .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { diff --git a/src/vs/workbench/contrib/markers/browser/media/status-error-inverse.svg b/src/vs/workbench/contrib/markers/browser/media/status-error-inverse.svg deleted file mode 100644 index 3c852a7ffde9..000000000000 --- a/src/vs/workbench/contrib/markers/browser/media/status-error-inverse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/media/status-error.svg b/src/vs/workbench/contrib/markers/browser/media/status-error.svg deleted file mode 100644 index a1ddb39fed6a..000000000000 --- a/src/vs/workbench/contrib/markers/browser/media/status-error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/media/status-info-inverse.svg b/src/vs/workbench/contrib/markers/browser/media/status-info-inverse.svg deleted file mode 100644 index d38c363e0e4c..000000000000 --- a/src/vs/workbench/contrib/markers/browser/media/status-info-inverse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/media/status-info.svg b/src/vs/workbench/contrib/markers/browser/media/status-info.svg deleted file mode 100644 index 6e2e22f67bc9..000000000000 --- a/src/vs/workbench/contrib/markers/browser/media/status-info.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/media/status-warning-inverse.svg b/src/vs/workbench/contrib/markers/browser/media/status-warning-inverse.svg deleted file mode 100644 index df44e61b3265..000000000000 --- a/src/vs/workbench/contrib/markers/browser/media/status-warning-inverse.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/media/status-warning.svg b/src/vs/workbench/contrib/markers/browser/media/status-warning.svg deleted file mode 100644 index f4e2a84b0af4..000000000000 --- a/src/vs/workbench/contrib/markers/browser/media/status-warning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index 105126945849..4e7c54e36cad 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -28,7 +28,7 @@ export default class Messages { public static MARKERS_PANEL_ACTION_TOOLTIP_FILTER: string = nls.localize('markers.panel.action.filter', "Filter Problems"); public static MARKERS_PANEL_ACTION_TOOLTIP_QUICKFIX: string = nls.localize('markers.panel.action.quickfix', "Show fixes"); public static MARKERS_PANEL_FILTER_ARIA_LABEL: string = nls.localize('markers.panel.filter.ariaLabel', "Filter Problems"); - public static MARKERS_PANEL_FILTER_PLACEHOLDER: string = nls.localize('markers.panel.filter.placeholder', "Filter. Eg: text, **/*.ts, !**/node_modules/**"); + public static MARKERS_PANEL_FILTER_PLACEHOLDER: string = nls.localize('markers.panel.filter.placeholder', "Filter. E.g.: text, **/*.ts, !**/node_modules/**"); public static MARKERS_PANEL_FILTER_ERRORS: string = nls.localize('markers.panel.filter.errors', "errors"); public static MARKERS_PANEL_FILTER_WARNINGS: string = nls.localize('markers.panel.filter.warnings', "warnings"); public static MARKERS_PANEL_FILTER_INFOS: string = nls.localize('markers.panel.filter.infos', "infos"); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index db8ee4243661..7301dc8b50f3 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -304,7 +304,7 @@ export class OutlinePanel extends ViewletPanel { this._inputContainer = dom.$('.outline-input'); this._progressBar = new ProgressBar(progressContainer); - this.disposables.push(attachProgressBarStyler(this._progressBar, this._themeService)); + this._register(attachProgressBarStyler(this._progressBar, this._themeService)); let treeContainer = dom.$('.outline-tree'); dom.append( @@ -340,13 +340,13 @@ export class OutlinePanel extends ViewletPanel { }); // feature: filter on type - keep tree and menu in sync - this.disposables.push(this._tree.onDidUpdateOptions(e => { + this._register(this._tree.onDidUpdateOptions(e => { this._outlineViewState.filterOnType = Boolean(e.filterOnType); })); // feature: expand all nodes when filtering (not when finding) let viewState: IDataTreeViewState | undefined; - this.disposables.push(this._tree.onDidChangeTypeFilterPattern(pattern => { + this._register(this._tree.onDidChangeTypeFilterPattern(pattern => { if (!this._tree.options.filterOnType) { return; } @@ -360,13 +360,13 @@ export class OutlinePanel extends ViewletPanel { })); // feature: toggle icons - this.disposables.push(this._configurationService.onDidChangeConfiguration(e => { + this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(OutlineConfigKeys.icons)) { this._tree.updateChildren(); } })); - this.disposables.push(this.onDidChangeBodyVisibility(visible => { + this._register(this.onDidChangeBodyVisibility(visible => { if (visible && !this._requestOracle) { this._requestOracle = this._instantiationService.createInstance(RequestOracle, (editor: ICodeEditor | undefined, event: IModelContentChangedEvent | undefined) => this._doUpdate(editor, event), DocumentSymbolProviderRegistry); } else if (!visible) { @@ -390,20 +390,21 @@ export class OutlinePanel extends ViewletPanel { } getSecondaryActions(): IAction[] { - let group = new RadioGroup([ + const group = this._register(new RadioGroup([ new SimpleToggleAction(this._outlineViewState, localize('sortByPosition', "Sort By: Position"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByPosition, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByPosition), new SimpleToggleAction(this._outlineViewState, localize('sortByName', "Sort By: Name"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByName, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByName), new SimpleToggleAction(this._outlineViewState, localize('sortByKind', "Sort By: Type"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByKind, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByKind), - ]); - let result = [ + ])); + const result = [ new SimpleToggleAction(this._outlineViewState, localize('followCur', "Follow Cursor"), () => this._outlineViewState.followCursor, action => this._outlineViewState.followCursor = action.checked), new SimpleToggleAction(this._outlineViewState, localize('filterOnType', "Filter on Type"), () => this._outlineViewState.filterOnType, action => this._outlineViewState.filterOnType = action.checked), new Separator(), ...group.actions, ]; + for (const r of result) { + this._register(r); + } - this.disposables.push(...result); - this.disposables.push(group); return result; } diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index cd2e5cac4ed3..1f0283df9ad4 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -141,10 +141,10 @@ export class SwitchOutputActionViewItem extends SelectActionViewItem { super(null, action, [], 0, contextViewService, { ariaLabel: nls.localize('outputChannels', 'Output Channels.') }); let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - this.toDispose.push(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions())); - this.toDispose.push(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions())); - this.toDispose.push(this.outputService.onActiveOutputChannel(() => this.updateOtions())); - this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); + this._register(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions())); + this._register(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions())); + this._register(this.outputService.onActiveOutputChannel(() => this.updateOtions())); + this._register(attachSelectBoxStyler(this.selectBox, themeService)); this.updateOtions(); } diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index 42466560e3bd..de0fadca75ca 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -68,7 +68,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo private activeChannelIdInStorage: string; private activeChannel?: OutputChannel; - private readonly _onActiveOutputChannel = new Emitter(); + private readonly _onActiveOutputChannel = this._register(new Emitter()); readonly onActiveOutputChannel: Event = this._onActiveOutputChannel.event; private _outputPanel: OutputPanel; diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index cc5ae9089983..1a86f4e6d1a3 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -20,7 +20,6 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap'; import { mergeSort } from 'vs/base/common/arrays'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import product from 'vs/platform/product/node/product'; import pkg from 'vs/platform/product/node/package'; @@ -73,7 +72,6 @@ class PerfModelContentProvider implements ITextModelContentProvider { @ICodeEditorService private readonly _editorService: ICodeEditorService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @ITimerService private readonly _timerService: ITimerService, - @IEnvironmentService private readonly _envService: IEnvironmentService, @IExtensionService private readonly _extensionService: IExtensionService, ) { } @@ -107,7 +105,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { ]).then(([metrics]) => { if (this._model && !this._model.isDisposed()) { - let stats = this._envService.args['prof-modules'] ? LoaderStats.get() : undefined; + let stats = LoaderStats.get(); let md = new MarkdownBuilder(); this._addSummary(md, metrics); md.blank(); @@ -118,6 +116,8 @@ class PerfModelContentProvider implements ITextModelContentProvider { this._addRawPerfMarks(md); md.blank(); this._addLoaderStats(md, stats); + md.blank(); + this._addCachedDataStats(md); this._model.setValue(md.value); } @@ -136,7 +136,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { md.li(`Memory(System): ${(metrics.totalmem / (1024 * 1024 * 1024)).toFixed(2)} GB(${(metrics.freemem / (1024 * 1024 * 1024)).toFixed(2)}GB free)`); } if (metrics.meminfo) { - md.li(`Memory(Process): ${(metrics.meminfo.workingSetSize / 1024).toFixed(2)} MB working set(${(metrics.meminfo.peakWorkingSetSize / 1024).toFixed(2)}MB peak, ${(metrics.meminfo.privateBytes / 1024).toFixed(2)}MB private, ${(metrics.meminfo.sharedBytes / 1024).toFixed(2)}MB shared)`); + md.li(`Memory(Process): ${(metrics.meminfo.workingSetSize / 1024).toFixed(2)} MB working set(${(metrics.meminfo.privateBytes / 1024).toFixed(2)}MB private, ${(metrics.meminfo.sharedBytes / 1024).toFixed(2)}MB shared)`); } md.li(`VM(likelyhood): ${metrics.isVMLikelyhood}%`); md.li(`Initial Startup: ${metrics.initialStartup}`); @@ -210,21 +210,54 @@ class PerfModelContentProvider implements ITextModelContentProvider { md.value += '```\n'; } - private _addLoaderStats(md: MarkdownBuilder, stats?: LoaderStats): void { - if (stats) { - md.heading(2, 'Loader Stats'); - md.heading(3, 'Load AMD-module'); - md.table(['Module', 'Duration'], stats.amdLoad); - md.blank(); - md.heading(3, 'Load commonjs-module'); - md.table(['Module', 'Duration'], stats.nodeRequire); - md.blank(); - md.heading(3, 'Invoke AMD-module factory'); - md.table(['Module', 'Duration'], stats.amdInvoke); - md.blank(); - md.heading(3, 'Invoke commonjs-module'); - md.table(['Module', 'Duration'], stats.nodeEval); + private _addLoaderStats(md: MarkdownBuilder, stats: LoaderStats): void { + md.heading(2, 'Loader Stats'); + md.heading(3, 'Load AMD-module'); + md.table(['Module', 'Duration'], stats.amdLoad); + md.blank(); + md.heading(3, 'Load commonjs-module'); + md.table(['Module', 'Duration'], stats.nodeRequire); + md.blank(); + md.heading(3, 'Invoke AMD-module factory'); + md.table(['Module', 'Duration'], stats.amdInvoke); + md.blank(); + md.heading(3, 'Invoke commonjs-module'); + md.table(['Module', 'Duration'], stats.nodeEval); + } + + private _addCachedDataStats(md: MarkdownBuilder): void { + + const map = new Map(); + map.set(LoaderEventType.CachedDataCreated, []); + map.set(LoaderEventType.CachedDataFound, []); + map.set(LoaderEventType.CachedDataMissed, []); + map.set(LoaderEventType.CachedDataRejected, []); + for (const stat of require.getStats()) { + if (map.has(stat.type)) { + map.get(stat.type)!.push(stat.detail); + } } + + const printLists = (arr?: string[]) => { + if (arr) { + arr.sort(); + for (const e of arr) { + md.li(`${e}`); + } + md.blank(); + } + }; + + md.heading(2, 'Node Cached Data Stats'); + md.blank(); + md.heading(3, 'cached data used'); + printLists(map.get(LoaderEventType.CachedDataFound)); + md.heading(3, 'cached data missed'); + printLists(map.get(LoaderEventType.CachedDataMissed)); + md.heading(3, 'cached data rejected'); + printLists(map.get(LoaderEventType.CachedDataRejected)); + md.heading(3, 'cached data created (lazy, might need refreshes)'); + printLists(map.get(LoaderEventType.CachedDataCreated)); } } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 2b253a63b93b..75d7399be81b 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; import { OS } from 'vs/base/common/platform'; -import { dispose, Disposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, toDisposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { CheckboxActionViewItem } from 'vs/base/browser/ui/checkbox/checkbox'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; @@ -804,7 +804,7 @@ class KeybindingItemRenderer implements IListRenderer element); this.keybindingsEditor.layoutColumns(elements); @@ -814,7 +814,7 @@ class KeybindingItemRenderer implements IListRenderer dispose(disposables)) + disposable: disposables }; } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index 1847e7378a11..4324c3b83b7a 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -23,11 +23,13 @@ import { parseTree, Node } from 'vs/base/common/json'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { WindowsNativeResolvedKeybinding } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper'; -import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService'; +import { themeColorFromId, ThemeColor, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { overviewRulerInfo, overviewRulerError } from 'vs/editor/common/view/editorColorRegistry'; import { IModelDeltaDecoration, ITextModel, TrackedRangeStickiness, OverviewRulerLane } from 'vs/editor/common/model'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; +import Severity from 'vs/base/common/severity'; +import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; const NLS_LAUNCH_MESSAGE = nls.localize('defineKeybinding.start', "Define Keybinding"); const NLS_KB_LAYOUT_ERROR_MESSAGE = nls.localize('defineKeybinding.kbLayoutErrorMessage', "You won't be able to produce this key combination under your current keyboard layout."); @@ -400,3 +402,8 @@ function isInterestingEditorModel(editor: ICodeEditor): boolean { registerEditorContribution(DefineKeybindingController); registerEditorCommand(new DefineKeybindingCommand()); + +registerThemingParticipant((theme, collector) => { + collector.addRule(`.monaco-editor .inlineKeybindingInfo:before { background: url("data:image/svg+xml,${SeverityIcon.getSVGData(Severity.Info, theme)}") -0.1em -0.2em no-repeat; }`); + collector.addRule(`.monaco-editor .inlineKeybindingError:before { background: url("data:image/svg+xml,${SeverityIcon.getSVGData(Severity.Error, theme)}") -0.1em -0.2em no-repeat; }`); +}); \ No newline at end of file diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/check-inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/check-inverse.svg similarity index 100% rename from src/vs/workbench/contrib/preferences/electron-browser/media/check-inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/check-inverse.svg diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/check.svg b/src/vs/workbench/contrib/preferences/browser/media/check.svg similarity index 100% rename from src/vs/workbench/contrib/preferences/electron-browser/media/check.svg rename to src/vs/workbench/contrib/preferences/browser/media/check.svg diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/configure-inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/configure-inverse.svg similarity index 100% rename from src/vs/workbench/contrib/preferences/electron-browser/media/configure-inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/configure-inverse.svg diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/configure.svg b/src/vs/workbench/contrib/preferences/browser/media/configure.svg similarity index 100% rename from src/vs/workbench/contrib/preferences/electron-browser/media/configure.svg rename to src/vs/workbench/contrib/preferences/browser/media/configure.svg diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/edit-json-inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/edit-json-inverse.svg similarity index 100% rename from src/vs/workbench/contrib/preferences/electron-browser/media/edit-json-inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/edit-json-inverse.svg diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/edit-json.svg b/src/vs/workbench/contrib/preferences/browser/media/edit-json.svg similarity index 100% rename from src/vs/workbench/contrib/preferences/electron-browser/media/edit-json.svg rename to src/vs/workbench/contrib/preferences/browser/media/edit-json.svg diff --git a/src/vs/workbench/contrib/preferences/browser/media/info.svg b/src/vs/workbench/contrib/preferences/browser/media/info.svg deleted file mode 100644 index 6578b81ea3ff..000000000000 --- a/src/vs/workbench/contrib/preferences/browser/media/info.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/preferences/browser/media/keybindings.css b/src/vs/workbench/contrib/preferences/browser/media/keybindings.css index c523a5920fb5..935d90fa26e4 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/keybindings.css +++ b/src/vs/workbench/contrib/preferences/browser/media/keybindings.css @@ -47,7 +47,6 @@ display:inline-block; height:0.8em; width:1em; - background: url(info.svg) 0px -0.1em no-repeat; background-size: 0.9em; } @@ -57,7 +56,6 @@ display:inline-block; height:0.8em; width:1em; - background: url(status-error.svg) 0px -0.1em no-repeat; background-size: 1em; } diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/preferences-editor-inverse.svg similarity index 100% rename from src/vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/preferences-editor-inverse.svg diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg b/src/vs/workbench/contrib/preferences/browser/media/preferences-editor.svg similarity index 100% rename from src/vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg rename to src/vs/workbench/contrib/preferences/browser/media/preferences-editor.svg diff --git a/src/vs/workbench/contrib/preferences/browser/media/preferences.css b/src/vs/workbench/contrib/preferences/browser/media/preferences.css index 9e5f46e3b189..3c36b41cb29d 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/preferences.css +++ b/src/vs/workbench/contrib/preferences/browser/media/preferences.css @@ -236,15 +236,6 @@ background: url('edit_inverse.svg') center center no-repeat; } -.monaco-editor .unsupportedWorkbenhSettingInfo:before { - content:" "; - display:inline-block; - height:1em; - width:1em; - background: url(info.svg) 50% 50% no-repeat; - background-size: 0.9em; -} - .monaco-editor .dim-configuration { color: #b1b1b1; } diff --git a/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css similarity index 100% rename from src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css rename to src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css diff --git a/src/vs/workbench/contrib/preferences/browser/media/status-error.svg b/src/vs/workbench/contrib/preferences/browser/media/status-error.svg deleted file mode 100644 index a1ddb39fed6a..000000000000 --- a/src/vs/workbench/contrib/preferences/browser/media/status-error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts similarity index 93% rename from src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts rename to src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 36dbbbd5409b..39e4e9860862 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -16,7 +16,6 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { WorkbenchStateContext, RemoteAuthorityContext } from 'vs/workbench/browser/contextkeys'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -30,10 +29,9 @@ import { ResourceContextKey } from 'vs/workbench/common/resources'; import { KeybindingsEditor } from 'vs/workbench/contrib/preferences/browser/keybindingsEditor'; import { ConfigureLanguageBasedSettingsAction, OpenDefaultKeybindingsFileAction, OpenFolderSettingsAction, OpenGlobalKeybindingsAction, OpenGlobalKeybindingsFileAction, OpenGlobalSettingsAction, OpenRawDefaultSettingsAction, OpenSettings2Action, OpenSettingsJsonAction, OpenWorkspaceSettingsAction, OPEN_FOLDER_SETTINGS_COMMAND, OPEN_FOLDER_SETTINGS_LABEL, OpenRemoteSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { PreferencesEditor } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; -import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IKeybindingsEditor, IPreferencesSearchService, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, SETTINGS_COMMAND_OPEN_SETTINGS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IKeybindingsEditor, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, SETTINGS_COMMAND_OPEN_SETTINGS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN } from 'vs/workbench/contrib/preferences/common/preferences'; import { PreferencesContribution } from 'vs/workbench/contrib/preferences/common/preferencesContribution'; -import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/electron-browser/preferencesSearch'; -import { SettingsEditor2 } from 'vs/workbench/contrib/preferences/electron-browser/settingsEditor2'; +import { SettingsEditor2 } from 'vs/workbench/contrib/preferences/browser/settingsEditor2'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; @@ -42,8 +40,6 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); - Registry.as(EditorExtensions.Editors).registerEditor( new EditorDescriptor( PreferencesEditor, @@ -385,8 +381,8 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: OpenGlobalKeybindingsAction.ID, title: OpenGlobalKeybindingsAction.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/preferences-editor.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/preferences-editor-inverse.svg`)) } }, when: ResourceContextKey.Resource.isEqualTo(URI.file(environmentService.appKeybindingsPath).toString()), @@ -401,11 +397,11 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: commandId, title: OpenSettings2Action.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/preferences-editor.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/preferences-editor-inverse.svg`)) } }, - when: ResourceContextKey.Resource.isEqualTo(URI.file(environmentService.appSettingsPath).toString()), + when: ResourceContextKey.Resource.isEqualTo(environmentService.settingsResource.toString()), group: 'navigation', order: 1 }); @@ -442,8 +438,8 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: commandId, title: OpenSettings2Action.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/preferences-editor.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/preferences-editor-inverse.svg`)) } }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.workspaceSettingsResource!.toString()), WorkbenchStateContext.isEqualTo('workspace')), @@ -470,8 +466,8 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: commandId, title: OpenSettings2Action.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/preferences-editor.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/preferences-editor-inverse.svg`)) } }, when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.getFolderSettingsResource(folder.uri)!.toString())), @@ -537,8 +533,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: OpenGlobalKeybindingsFileAction.ID, title: OpenGlobalKeybindingsFileAction.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/edit-json.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/edit-json-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/edit-json.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/browser/media/edit-json-inverse.svg`)) } }, when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), @@ -775,8 +771,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, title: nls.localize('openSettingsJson', "Open Settings (JSON)"), iconLocation: { - dark: URI.parse(require.toUrl('vs/workbench/contrib/preferences/electron-browser/media/edit-json-inverse.svg')), - light: URI.parse(require.toUrl('vs/workbench/contrib/preferences/electron-browser/media/edit-json.svg')) + dark: URI.parse(require.toUrl('vs/workbench/contrib/preferences/browser/media/edit-json-inverse.svg')), + light: URI.parse(require.toUrl('vs/workbench/contrib/preferences/browser/media/edit-json.svg')) } }, group: 'navigation', diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index c3f19e77a370..e428e6df327b 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -164,7 +164,7 @@ export class OpenWorkspaceSettingsAction extends Action { static readonly ID = 'workbench.action.openWorkspaceSettings'; static readonly LABEL = nls.localize('openWorkspaceSettings', "Open Workspace Settings"); - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( id: string, @@ -174,7 +174,7 @@ export class OpenWorkspaceSettingsAction extends Action { ) { super(id, label); this.update(); - this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables); + this.disposables.add(this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this)); } private update(): void { @@ -186,7 +186,7 @@ export class OpenWorkspaceSettingsAction extends Action { } dispose(): void { - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index b062bfec4f13..f12f2fd0d425 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -31,7 +31,7 @@ import { ConfigurationTarget } from 'vs/platform/configuration/common/configurat import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -84,7 +84,7 @@ export class PreferencesEditor extends BaseEditor { readonly minimumHeight = 260; - private _onDidCreateWidget = new Emitter<{ width: number; height: number; } | undefined>(); + private _onDidCreateWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>()); readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined> = this._onDidCreateWidget.event; constructor( @@ -94,7 +94,7 @@ export class PreferencesEditor extends BaseEditor { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @IProgressService private readonly progressService: IProgressService, + @ILocalProgressService private readonly progressService: ILocalProgressService, @IStorageService storageService: IStorageService ) { super(PreferencesEditor.ID, telemetryService, themeService, storageService); @@ -330,11 +330,6 @@ export class PreferencesEditor extends BaseEditor { this._lastReportedFilter = filter; } } - - dispose(): void { - this._onDidCreateWidget.dispose(); - super.dispose(); - } } class SettingsNavigator extends ArrayNavigator { @@ -774,10 +769,10 @@ class SideBySidePreferencesWidget extends Widget { private settingsTargetsWidget: SettingsTargetsWidget; - private readonly _onFocus = new Emitter(); + private readonly _onFocus = this._register(new Emitter()); readonly onFocus: Event = this._onFocus.event; - private readonly _onDidSettingsTargetChange = new Emitter(); + private readonly _onDidSettingsTargetChange = this._register(new Emitter()); readonly onDidSettingsTargetChange: Event = this._onDidSettingsTargetChange.event; private splitview: SplitView; diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index 98a8d5505eae..5982c1c5d01b 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -56,13 +56,13 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend private modelChangeDelayer: Delayer = new Delayer(200); private associatedPreferencesModel: IPreferencesEditorModel; - private readonly _onFocusPreference = new Emitter(); + private readonly _onFocusPreference = this._register(new Emitter()); readonly onFocusPreference: Event = this._onFocusPreference.event; - private readonly _onClearFocusPreference = new Emitter(); + private readonly _onClearFocusPreference = this._register(new Emitter()); readonly onClearFocusPreference: Event = this._onClearFocusPreference.event; - private readonly _onUpdatePreference: Emitter<{ key: string, value: any, source: IIndexedSetting }> = new Emitter<{ key: string, value: any, source: IIndexedSetting }>(); + private readonly _onUpdatePreference = this._register(new Emitter<{ key: string, value: any, source: IIndexedSetting }>()); readonly onUpdatePreference: Event<{ key: string, value: any, source: IIndexedSetting }> = this._onUpdatePreference.event; private filterResult: IFilterResult | undefined; @@ -233,13 +233,13 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR private bracesHidingRenderer: BracesHidingRenderer; private filterResult: IFilterResult | undefined; - private readonly _onUpdatePreference: Emitter<{ key: string, value: any, source: IIndexedSetting }> = new Emitter<{ key: string, value: any, source: IIndexedSetting }>(); + private readonly _onUpdatePreference = this._register(new Emitter<{ key: string, value: any, source: IIndexedSetting }>()); readonly onUpdatePreference: Event<{ key: string, value: any, source: IIndexedSetting }> = this._onUpdatePreference.event; - private readonly _onFocusPreference = new Emitter(); + private readonly _onFocusPreference = this._register(new Emitter()); readonly onFocusPreference: Event = this._onFocusPreference.event; - private readonly _onClearFocusPreference = new Emitter(); + private readonly _onClearFocusPreference = this._register(new Emitter()); readonly onClearFocusPreference: Event = this._onClearFocusPreference.event; constructor(protected editor: ICodeEditor, readonly preferencesModel: DefaultSettingsEditorModel, @@ -436,13 +436,13 @@ class DefaultSettingsHeaderRenderer extends Disposable { export class SettingsGroupTitleRenderer extends Disposable implements HiddenAreasProvider { - private readonly _onHiddenAreasChanged = new Emitter(); + private readonly _onHiddenAreasChanged = this._register(new Emitter()); get onHiddenAreasChanged(): Event { return this._onHiddenAreasChanged.event; } private settingsGroups: ISettingsGroup[]; private hiddenGroups: ISettingsGroup[] = []; private settingsGroupTitleWidgets: SettingsGroupTitleWidget[]; - private disposables: IDisposable[] = []; + private renderDisposables: IDisposable[] = []; constructor(private editor: ICodeEditor, @IInstantiationService private readonly instantiationService: IInstantiationService @@ -474,8 +474,8 @@ export class SettingsGroupTitleRenderer extends Disposable implements HiddenArea const settingsGroupTitleWidget = this.instantiationService.createInstance(SettingsGroupTitleWidget, this.editor, group); settingsGroupTitleWidget.render(); this.settingsGroupTitleWidgets.push(settingsGroupTitleWidget); - this.disposables.push(settingsGroupTitleWidget); - this.disposables.push(settingsGroupTitleWidget.onToggled(collapsed => this.onToggled(collapsed, settingsGroupTitleWidget.settingsGroup))); + this.renderDisposables.push(settingsGroupTitleWidget); + this.renderDisposables.push(settingsGroupTitleWidget.onToggled(collapsed => this.onToggled(collapsed, settingsGroupTitleWidget.settingsGroup))); } this.settingsGroupTitleWidgets.reverse(); } @@ -515,7 +515,7 @@ export class SettingsGroupTitleRenderer extends Disposable implements HiddenArea private disposeWidgets() { this.hiddenGroups = []; - this.disposables = dispose(this.disposables); + this.renderDisposables = dispose(this.renderDisposables); } dispose() { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts new file mode 100644 index 000000000000..bc087bc134c0 --- /dev/null +++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -0,0 +1,219 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ISettingsEditorModel, ISetting, ISettingsGroup, ISearchResult, IGroupFilter } from 'vs/workbench/services/preferences/common/preferences'; +import { IRange } from 'vs/editor/common/core/range'; +import { distinct } from 'vs/base/common/arrays'; +import * as strings from 'vs/base/common/strings'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IMatch, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IPreferencesSearchService, ISearchProvider } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CancellationToken } from 'vs/base/common/cancellation'; + +export interface IEndpointDetails { + urlBase?: string; + key?: string; +} + +export class PreferencesSearchService extends Disposable implements IPreferencesSearchService { + _serviceBrand: any; + + constructor( + @IInstantiationService protected readonly instantiationService: IInstantiationService, + ) { + super(); + } + + getRemoteSearchProvider(filter: string, newExtensionsOnly = false): ISearchProvider | undefined { + return undefined; + } + + getLocalSearchProvider(filter: string): LocalSearchProvider { + return this.instantiationService.createInstance(LocalSearchProvider, filter); + } +} + +export class LocalSearchProvider implements ISearchProvider { + static readonly EXACT_MATCH_SCORE = 10000; + static readonly START_SCORE = 1000; + + constructor(private _filter: string) { + // Remove " and : which are likely to be copypasted as part of a setting name. + // Leave other special characters which the user might want to search for. + this._filter = this._filter + .replace(/[":]/g, ' ') + .replace(/ /g, ' ') + .trim(); + } + + searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise { + if (!this._filter) { + return Promise.resolve(null); + } + + let orderedScore = LocalSearchProvider.START_SCORE; // Sort is not stable + const settingMatcher = (setting: ISetting) => { + const matches = new SettingMatches(this._filter, setting, true, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches; + const score = this._filter === setting.key ? + LocalSearchProvider.EXACT_MATCH_SCORE : + orderedScore--; + + return matches && matches.length ? + { + matches, + score + } : + null; + }; + + const filterMatches = preferencesModel.filterSettings(this._filter, this.getGroupFilter(this._filter), settingMatcher); + if (filterMatches[0] && filterMatches[0].score === LocalSearchProvider.EXACT_MATCH_SCORE) { + return Promise.resolve({ + filterMatches: filterMatches.slice(0, 1), + exactMatch: true + }); + } else { + return Promise.resolve({ + filterMatches + }); + } + } + + private getGroupFilter(filter: string): IGroupFilter { + const regex = strings.createRegExp(filter, false, { global: true }); + return (group: ISettingsGroup) => { + return regex.test(group.title); + }; + } +} + +export class SettingMatches { + + private readonly descriptionMatchingWords: Map = new Map(); + private readonly keyMatchingWords: Map = new Map(); + private readonly valueMatchingWords: Map = new Map(); + + readonly matches: IRange[]; + + constructor(searchString: string, setting: ISetting, private requireFullQueryMatch: boolean, private searchDescription: boolean, private valuesMatcher: (filter: string, setting: ISetting) => IRange[]) { + this.matches = distinct(this._findMatchesInSetting(searchString, setting), (match) => `${match.startLineNumber}_${match.startColumn}_${match.endLineNumber}_${match.endColumn}_`); + } + + private _findMatchesInSetting(searchString: string, setting: ISetting): IRange[] { + const result = this._doFindMatchesInSetting(searchString, setting); + if (setting.overrides && setting.overrides.length) { + for (const subSetting of setting.overrides) { + const subSettingMatches = new SettingMatches(searchString, subSetting, this.requireFullQueryMatch, this.searchDescription, this.valuesMatcher); + const words = searchString.split(' '); + const descriptionRanges: IRange[] = this.getRangesForWords(words, this.descriptionMatchingWords, [subSettingMatches.descriptionMatchingWords, subSettingMatches.keyMatchingWords, subSettingMatches.valueMatchingWords]); + const keyRanges: IRange[] = this.getRangesForWords(words, this.keyMatchingWords, [subSettingMatches.descriptionMatchingWords, subSettingMatches.keyMatchingWords, subSettingMatches.valueMatchingWords]); + const subSettingKeyRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.keyMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.valueMatchingWords]); + const subSettinValueRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.valueMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.keyMatchingWords]); + result.push(...descriptionRanges, ...keyRanges, ...subSettingKeyRanges, ...subSettinValueRanges); + result.push(...subSettingMatches.matches); + } + } + return result; + } + + private _doFindMatchesInSetting(searchString: string, setting: ISetting): IRange[] { + const registry: { [qualifiedKey: string]: IJSONSchema } = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const schema: IJSONSchema = registry[setting.key]; + + const words = searchString.split(' '); + const settingKeyAsWords: string = setting.key.split('.').join(' '); + + for (const word of words) { + if (this.searchDescription) { + for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) { + const descriptionMatches = matchesWords(word, setting.description[lineIndex], true); + if (descriptionMatches) { + this.descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex))); + } + } + } + + const keyMatches = or(matchesWords, matchesCamelCase)(word, settingKeyAsWords); + if (keyMatches) { + this.keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match))); + } + + const valueMatches = typeof setting.value === 'string' ? matchesContiguousSubString(word, setting.value) : null; + if (valueMatches) { + this.valueMatchingWords.set(word, valueMatches.map(match => this.toValueRange(setting, match))); + } else if (schema && schema.enum && schema.enum.some(enumValue => typeof enumValue === 'string' && !!matchesContiguousSubString(word, enumValue))) { + this.valueMatchingWords.set(word, []); + } + } + + const descriptionRanges: IRange[] = []; + if (this.searchDescription) { + for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) { + const matches = or(matchesContiguousSubString)(searchString, setting.description[lineIndex] || '') || []; + descriptionRanges.push(...matches.map(match => this.toDescriptionRange(setting, match, lineIndex))); + } + if (descriptionRanges.length === 0) { + descriptionRanges.push(...this.getRangesForWords(words, this.descriptionMatchingWords, [this.keyMatchingWords, this.valueMatchingWords])); + } + } + + const keyMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.key); + const keyRanges: IRange[] = keyMatches ? keyMatches.map(match => this.toKeyRange(setting, match)) : this.getRangesForWords(words, this.keyMatchingWords, [this.descriptionMatchingWords, this.valueMatchingWords]); + + let valueRanges: IRange[] = []; + if (setting.value && typeof setting.value === 'string') { + const valueMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.value); + valueRanges = valueMatches ? valueMatches.map(match => this.toValueRange(setting, match)) : this.getRangesForWords(words, this.valueMatchingWords, [this.keyMatchingWords, this.descriptionMatchingWords]); + } else { + valueRanges = this.valuesMatcher ? this.valuesMatcher(searchString, setting) : []; + } + + return [...descriptionRanges, ...keyRanges, ...valueRanges]; + } + + private getRangesForWords(words: string[], from: Map, others: Map[]): IRange[] { + const result: IRange[] = []; + for (const word of words) { + const ranges = from.get(word); + if (ranges) { + result.push(...ranges); + } else if (this.requireFullQueryMatch && others.every(o => !o.has(word))) { + return []; + } + } + return result; + } + + private toKeyRange(setting: ISetting, match: IMatch): IRange { + return { + startLineNumber: setting.keyRange.startLineNumber, + startColumn: setting.keyRange.startColumn + match.start, + endLineNumber: setting.keyRange.startLineNumber, + endColumn: setting.keyRange.startColumn + match.end + }; + } + + private toDescriptionRange(setting: ISetting, match: IMatch, lineIndex: number): IRange { + return { + startLineNumber: setting.descriptionRanges[lineIndex].startLineNumber, + startColumn: setting.descriptionRanges[lineIndex].startColumn + match.start, + endLineNumber: setting.descriptionRanges[lineIndex].endLineNumber, + endColumn: setting.descriptionRanges[lineIndex].startColumn + match.end + }; + } + + private toValueRange(setting: ISetting, match: IMatch): IRange { + return { + startLineNumber: setting.valueRange.startLineNumber, + startColumn: setting.valueRange.startColumn + match.start + 1, + endLineNumber: setting.valueRange.startLineNumber, + endColumn: setting.valueRange.startColumn + match.end + 1 + }; + } +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 87b297db1457..e336c159b31c 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -484,7 +484,7 @@ export class SettingsTargetsWidget extends Widget { private _settingsTarget: SettingsTarget; - private readonly _onDidTargetChange = new Emitter(); + private readonly _onDidTargetChange = this._register(new Emitter()); readonly onDidTargetChange: Event = this._onDidTargetChange.event; constructor( @@ -517,7 +517,7 @@ export class SettingsTargetsWidget extends Widget { const remoteAuthority = this.environmentService.configuration.remoteAuthority; const hostLabel = remoteAuthority && this.labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority); const remoteSettingsLabel = localize('userSettingsRemote', "Remote") + - (hostLabel ? ` (${hostLabel})` : ''); + (hostLabel ? ` [${hostLabel}]` : ''); this.userRemoteSettings = new Action('userSettingsRemote', remoteSettingsLabel, '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE)); this.userRemoteSettings.tooltip = this.userRemoteSettings.label; @@ -751,7 +751,7 @@ export class EditPreferenceWidget extends Disposable { private _editPreferenceDecoration: string[]; - private readonly _onClick = new Emitter(); + private readonly _onClick = this._register(new Emitter()); get onClick(): Event { return this._onClick.event; } constructor(private editor: ICodeEditor diff --git a/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts similarity index 99% rename from src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts rename to src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 4058ac0ba653..775cecadca73 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; import * as arrays from 'vs/base/common/arrays'; -import { Delayer, ThrottledDelayer } from 'vs/base/common/async'; +import { Delayer, ThrottledDelayer, timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import * as collections from 'vs/base/common/collections'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; @@ -211,7 +211,7 @@ export class SettingsEditor2 extends BaseEditor { setInput(input: SettingsEditor2Input, options: SettingsEditorOptions | null, token: CancellationToken): Promise { this.inSettingsEditorContextKey.set(true); return super.setInput(input, options, token) - .then(() => new Promise(process.nextTick)) // Force setInput to be async + .then(() => timeout(0)) // Force setInput to be async .then(() => { return this.render(token); }) @@ -251,10 +251,12 @@ export class SettingsEditor2 extends BaseEditor { } } - setOptions(options: SettingsEditorOptions): void { + setOptions(options: SettingsEditorOptions | null): void { super.setOptions(options); - this._setOptions(options); + if (options) { + this._setOptions(options); + } } private _setOptions(options: SettingsEditorOptions): void { @@ -565,7 +567,7 @@ export class SettingsEditor2 extends BaseEditor { } private createTOC(parent: HTMLElement): void { - this.tocTreeModel = new TOCTreeModel(this.viewState); + this.tocTreeModel = this.instantiationService.createInstance(TOCTreeModel, this.viewState); this.tocTreeContainer = DOM.append(parent, $('.settings-toc-container')); this.tocTree = this._register(this.instantiationService.createInstance(TOCTree, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index b75e24023957..04aa9020d609 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -25,7 +25,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ISpliceable } from 'vs/base/common/sequence'; import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -45,6 +45,7 @@ import { ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElemen import { ExcludeSettingWidget, IExcludeChangeEvent, IExcludeDataItem, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const $ = DOM.$; @@ -249,7 +250,7 @@ export interface ISettingOverrideClickEvent { targetKey: string; } -export abstract class AbstractSettingRenderer implements ITreeRenderer { +export abstract class AbstractSettingRenderer extends Disposable implements ITreeRenderer { /** To override */ abstract get templateId(): string; @@ -261,19 +262,19 @@ export abstract class AbstractSettingRenderer implements ITreeRenderer(); + private readonly _onDidClickOverrideElement = this._register(new Emitter()); readonly onDidClickOverrideElement: Event = this._onDidClickOverrideElement.event; - protected readonly _onDidChangeSetting = new Emitter(); + protected readonly _onDidChangeSetting = this._register(new Emitter()); readonly onDidChangeSetting: Event = this._onDidChangeSetting.event; - protected readonly _onDidOpenSettings = new Emitter(); + protected readonly _onDidOpenSettings = this._register(new Emitter()); readonly onDidOpenSettings: Event = this._onDidOpenSettings.event; - private readonly _onDidClickSettingLink = new Emitter(); + private readonly _onDidClickSettingLink = this._register(new Emitter()); readonly onDidClickSettingLink: Event = this._onDidClickSettingLink.event; - private readonly _onDidFocusSetting = new Emitter(); + private readonly _onDidFocusSetting = this._register(new Emitter()); readonly onDidFocusSetting: Event = this._onDidFocusSetting.event; // Put common injections back here @@ -287,6 +288,7 @@ export abstract class AbstractSettingRenderer implements ITreeRenderer { + toDispose.add(checkbox); + toDispose.add(checkbox.onChange(() => { if (template.onChange) { template.onChange(checkbox.checked); } @@ -951,7 +953,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre // Need to listen for mouse clicks on description and toggle checkbox - use target ID for safety // Also have to ignore embedded links - too buried to stop propagation - toDispose.push(DOM.addDisposableListener(descriptionElement, DOM.EventType.MOUSE_DOWN, (e) => { + toDispose.add(DOM.addDisposableListener(descriptionElement, DOM.EventType.MOUSE_DOWN, (e) => { const targetElement = e.target; const targetId = descriptionElement.getAttribute('checkbox_label_target_id'); @@ -968,10 +970,10 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre checkbox.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS); const toolbarContainer = DOM.append(container, $('.setting-toolbar-container')); const toolbar = this.renderSettingToolbar(toolbarContainer); - toDispose.push(toolbar); + toDispose.add(toolbar); const template: ISettingBoolItemTemplate = { - toDispose, + toDispose: [toDispose], containerElement: container, categoryElement, @@ -987,16 +989,16 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre this.addSettingElementFocusHandler(template); // Prevent clicks from being handled by list - toDispose.push(DOM.addDisposableListener(controlElement, 'mousedown', (e: IMouseEvent) => e.stopPropagation())); + toDispose.add(DOM.addDisposableListener(controlElement, 'mousedown', (e: IMouseEvent) => e.stopPropagation())); - toDispose.push(DOM.addStandardDisposableListener(controlElement, 'keydown', (e: StandardKeyboardEvent) => { + toDispose.add(DOM.addStandardDisposableListener(controlElement, 'keydown', (e: StandardKeyboardEvent) => { if (e.keyCode === KeyCode.Escape) { e.browserEvent.stopPropagation(); } })); - toDispose.push(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_ENTER, e => container.classList.add('mouseover'))); - toDispose.push(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_LEAVE, e => container.classList.remove('mouseover'))); + toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_ENTER, e => container.classList.add('mouseover'))); + toDispose.add(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_LEAVE, e => container.classList.remove('mouseover'))); return template; } @@ -1163,6 +1165,7 @@ function escapeInvisibleChars(enumValue: string): string { export class SettingsTreeFilter implements ITreeFilter { constructor( private viewState: ISettingsEditorViewState, + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, ) { } filter(element: SettingsTreeElement, parentVisibility: TreeVisibility): TreeFilterResult { @@ -1175,7 +1178,8 @@ export class SettingsTreeFilter implements ITreeFilter { // Non-user scope selected if (element instanceof SettingsTreeSettingElement && this.viewState.settingsTarget !== ConfigurationTarget.USER_LOCAL) { - if (!element.matchesScope(this.viewState.settingsTarget)) { + const isRemote = !!this.environmentService.configuration.remoteAuthority; + if (!element.matchesScope(this.viewState.settingsTarget, isRemote)) { return false; } } @@ -1303,7 +1307,8 @@ export class SettingsTree extends ObjectTree { container: HTMLElement, viewState: ISettingsEditorViewState, renderers: ITreeRenderer[], - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService, ) { const treeClass = 'settings-editor-tree'; @@ -1320,7 +1325,7 @@ export class SettingsTree extends ObjectTree { } }, styleController: new DefaultStyleController(DOM.createStyleSheet(container), treeClass), - filter: new SettingsTreeFilter(viewState) + filter: instantiationService.createInstance(SettingsTreeFilter, viewState) }); this.disposables = []; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 08d0d16094a7..1b2f01170814 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -14,6 +14,7 @@ import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/prefere import { ITOCEntry, knownAcronyms, knownTermMappings } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices'; @@ -221,7 +222,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { } } - matchesScope(scope: SettingsTarget): boolean { + matchesScope(scope: SettingsTarget, isRemote: boolean): boolean { const configTarget = URI.isUri(scope) ? ConfigurationTarget.WORKSPACE_FOLDER : scope; if (configTarget === ConfigurationTarget.WORKSPACE_FOLDER) { @@ -236,6 +237,10 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { return this.setting.scope === ConfigurationScope.MACHINE || this.setting.scope === ConfigurationScope.WINDOW || this.setting.scope === ConfigurationScope.RESOURCE; } + if (configTarget === ConfigurationTarget.USER_LOCAL && isRemote) { + return this.setting.scope !== ConfigurationScope.MACHINE; + } + return true; } @@ -479,7 +484,8 @@ export class SearchResultModel extends SettingsTreeModel { constructor( viewState: ISettingsEditorViewState, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, ) { super(viewState, configurationService); this.update({ id: 'searchResultModel', label: '' }); @@ -537,8 +543,9 @@ export class SearchResultModel extends SettingsTreeModel { }); // Save time, filter children in the search model instead of relying on the tree filter, which still requires heights to be calculated. + const isRemote = !!this.environmentService.configuration.remoteAuthority; this.root.children = this.root.children - .filter(child => child instanceof SettingsTreeSettingElement && child.matchesAllTags(this._viewState.tagFilters) && child.matchesScope(this._viewState.settingsTarget) && child.matchesAnyExtension(this._viewState.extensionFilters)); + .filter(child => child instanceof SettingsTreeSettingElement && child.matchesAllTags(this._viewState.tagFilters) && child.matchesScope(this._viewState.settingsTarget, isRemote) && child.matchesAnyExtension(this._viewState.extensionFilters)); if (this.newExtensionSearchResults && this.newExtensionSearchResults.filterMatches.length) { const newExtElement = new SettingsTreeNewExtensionsElement(); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index e0b861c5247d..57708110af20 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -203,7 +203,7 @@ export class ExcludeSettingWidget extends Disposable { private model = new ExcludeSettingListModel(); - private readonly _onDidChangeExclude = new Emitter(); + private readonly _onDidChangeExclude = this._register(new Emitter()); readonly onDidChangeExclude: Event = this._onDidChangeExclude.event; get domNode(): HTMLElement { diff --git a/src/vs/workbench/contrib/preferences/browser/tocTree.ts b/src/vs/workbench/contrib/preferences/browser/tocTree.ts index 000197065a8e..891b6a410887 100644 --- a/src/vs/workbench/contrib/preferences/browser/tocTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/tocTree.ts @@ -17,6 +17,7 @@ import { SettingsTreeFilter } from 'vs/workbench/contrib/preferences/browser/set import { ISettingsEditorViewState, SearchResultModel, SettingsTreeElement, SettingsTreeGroupElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { settingsHeaderForeground } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { localize } from 'vs/nls'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; const $ = DOM.$; @@ -25,7 +26,10 @@ export class TOCTreeModel { private _currentSearchModel: SearchResultModel | null; private _settingsTreeRoot: SettingsTreeGroupElement; - constructor(private _viewState: ISettingsEditorViewState) { + constructor( + private _viewState: ISettingsEditorViewState, + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService + ) { } get settingsTreeRoot(): SettingsTreeGroupElement { @@ -81,7 +85,8 @@ export class TOCTreeModel { } // Check everything that the SettingsFilter checks except whether it's filtered by a category - return child.matchesScope(this._viewState.settingsTarget) && child.matchesAllTags(this._viewState.tagFilters) && child.matchesAnyExtension(this._viewState.extensionFilters); + const isRemote = !!this.environmentService.configuration.remoteAuthority; + return child.matchesScope(this._viewState.settingsTarget, isRemote) && child.matchesAllTags(this._viewState.tagFilters) && child.matchesAnyExtension(this._viewState.extensionFilters); }).length; } } diff --git a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index 5453db8b2f7c..2a6686acbdbf 100644 --- a/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { isLinux } from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; import { endsWith } from 'vs/base/common/strings'; @@ -79,7 +79,7 @@ export class PreferencesContribution implements IWorkbenchContribution { } // Global User Settings File - if (isEqual(resource, URI.file(this.environmentService.appSettingsPath), !isLinux)) { + if (isEqual(resource, this.environmentService.settingsResource, !isLinux)) { return { override: this.preferencesService.openGlobalSettings(true, options, group) }; } @@ -129,14 +129,14 @@ export class PreferencesContribution implements IWorkbenchContribution { const modelContent = JSON.stringify(schema); const languageSelection = this.modeService.create('jsonc'); const model = this.modelService.createModel(modelContent, languageSelection, uri); - const disposables: IDisposable[] = []; - disposables.push(schemaRegistry.onDidChangeSchema(schemaUri => { + const disposables = new DisposableStore(); + disposables.add(schemaRegistry.onDidChangeSchema(schemaUri => { if (schemaUri === uri.toString()) { schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; model.setValue(JSON.stringify(schema)); } })); - disposables.push(model.onWillDispose(() => dispose(disposables))); + disposables.add(model.onWillDispose(() => disposables.dispose())); return model; } diff --git a/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts index 69accb99c998..625dce6d8304 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts @@ -4,18 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { ISettingsEditorModel, ISetting, ISettingsGroup, IFilterMetadata, ISearchResult, IGroupFilter, ISettingMatcher, IScoredResults, ISettingMatch, IRemoteSetting, IExtensionSetting } from 'vs/workbench/services/preferences/common/preferences'; -import { IRange } from 'vs/editor/common/core/range'; -import { distinct, top } from 'vs/base/common/arrays'; +import { top } from 'vs/base/common/arrays'; import * as strings from 'vs/base/common/strings'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IMatch, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IRequestService } from 'vs/platform/request/node/request'; import { asJson } from 'vs/base/node/request'; -import { Disposable } from 'vs/base/common/lifecycle'; import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ILogService } from 'vs/platform/log/common/log'; import { IPreferencesSearchService, ISearchProvider, IWorkbenchSettingsConfiguration } from 'vs/workbench/contrib/preferences/common/preferences'; @@ -24,25 +18,26 @@ import { canceled } from 'vs/base/common/errors'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { nullRange } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { PreferencesSearchService as LocalPreferencesSearchService, SettingMatches } from 'vs/workbench/contrib/preferences/browser/preferencesSearch'; export interface IEndpointDetails { urlBase?: string; key?: string; } -export class PreferencesSearchService extends Disposable implements IPreferencesSearchService { +export class PreferencesSearchService extends LocalPreferencesSearchService implements IPreferencesSearchService { _serviceBrand: any; private _installedExtensions: Promise; constructor( + @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService ) { - super(); + super(instantiationService); // This request goes to the shared process but results won't change during a window's lifetime, so cache the results. this._installedExtensions = this.extensionManagementService.getInstalled(ExtensionType.User).then(exts => { @@ -86,10 +81,6 @@ export class PreferencesSearchService extends Disposable implements IPreferences return this.remoteSearchAllowed ? this.instantiationService.createInstance(RemoteSearchProvider, opts, this._installedExtensions) : undefined; } - - getLocalSearchProvider(filter: string): LocalSearchProvider { - return this.instantiationService.createInstance(LocalSearchProvider, filter); - } } export class LocalSearchProvider implements ISearchProvider { @@ -437,129 +428,4 @@ function remoteSettingToISetting(remoteSetting: IRemoteSetting): IExtensionSetti extensionName: remoteSetting.extensionName, extensionPublisher: remoteSetting.extensionPublisher }; -} - -class SettingMatches { - - private readonly descriptionMatchingWords: Map = new Map(); - private readonly keyMatchingWords: Map = new Map(); - private readonly valueMatchingWords: Map = new Map(); - - readonly matches: IRange[]; - - constructor(searchString: string, setting: ISetting, private requireFullQueryMatch: boolean, private searchDescription: boolean, private valuesMatcher: (filter: string, setting: ISetting) => IRange[]) { - this.matches = distinct(this._findMatchesInSetting(searchString, setting), (match) => `${match.startLineNumber}_${match.startColumn}_${match.endLineNumber}_${match.endColumn}_`); - } - - private _findMatchesInSetting(searchString: string, setting: ISetting): IRange[] { - const result = this._doFindMatchesInSetting(searchString, setting); - if (setting.overrides && setting.overrides.length) { - for (const subSetting of setting.overrides) { - const subSettingMatches = new SettingMatches(searchString, subSetting, this.requireFullQueryMatch, this.searchDescription, this.valuesMatcher); - const words = searchString.split(' '); - const descriptionRanges: IRange[] = this.getRangesForWords(words, this.descriptionMatchingWords, [subSettingMatches.descriptionMatchingWords, subSettingMatches.keyMatchingWords, subSettingMatches.valueMatchingWords]); - const keyRanges: IRange[] = this.getRangesForWords(words, this.keyMatchingWords, [subSettingMatches.descriptionMatchingWords, subSettingMatches.keyMatchingWords, subSettingMatches.valueMatchingWords]); - const subSettingKeyRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.keyMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.valueMatchingWords]); - const subSettinValueRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.valueMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.keyMatchingWords]); - result.push(...descriptionRanges, ...keyRanges, ...subSettingKeyRanges, ...subSettinValueRanges); - result.push(...subSettingMatches.matches); - } - } - return result; - } - - private _doFindMatchesInSetting(searchString: string, setting: ISetting): IRange[] { - const registry: { [qualifiedKey: string]: IJSONSchema } = Registry.as(Extensions.Configuration).getConfigurationProperties(); - const schema: IJSONSchema = registry[setting.key]; - - const words = searchString.split(' '); - const settingKeyAsWords: string = setting.key.split('.').join(' '); - - for (const word of words) { - if (this.searchDescription) { - for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) { - const descriptionMatches = matchesWords(word, setting.description[lineIndex], true); - if (descriptionMatches) { - this.descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex))); - } - } - } - - const keyMatches = or(matchesWords, matchesCamelCase)(word, settingKeyAsWords); - if (keyMatches) { - this.keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match))); - } - - const valueMatches = typeof setting.value === 'string' ? matchesContiguousSubString(word, setting.value) : null; - if (valueMatches) { - this.valueMatchingWords.set(word, valueMatches.map(match => this.toValueRange(setting, match))); - } else if (schema && schema.enum && schema.enum.some(enumValue => typeof enumValue === 'string' && !!matchesContiguousSubString(word, enumValue))) { - this.valueMatchingWords.set(word, []); - } - } - - const descriptionRanges: IRange[] = []; - if (this.searchDescription) { - for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) { - const matches = or(matchesContiguousSubString)(searchString, setting.description[lineIndex] || '') || []; - descriptionRanges.push(...matches.map(match => this.toDescriptionRange(setting, match, lineIndex))); - } - if (descriptionRanges.length === 0) { - descriptionRanges.push(...this.getRangesForWords(words, this.descriptionMatchingWords, [this.keyMatchingWords, this.valueMatchingWords])); - } - } - - const keyMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.key); - const keyRanges: IRange[] = keyMatches ? keyMatches.map(match => this.toKeyRange(setting, match)) : this.getRangesForWords(words, this.keyMatchingWords, [this.descriptionMatchingWords, this.valueMatchingWords]); - - let valueRanges: IRange[] = []; - if (setting.value && typeof setting.value === 'string') { - const valueMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.value); - valueRanges = valueMatches ? valueMatches.map(match => this.toValueRange(setting, match)) : this.getRangesForWords(words, this.valueMatchingWords, [this.keyMatchingWords, this.descriptionMatchingWords]); - } else { - valueRanges = this.valuesMatcher ? this.valuesMatcher(searchString, setting) : []; - } - - return [...descriptionRanges, ...keyRanges, ...valueRanges]; - } - - private getRangesForWords(words: string[], from: Map, others: Map[]): IRange[] { - const result: IRange[] = []; - for (const word of words) { - const ranges = from.get(word); - if (ranges) { - result.push(...ranges); - } else if (this.requireFullQueryMatch && others.every(o => !o.has(word))) { - return []; - } - } - return result; - } - - private toKeyRange(setting: ISetting, match: IMatch): IRange { - return { - startLineNumber: setting.keyRange.startLineNumber, - startColumn: setting.keyRange.startColumn + match.start, - endLineNumber: setting.keyRange.startLineNumber, - endColumn: setting.keyRange.startColumn + match.end - }; - } - - private toDescriptionRange(setting: ISetting, match: IMatch, lineIndex: number): IRange { - return { - startLineNumber: setting.descriptionRanges[lineIndex].startLineNumber, - startColumn: setting.descriptionRanges[lineIndex].startColumn + match.start, - endLineNumber: setting.descriptionRanges[lineIndex].endLineNumber, - endColumn: setting.descriptionRanges[lineIndex].startColumn + match.end - }; - } - - private toValueRange(setting: ISetting, match: IMatch): IRange { - return { - startLineNumber: setting.valueRange.startLineNumber, - startColumn: setting.valueRange.startColumn + match.start + 1, - endLineNumber: setting.valueRange.startLineNumber, - endColumn: setting.valueRange.startColumn + match.end + 1 - }; - } -} +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index c63324ed9754..d415732c92bb 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -30,7 +30,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; export const ALL_COMMANDS_PREFIX = '>'; @@ -294,7 +294,7 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { this.onBeforeRun(this.commandId); // Use a timeout to give the quick open widget a chance to close itself first - setTimeout(() => { + setTimeout(async () => { if (action && (!(action instanceof Action) || action.enabled)) { try { /* __GDPR__ @@ -304,11 +304,17 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { } */ this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'quick open' }); - (action.run() || Promise.resolve()).then(() => { - if (action instanceof Action) { - action.dispose(); + + const promise = action.run(); + if (promise) { + try { + await promise; + } finally { + if (action instanceof Action) { + action.dispose(); + } } - }, err => this.onError(err)); + } } catch (error) { this.onError(error); } @@ -371,12 +377,14 @@ class ActionCommandEntry extends BaseCommandEntry { const wordFilter = or(matchesPrefix, matchesWords, matchesContiguousSubString); -export class CommandsHandler extends QuickOpenHandler { +export class CommandsHandler extends QuickOpenHandler implements IDisposable { static readonly ID = 'workbench.picker.commands'; private commandHistoryEnabled: boolean; - private commandsHistory: CommandsHistory; + private readonly commandsHistory: CommandsHistory; + + private readonly disposables = new DisposableStore(); private waitedForExtensionsRegistered: boolean; @@ -390,7 +398,7 @@ export class CommandsHandler extends QuickOpenHandler { ) { super(); - this.commandsHistory = this.instantiationService.createInstance(CommandsHistory); + this.commandsHistory = this.disposables.add(this.instantiationService.createInstance(CommandsHistory)); this.extensionService.whenInstalledExtensionsRegistered().then(() => this.waitedForExtensionsRegistered = true); @@ -402,7 +410,7 @@ export class CommandsHandler extends QuickOpenHandler { this.commandHistoryEnabled = resolveCommandHistory(this.configurationService) > 0; } - getResults(searchValue: string, token: CancellationToken): Promise { + async getResults(searchValue: string, token: CancellationToken): Promise { if (this.waitedForExtensionsRegistered) { return this.doGetResults(searchValue, token); } @@ -411,11 +419,10 @@ export class CommandsHandler extends QuickOpenHandler { // a chance to register so that the complete set of commands shows up as result // We do not want to delay functionality beyond that time though to keep the commands // functional. - return Promise.race([timeout(800), this.extensionService.whenInstalledExtensionsRegistered().then(() => undefined)]).then(() => { - this.waitedForExtensionsRegistered = true; + await Promise.race([timeout(800).then(), this.extensionService.whenInstalledExtensionsRegistered()]); + this.waitedForExtensionsRegistered = true; - return this.doGetResults(searchValue, token); - }); + return this.doGetResults(searchValue, token); } private doGetResults(searchValue: string, token: CancellationToken): Promise { @@ -583,6 +590,10 @@ export class CommandsHandler extends QuickOpenHandler { getEmptyLabel(searchString: string): string { return nls.localize('noCommandsMatching', "No commands matching"); } + + dispose() { + this.disposables.dispose(); + } } registerEditorAction(CommandPaletteEditorAction); diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index f8be9ca47805..e04f0aa3faaf 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -495,7 +495,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { return this.cachedOutlineRequest; } - private doGetActiveOutline(): Promise { + private async doGetActiveOutline(): Promise { const activeTextEditorWidget = this.editorService.activeTextEditorWidget; if (activeTextEditorWidget) { let model = activeTextEditorWidget.getModel(); @@ -504,13 +504,13 @@ export class GotoSymbolHandler extends QuickOpenHandler { } if (model && types.isFunction((model).getLanguageIdentifier)) { - return Promise.resolve(asPromise(() => getDocumentSymbols(model, true, this.pendingOutlineRequest!.token)).then(entries => { - return new OutlineModel(this.toQuickOpenEntries(entries)); - })); + const entries = await asPromise(() => getDocumentSymbols(model, true, this.pendingOutlineRequest!.token)); + + return new OutlineModel(this.toQuickOpenEntries(entries)); } } - return Promise.resolve(null); + return null; } decorateOutline(fullRange: IRange, startRange: IRange, editor: IEditor, group: IEditorGroup): void { diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index abb53e72e2d4..3109ddeebbc2 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -32,7 +32,7 @@ import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc'; import { ipcRenderer as ipc } from 'electron'; import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProgressService2, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; @@ -143,7 +143,7 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc if (this.windowIndicatorEntry) { this.windowIndicatorEntry.update(properties); } else { - this.windowIndicatorEntry = this.statusbarService.addEntry(properties, StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */); + this.windowIndicatorEntry = this.statusbarService.addEntry(properties, 'status.host', nls.localize('status.host', "Remote Host"), StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */); } } @@ -272,7 +272,7 @@ class ProgressReporter { class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IProgressService2 progressService: IProgressService2, + @IProgressService progressService: IProgressService, @IDialogService dialogService: IDialogService, @ICommandService commandService: ICommandService ) { diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index d73fa11bf00a..f92bd1d64248 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import 'vs/css!./media/dirtydiffDecorator'; import { ThrottledDelayer, first } from 'vs/base/common/async'; -import { IDisposable, dispose, toDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as ext from 'vs/workbench/common/contributions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -184,7 +184,7 @@ class DirtyDiffWidget extends PeekViewWidget { ) { super(editor, { isResizeable: true, frameWidth: 1, keepEditorSelection: true }); - themeService.onThemeChange(this._applyTheme, this, this._disposables); + this._disposables.add(themeService.onThemeChange(this._applyTheme, this)); this._applyTheme(themeService.getTheme()); this.contextKeyService = contextKeyService.createScoped(); @@ -199,7 +199,7 @@ class DirtyDiffWidget extends PeekViewWidget { } this.setTitle(this.title); - model.onDidChange(this.renderTitle, this, this._disposables); + this._disposables.add(model.onDidChange(this.renderTitle, this)); } showChange(index: number): void { @@ -253,8 +253,8 @@ class DirtyDiffWidget extends PeekViewWidget { const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), 'show-previous-change chevron-up'); const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), 'show-next-change chevron-down'); - this._disposables.push(previous); - this._disposables.push(next); + this._disposables.add(previous); + this._disposables.add(next); this._actionbarWidget.push([previous, next], { label: false, icon: true }); const actions: IAction[] = []; @@ -554,7 +554,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -export class DirtyDiffController implements IEditorContribution { +export class DirtyDiffController extends Disposable implements IEditorContribution { private static readonly ID = 'editor.contrib.dirtydiff'; @@ -571,20 +571,20 @@ export class DirtyDiffController implements IEditorContribution { private session: IDisposable = Disposable.None; private mouseDownInfo: { lineNumber: number } | null = null; private enabled = false; - private disposables: IDisposable[] = []; constructor( private editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { + super(); this.enabled = !contextKeyService.getContextKeyValue('isInDiffEditor'); if (this.enabled) { this.isDirtyDiffVisible = isDirtyDiffVisible.bindTo(contextKeyService); - this.disposables.push(editor.onMouseDown(e => this.onEditorMouseDown(e))); - this.disposables.push(editor.onMouseUp(e => this.onEditorMouseUp(e))); - this.disposables.push(editor.onDidChangeModel(() => this.close())); + this._register(editor.onMouseDown(e => this.onEditorMouseDown(e))); + this._register(editor.onMouseUp(e => this.onEditorMouseUp(e))); + this._register(editor.onDidChangeModel(() => this.close())); } } @@ -674,22 +674,20 @@ export class DirtyDiffController implements IEditorContribution { this.widget = this.instantiationService.createInstance(DirtyDiffWidget, this.editor, model); this.isDirtyDiffVisible.set(true); - const disposables: IDisposable[] = []; - Event.once(this.widget.onDidClose)(this.close, this, disposables); - model.onDidChange(this.onDidModelChange, this, disposables); - - disposables.push( - this.widget, - toDisposable(() => { - this.model = null; - this.widget = null; - this.currentIndex = -1; - this.isDirtyDiffVisible.set(false); - this.editor.focus(); - }) - ); + const disposables = new DisposableStore(); + disposables.add(Event.once(this.widget.onDidClose)(this.close, this)); + disposables.add(model.onDidChange(this.onDidModelChange, this)); + + disposables.add(this.widget); + disposables.add(toDisposable(() => { + this.model = null; + this.widget = null; + this.currentIndex = -1; + this.isDirtyDiffVisible.set(false); + this.editor.focus(); + })); - this.session = combinedDisposable(disposables); + this.session = disposables; return true; } @@ -808,10 +806,6 @@ export class DirtyDiffController implements IEditorContribution { return model.changes; } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } export const editorGutterModifiedBackground = registerColor('editorGutter.modifiedBackground', { @@ -837,7 +831,7 @@ export const overviewRulerModifiedForeground = registerColor('editorOverviewRule export const overviewRulerAddedForeground = registerColor('editorOverviewRuler.addedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerAddedForeground', 'Overview ruler marker color for added content.')); export const overviewRulerDeletedForeground = registerColor('editorOverviewRuler.deletedForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('overviewRulerDeletedForeground', 'Overview ruler marker color for deleted content.')); -class DirtyDiffDecorator { +class DirtyDiffDecorator extends Disposable { static createDecoration(className: string, foregroundColor: string, options: { gutter: boolean, overview: boolean, isWholeLine: boolean }): ModelDecorationOptions { const decorationOptions: IModelDecorationOptions = { @@ -862,7 +856,6 @@ class DirtyDiffDecorator { private addedOptions: ModelDecorationOptions; private deletedOptions: ModelDecorationOptions; private decorations: string[] = []; - private disposables: IDisposable[] = []; private editorModel: ITextModel | null; constructor( @@ -870,6 +863,7 @@ class DirtyDiffDecorator { private model: DirtyDiffModel, @IConfigurationService configurationService: IConfigurationService ) { + super(); this.editorModel = editorModel; const decorations = configurationService.getValue('scm.diffDecorations'); const gutter = decorations === 'all' || decorations === 'gutter'; @@ -880,7 +874,7 @@ class DirtyDiffDecorator { this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', overviewRulerAddedForeground, options); this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', overviewRulerDeletedForeground, { ...options, isWholeLine: false }); - model.onDidChange(this.onDidChange, this, this.disposables); + this._register(model.onDidChange(this.onDidChange, this)); } private onDidChange(): void { @@ -924,7 +918,7 @@ class DirtyDiffDecorator { } dispose(): void { - this.disposables = dispose(this.disposables); + super.dispose(); if (this.editorModel && !this.editorModel.isDisposed()) { this.editorModel.deltaDecorations(this.decorations, []); @@ -957,7 +951,7 @@ function compareChanges(a: IChange, b: IChange): number { return a.originalEndLineNumber - b.originalEndLineNumber; } -export class DirtyDiffModel { +export class DirtyDiffModel extends Disposable { private _originalModel: ITextModel | null; get original(): ITextModel | null { return this._originalModel; } @@ -965,9 +959,8 @@ export class DirtyDiffModel { private diffDelayer: ThrottledDelayer | null; private _originalURIPromise?: Promise; - private repositoryDisposables = new Set(); - private originalModelDisposables: IDisposable[] = []; - private disposables: IDisposable[] = []; + private repositoryDisposables = new Set(); + private readonly originalModelDisposables = this._register(new DisposableStore()); private _onDidChange = new Emitter[]>(); readonly onDidChange: Event[]> = this._onDidChange.event; @@ -985,27 +978,28 @@ export class DirtyDiffModel { @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, @ITextModelService private readonly textModelResolverService: ITextModelService ) { + super(); this._editorModel = editorModel; this.diffDelayer = new ThrottledDelayer(200); - this.disposables.push(editorModel.onDidChangeContent(() => this.triggerDiff())); - scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + this._register(editorModel.onDidChangeContent(() => this.triggerDiff())); + this._register(scmService.onDidAddRepository(this.onDidAddRepository, this)); scmService.repositories.forEach(r => this.onDidAddRepository(r)); this.triggerDiff(); } private onDidAddRepository(repository: ISCMRepository): void { - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); this.repositoryDisposables.add(disposables); - disposables.push(toDisposable(() => this.repositoryDisposables.delete(disposables))); + disposables.add(toDisposable(() => this.repositoryDisposables.delete(disposables))); const onDidChange = Event.any(repository.provider.onDidChange, repository.provider.onDidChangeResources); - onDidChange(this.triggerDiff, this, disposables); + disposables.add(onDidChange(this.triggerDiff, this)); const onDidRemoveThis = Event.filter(this.scmService.onDidRemoveRepository, r => r === repository); - onDidRemoveThis(() => dispose(disposables), null, disposables); + disposables.add(onDidRemoveThis(() => dispose(disposables), null)); this.triggerDiff(); } @@ -1075,12 +1069,9 @@ export class DirtyDiffModel { this._originalModel = ref.object.textEditorModel; - const originalModelDisposables: IDisposable[] = []; - originalModelDisposables.push(ref); - originalModelDisposables.push(ref.object.textEditorModel.onDidChangeContent(() => this.triggerDiff())); - - dispose(this.originalModelDisposables); - this.originalModelDisposables = originalModelDisposables; + this.originalModelDisposables.clear(); + this.originalModelDisposables.add(ref); + this.originalModelDisposables.add(ref.object.textEditorModel.onDidChangeContent(() => this.triggerDiff())); return originalUri; }); @@ -1137,8 +1128,7 @@ export class DirtyDiffModel { } dispose(): void { - this.originalModelDisposables = dispose(this.originalModelDisposables); - this.disposables = dispose(this.disposables); + super.dispose(); this._editorModel = null; this._originalModel = null; @@ -1163,25 +1153,25 @@ class DirtyDiffItem { } } -export class DirtyDiffWorkbenchController implements ext.IWorkbenchContribution, IModelRegistry { +export class DirtyDiffWorkbenchController extends Disposable implements ext.IWorkbenchContribution, IModelRegistry { private enabled = false; private models: ITextModel[] = []; private items: { [modelId: string]: DirtyDiffItem; } = Object.create(null); private transientDisposables: IDisposable[] = []; private stylesheet: HTMLStyleElement; - private disposables: IDisposable[] = []; constructor( @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService private readonly configurationService: IConfigurationService ) { + super(); this.stylesheet = createStyleSheet(); - this.disposables.push(toDisposable(() => this.stylesheet.parentElement!.removeChild(this.stylesheet))); + this._register(toDisposable(() => this.stylesheet.parentElement!.removeChild(this.stylesheet))); const onDidChangeConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorations')); - onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); + this._register(onDidChangeConfiguration(this.onDidChangeConfiguration, this)); this.onDidChangeConfiguration(); const onDidChangeDiffWidthConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorationsGutterWidth')); @@ -1284,7 +1274,7 @@ export class DirtyDiffWorkbenchController implements ext.IWorkbenchContribution, dispose(): void { this.disable(); - this.disposables = dispose(this.disposables); + super.dispose(); } } diff --git a/src/vs/workbench/contrib/scm/browser/scmActivity.ts b/src/vs/workbench/contrib/scm/browser/scmActivity.ts index 89b988b19396..a6d45f8a967a 100644 --- a/src/vs/workbench/contrib/scm/browser/scmActivity.ts +++ b/src/vs/workbench/contrib/scm/browser/scmActivity.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { basename } from 'vs/base/common/resources'; -import { IDisposable, dispose, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; @@ -46,7 +46,7 @@ export class StatusUpdater implements IWorkbenchContribution { this.render(); }); - const disposable = combinedDisposable([changeDisposable, removeDisposable]); + const disposable = combinedDisposable(changeDisposable, removeDisposable); this.disposables.push(disposable); } @@ -151,7 +151,7 @@ export class StatusBarController implements IWorkbenchContribution { } }); - const disposable = combinedDisposable([changeDisposable, removeDisposable]); + const disposable = combinedDisposable(changeDisposable, removeDisposable); this.disposables.push(disposable); if (!this.focusedRepository) { @@ -187,14 +187,17 @@ export class StatusBarController implements IWorkbenchContribution { ? `${basename(repository.provider.rootUri)} (${repository.provider.label})` : repository.provider.label; - const disposables = commands.map(c => this.statusbarService.addEntry({ - text: c.title, - tooltip: `${label} - ${c.tooltip}`, - command: c.id, - arguments: c.arguments - }, MainThreadStatusBarAlignment.LEFT, 10000)); + const disposables = new DisposableStore(); + for (const c of commands) { + disposables.add(this.statusbarService.addEntry({ + text: c.title, + tooltip: `${label} - ${c.tooltip}`, + command: c.id, + arguments: c.arguments + }, 'status.scm', localize('status.scm', "Source Control"), MainThreadStatusBarAlignment.LEFT, 10000)); + } - this.statusBarDisposable = combinedDisposable(disposables); + this.statusBarDisposable = disposables; } dispose(): void { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 83199b384f2f..f83c1add2099 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { basename } from 'vs/base/common/resources'; -import { IDisposable, dispose, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { append, $, addClass, toggleClass, trackFocus, removeClass, addClasses } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -162,14 +162,14 @@ class ProviderRenderer implements IListRenderer new StatusBarActionViewItem(a as StatusBarAction) }); const disposable = Disposable.None; - const templateDisposable = combinedDisposable([actionBar, badgeStyler]); + const templateDisposable = combinedDisposable(actionBar, badgeStyler); return { title, type, countContainer, count, actionBar, disposable, templateDisposable }; } renderElement(repository: ISCMRepository, index: number, templateData: RepositoryTemplateData): void { templateData.disposable.dispose(); - const disposables: IDisposable[] = []; + const disposables = new DisposableStore(); if (repository.provider.rootUri) { templateData.title.textContent = basename(repository.provider.rootUri); @@ -181,7 +181,7 @@ class ProviderRenderer implements IListRenderer dispose(actions); - disposables.push({ dispose: disposeActions }); + disposables.add({ dispose: disposeActions }); const update = () => { disposeActions(); @@ -198,10 +198,10 @@ class ProviderRenderer implements IListRenderer; - renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null, this.disposables); - this.list.onSelectionChange(this.onListSelectionChange, this, this.disposables); - this.list.onFocusChange(this.onListFocusChange, this, this.disposables); - this.list.onContextMenu(this.onListContextMenu, this, this.disposables); + this._register(renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null)); + this._register(this.list.onSelectionChange(this.onListSelectionChange, this)); + this._register(this.list.onFocusChange(this.onListFocusChange, this)); + this._register(this.list.onContextMenu(this.onListContextMenu, this)); - this.viewModel.onDidChangeVisibleRepositories(this.updateListSelection, this, this.disposables); + this._register(this.viewModel.onDidChangeVisibleRepositories(this.updateListSelection, this)); - this.viewModel.onDidSplice(({ index, deleteCount, elements }) => this.splice(index, deleteCount, elements), null, this.disposables); + this._register(this.viewModel.onDidSplice(({ index, deleteCount, elements }) => this.splice(index, deleteCount, elements), null)); this.splice(0, 0, this.viewModel.repositories); - this.disposables.push(this.list); + this._register(this.list); - this.configurationService.onDidChangeConfiguration(e => { + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('scm.providers.visible')) { this.updateBodySize(); } - }, this.disposables); + })); this.updateListSelection(); } @@ -396,14 +396,14 @@ class ResourceGroupRenderer implements IListRenderer template.count.setCount(group.elements.length); - group.onDidSplice(updateCount, null, disposables); + disposables.add(group.onDidSplice(updateCount, null)); updateCount(); - template.elementDisposable = combinedDisposable(disposables); + template.elementDisposable = disposables; } disposeElement(group: ISCMResourceGroup, index: number, template: ResourceGroupTemplate): void { @@ -489,8 +489,8 @@ class ResourceRenderer implements IListRenderer template.fileLabel.setFile(resource.sourceUri, { fileDecorations: { colors: false, badges: !icon, data: resource.decorations } }); template.actionBar.context = resource; - const disposables: IDisposable[] = []; - disposables.push(connectPrimaryMenuToInlineActionBar(this.menus.getResourceMenu(resource.resourceGroup), template.actionBar)); + const disposables = new DisposableStore(); + disposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceMenu(resource.resourceGroup), template.actionBar)); toggleClass(template.name, 'strike-through', resource.decorations.strikeThrough); toggleClass(template.element, 'faded', resource.decorations.faded); @@ -505,7 +505,7 @@ class ResourceRenderer implements IListRenderer } template.element.setAttribute('data-tooltip', resource.decorations.tooltip || ''); - template.elementDisposable = combinedDisposable(disposables); + template.elementDisposable = disposables; } disposeElement(resource: ISCMResource, index: number, template: ResourceTemplate): void { @@ -602,10 +602,10 @@ class ResourceGroupSplicer { absoluteToInsert.push(element); } - const disposable = combinedDisposable([ + const disposable = combinedDisposable( group.onDidChange(() => this.onDidChangeGroup(group)), group.onDidSplice(splice => this.onDidSpliceGroup(group, splice)) - ]); + ); itemsToInsert.push({ group, visible, disposable }); } @@ -699,6 +699,7 @@ export class RepositoryPanel extends ViewletPanel { private cachedHeight: number | undefined = undefined; private cachedWidth: number | undefined = undefined; + private cachedScrollTop: number | undefined = undefined; private inputBoxContainer: HTMLElement; private inputBox: InputBox; private listContainer: HTMLElement; @@ -727,8 +728,8 @@ export class RepositoryPanel extends ViewletPanel { super(options, keybindingService, contextMenuService, configurationService); this.menus = instantiationService.createInstance(SCMMenus, this.repository.provider); - this.disposables.push(this.menus); - this.menus.onDidChangeTitle(this._onDidChangeTitleArea.fire, this._onDidChangeTitleArea, this.disposables); + this._register(this.menus); + this._register(this.menus.onDidChangeTitle(this._onDidChangeTitleArea.fire, this._onDidChangeTitleArea)); this.contextKeyService = contextKeyService.createScoped(this.element); this.contextKeyService.createKey('scmRepository', this.repository); @@ -736,7 +737,7 @@ export class RepositoryPanel extends ViewletPanel { render(): void { super.render(); - this.menus.onDidChangeTitle(this.updateActions, this, this.disposables); + this._register(this.menus.onDidChangeTitle(this.updateActions, this)); } protected renderHeaderTitle(container: HTMLElement): void { @@ -758,8 +759,8 @@ export class RepositoryPanel extends ViewletPanel { protected renderBody(container: HTMLElement): void { const focusTracker = trackFocus(container); - this.disposables.push(focusTracker.onDidFocus(() => this.repository.focus())); - this.disposables.push(focusTracker); + this._register(focusTracker.onDidFocus(() => this.repository.focus())); + this._register(focusTracker); // Input this.inputBoxContainer = append(container, $('.scm-editor')); @@ -789,33 +790,33 @@ export class RepositoryPanel extends ViewletPanel { this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { flexibleHeight: true }); this.inputBox.setEnabled(this.isBodyVisible()); - this.disposables.push(attachInputBoxStyler(this.inputBox, this.themeService)); - this.disposables.push(this.inputBox); + this._register(attachInputBoxStyler(this.inputBox, this.themeService)); + this._register(this.inputBox); - this.inputBox.onDidChange(triggerValidation, null, this.disposables); + this._register(this.inputBox.onDidChange(triggerValidation, null)); const onKeyUp = domEvent(this.inputBox.inputElement, 'keyup'); const onMouseUp = domEvent(this.inputBox.inputElement, 'mouseup'); - Event.any(onKeyUp, onMouseUp)(triggerValidation, null, this.disposables); + this._register(Event.any(onKeyUp, onMouseUp)(triggerValidation, null)); this.inputBox.value = this.repository.input.value; - this.inputBox.onDidChange(value => this.repository.input.value = value, null, this.disposables); - this.repository.input.onDidChange(value => this.inputBox.value = value, null, this.disposables); + this._register(this.inputBox.onDidChange(value => this.repository.input.value = value, null)); + this._register(this.repository.input.onDidChange(value => this.inputBox.value = value, null)); updatePlaceholder(); - this.repository.input.onDidChangePlaceholder(updatePlaceholder, null, this.disposables); - this.keybindingService.onDidUpdateKeybindings(updatePlaceholder, null, this.disposables); + this._register(this.repository.input.onDidChangePlaceholder(updatePlaceholder, null)); + this._register(this.keybindingService.onDidUpdateKeybindings(updatePlaceholder, null)); - this.disposables.push(this.inputBox.onDidHeightChange(() => this.layoutBody())); + this._register(this.inputBox.onDidHeightChange(() => this.layoutBody())); if (this.repository.provider.onDidChangeCommitTemplate) { - this.repository.provider.onDidChangeCommitTemplate(this.updateInputBox, this, this.disposables); + this._register(this.repository.provider.onDidChangeCommitTemplate(this.updateInputBox, this)); } this.updateInputBox(); // Input box visibility - this.repository.input.onDidChangeVisibility(this.updateInputBoxVisibility, this, this.disposables); + this._register(this.repository.input.onDidChangeVisibility(this.updateInputBoxVisibility, this)); this.updateInputBoxVisibility(); // List @@ -830,7 +831,7 @@ export class RepositoryPanel extends ViewletPanel { const actionViewItemProvider = (action: IAction) => this.getActionViewItem(action); this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); - this.disposables.push(this.listLabels); + this._register(this.listLabels); const renderers = [ new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus), @@ -843,20 +844,20 @@ export class RepositoryPanel extends ViewletPanel { horizontalScrolling: false }) as WorkbenchList; - Event.chain(this.list.onDidOpen) + this._register(Event.chain(this.list.onDidOpen) .map(e => e.elements[0]) .filter(e => !!e && isSCMResource(e)) - .on(this.open, this, this.disposables); + .on(this.open, this)); - Event.chain(this.list.onPin) + this._register(Event.chain(this.list.onPin) .map(e => e.elements[0]) .filter(e => !!e && isSCMResource(e)) - .on(this.pin, this, this.disposables); + .on(this.pin, this)); - this.list.onContextMenu(this.onListContextMenu, this, this.disposables); - this.disposables.push(this.list); + this._register(this.list.onContextMenu(this.onListContextMenu, this)); + this._register(this.list); - this.viewModel.onDidChangeVisibility(this.onDidChangeVisibility, this, this.disposables); + this._register(this.viewModel.onDidChangeVisibility(this.onDidChangeVisibility, this)); this.onDidChangeVisibility(this.viewModel.isVisible()); this.onDidChangeBodyVisibility(visible => this.inputBox.setEnabled(visible)); } @@ -866,6 +867,7 @@ export class RepositoryPanel extends ViewletPanel { const listSplicer = new ResourceGroupSplicer(this.repository.provider.groups, this.list); this.visibilityDisposables.push(listSplicer); } else { + this.cachedScrollTop = this.list.scrollTop; this.visibilityDisposables = dispose(this.visibilityDisposables); } } @@ -894,6 +896,13 @@ export class RepositoryPanel extends ViewletPanel { this.listContainer.style.height = `${height}px`; this.list.layout(height, width); } + + if (this.cachedScrollTop !== undefined && this.list.scrollTop !== this.cachedScrollTop) { + this.list.scrollTop = Math.min(this.cachedScrollTop, this.list.scrollHeight); + // Applying the cached scroll position just once until the next leave. + // This, also, avoids the scrollbar to flicker when resizing the sidebar. + this.cachedScrollTop = undefined; + } } focus(): void { @@ -1106,15 +1115,15 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { super(VIEWLET_ID, SCMViewlet.STATE_KEY, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); this.menus = instantiationService.createInstance(SCMMenus, undefined); - this.menus.onDidChangeTitle(this.updateTitleArea, this, this.toDispose); + this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); this.message = $('.empty-message', { tabIndex: 0 }, localize('no open repo', "No source control providers registered.")); - configurationService.onDidChangeConfiguration(e => { + this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('scm.alwaysShowProviders')) { this.onDidChangeRepositories(); } - }, this.toDispose); + })); } create(parent: HTMLElement): void { @@ -1124,8 +1133,8 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { addClasses(parent, 'scm-viewlet', 'empty'); append(parent, this.message); - this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.toDispose); - this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.toDispose); + this._register(this.scmService.onDidAddRepository(this.onDidAddRepository, this)); + this._register(this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this)); this.scmService.repositories.forEach(r => this.onDidAddRepository(r)); } diff --git a/src/vs/workbench/contrib/search/browser/openFileHandler.ts b/src/vs/workbench/contrib/search/browser/openFileHandler.ts index 24a03d6887c5..9e08445143e6 100644 --- a/src/vs/workbench/contrib/search/browser/openFileHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openFileHandler.ts @@ -117,7 +117,6 @@ export class OpenFileHandler extends QuickOpenHandler { private cacheState: CacheState; constructor( - @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -208,7 +207,7 @@ export class OpenFileHandler extends QuickOpenHandler { private doResolveQueryOptions(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): IFileQueryBuilderOptions { const queryOptions: IFileQueryBuilderOptions = { _reason: 'openFileHandler', - extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService), + extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), filePattern: query.original, cacheKey }; @@ -233,7 +232,7 @@ export class OpenFileHandler extends QuickOpenHandler { private cacheQuery(cacheKey: string): IFileQuery { const options: IFileQueryBuilderOptions = { _reason: 'openFileHandler', - extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService), + extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), filePattern: '', cacheKey: cacheKey, maxResults: 0, diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 805d0b3b07e7..0bd9ec0e2b71 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -156,12 +156,12 @@ export class OpenSymbolHandler extends QuickOpenHandler { return true; } - getResults(searchValue: string, token: CancellationToken): Promise { + async getResults(searchValue: string, token: CancellationToken): Promise { searchValue = searchValue.trim(); - let promise: Promise; + let entries: QuickOpenEntry[]; if (!this.options.skipDelay) { - promise = this.delayer.trigger(() => { + entries = await this.delayer.trigger(() => { if (token.isCancellationRequested) { return Promise.resolve([]); } @@ -169,32 +169,31 @@ export class OpenSymbolHandler extends QuickOpenHandler { return this.doGetResults(searchValue, token); }); } else { - promise = this.doGetResults(searchValue, token); + entries = await this.doGetResults(searchValue, token); } - return promise.then(e => new QuickOpenModel(e)); + return new QuickOpenModel(entries); } - private doGetResults(searchValue: string, token: CancellationToken): Promise { - return getWorkspaceSymbols(searchValue, token).then(tuples => { - if (token.isCancellationRequested) { - return []; - } + private async doGetResults(searchValue: string, token: CancellationToken): Promise { + const tuples = await getWorkspaceSymbols(searchValue, token); + if (token.isCancellationRequested) { + return []; + } - const result: SymbolEntry[] = []; - for (let tuple of tuples) { - const [provider, bearings] = tuple; - this.fillInSymbolEntries(result, provider, bearings, searchValue); - } + const result: SymbolEntry[] = []; + for (let tuple of tuples) { + const [provider, bearings] = tuple; + this.fillInSymbolEntries(result, provider, bearings, searchValue); + } - // Sort (Standalone only) - if (!this.options.skipSorting) { - searchValue = searchValue ? strings.stripWildcards(searchValue.toLowerCase()) : searchValue; - return result.sort((a, b) => SymbolEntry.compare(a, b, searchValue)); - } + // Sort (Standalone only) + if (!this.options.skipSorting) { + searchValue = searchValue ? strings.stripWildcards(searchValue.toLowerCase()) : searchValue; + return result.sort((a, b) => SymbolEntry.compare(a, b, searchValue)); + } - return result; - }); + return result; } private fillInSymbolEntries(bucket: SymbolEntry[], provider: IWorkspaceSymbolProvider, types: IWorkspaceSymbol[], searchValue: string): void { diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 479fe98b796e..1048017e2e6b 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -33,8 +33,8 @@ import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/file import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TreeResourceNavigator2, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IProgressService } from 'vs/platform/progress/common/progress'; -import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchErrorCode, VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { ILocalProgressService, IProgressService } from 'vs/platform/progress/common/progress'; +import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchErrorCode, VIEW_ID, VIEWLET_ID } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -59,7 +59,7 @@ import { relativePath } from 'vs/base/common/resources'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { Memento } from 'vs/workbench/common/memento'; +import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; const $ = dom.$; @@ -102,8 +102,8 @@ export class SearchView extends ViewletPanel { private tree: WorkbenchObjectTree; private treeLabels: ResourceLabels; - private viewletState: object; - private globalMemento: object; + private viewletState: MementoObject; + private globalMemento: MementoObject; private messagesElement: HTMLElement; private messageDisposables: IDisposable[] = []; private searchWidgetsContainerElement: HTMLElement; @@ -129,6 +129,7 @@ export class SearchView extends ViewletPanel { options: IViewletPanelOptions, @IFileService private readonly fileService: IFileService, @IEditorService private readonly editorService: IEditorService, + @ILocalProgressService private readonly localProgressService: ILocalProgressService, @IProgressService private readonly progressService: IProgressService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @@ -517,7 +518,7 @@ export class SearchView extends ViewletPanel { return; } - const progressRunner = this.progressService.show(100); + const progressRunner = this.localProgressService.show(100); const occurrences = this.viewModel.searchResult.count(); const fileCount = this.viewModel.searchResult.fileCount(); @@ -1193,7 +1194,7 @@ export class SearchView extends ViewletPanel { const options: ITextQueryBuilderOptions = { _reason: 'searchView', - extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService), + extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), maxResults: SearchView.MAX_TEXT_RESULTS, disregardIgnoreFiles: !useExcludesAndIgnoreFiles || undefined, disregardExcludeSettings: !useExcludesAndIgnoreFiles || undefined, @@ -1265,7 +1266,10 @@ export class SearchView extends ViewletPanel { } private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): Thenable { - const progressRunner = this.progressService.show(/*infinite=*/true); + let progressComplete: () => void; + this.progressService.withProgress({ location: VIEWLET_ID }, _progress => { + return new Promise(resolve => progressComplete = resolve); + }); this.searchWidget.searchInput.clearMessage(); this.searching = true; @@ -1280,7 +1284,7 @@ export class SearchView extends ViewletPanel { this.searching = false; // Complete up to 100% as needed - progressRunner.done(); + progressComplete(); // Do final render, then expand if just 1 file with less than 50 matches this.onSearchResultsChanged(); @@ -1375,7 +1379,7 @@ export class SearchView extends ViewletPanel { } else { this.searching = false; this.updateActions(); - progressRunner.done(); + progressComplete(); this.searchWidget.searchInput.showMessage({ content: e.message, type: MessageType.ERROR }); this.viewModel.searchResult.clear(); @@ -1507,7 +1511,7 @@ export class SearchView extends ViewletPanel { this.searchWithoutFolderMessageElement = this.clearMessage(); const textEl = dom.append(this.searchWithoutFolderMessageElement, - $('p', undefined, nls.localize('searchWithoutFolder', "You have not yet opened a folder. Only open files are currently searched - "))); + $('p', undefined, nls.localize('searchWithoutFolder', "You have not opened or specified a folder. Only open files are currently searched - "))); const openFolderLink = dom.append(textEl, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder"))); @@ -1695,21 +1699,9 @@ export class SearchView extends ViewletPanel { super.saveState(); } - private _toDispose: IDisposable[] = []; - protected _register(t: T): T { - if (this.isDisposed) { - console.warn('Registering disposable on object that has already been disposed.'); - t.dispose(); - } else { - this._toDispose.push(t); - } - return t; - } - dispose(): void { this.isDisposed = true; this.saveState(); - this._toDispose = dispose(this._toDispose); super.dispose(); } } diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index 938b52c8568d..534d110295be 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -288,7 +288,7 @@ export class QueryBuilder { globPortion = normalizeGlobPattern(globPortion); } - // One pathPortion to multiple expanded search paths (eg duplicate matching workspace folders) + // One pathPortion to multiple expanded search paths (e.g. duplicate matching workspace folders) const oneExpanded = this.expandOneSearchPath(pathPortion); // Expanded search paths to multiple resolved patterns (with ** and without) diff --git a/src/vs/workbench/contrib/search/common/search.ts b/src/vs/workbench/contrib/search/common/search.ts index 3391de2a52fc..8238a1821064 100644 --- a/src/vs/workbench/contrib/search/common/search.ts +++ b/src/vs/workbench/contrib/search/common/search.ts @@ -12,6 +12,8 @@ import { URI } from 'vs/base/common/uri'; import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IFileService } from 'vs/platform/files/common/files'; export interface IWorkspaceSymbol { name: string; @@ -81,15 +83,14 @@ export interface IWorkbenchSearchConfiguration extends ISearchConfiguration { /** * Helper to return all opened editors with resources not belonging to the currently opened workspace. */ -export function getOutOfWorkspaceEditorResources(editorService: IEditorService, contextService: IWorkspaceContextService): URI[] { - const resources: URI[] = []; +export function getOutOfWorkspaceEditorResources(accessor: ServicesAccessor): URI[] { + const editorService = accessor.get(IEditorService); + const contextService = accessor.get(IWorkspaceContextService); + const fileService = accessor.get(IFileService); - editorService.editors.forEach(editor => { - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource && !contextService.isInsideWorkspace(resource)) { - resources.push(resource); - } - }); + const resources = editorService.editors + .map(editor => toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })) + .filter(resource => !!resource && !contextService.isInsideWorkspace(resource) && fileService.canHandleResource(resource)); - return resources; + return resources as URI[]; } diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index dd1759d30860..b1a44831b907 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -43,7 +43,7 @@ const languageScopeSchema: IJSONSchema = { type: ['string', 'array'] }, body: { - description: nls.localize('snippetSchema.json.body', 'The snippet content. Use \'$1\', \'${1:defaultText}\' to define cursor positions, use \'$0\' for the final cursor position. Insert variable values with \'${varName}\' and \'${varName:defaultText}\', e.g \'This is file: $TM_FILENAME\'.'), + description: nls.localize('snippetSchema.json.body', 'The snippet content. Use \'$1\', \'${1:defaultText}\' to define cursor positions, use \'$0\' for the final cursor position. Insert variable values with \'${varName}\' and \'${varName:defaultText}\', e.g. \'This is file: $TM_FILENAME\'.'), type: ['string', 'array'], items: { type: 'string' @@ -78,11 +78,11 @@ const globalSchema: IJSONSchema = { type: ['string', 'array'] }, scope: { - description: nls.localize('snippetSchema.json.scope', "A list of language names to which this snippet applies, e.g 'typescript,javascript'."), + description: nls.localize('snippetSchema.json.scope', "A list of language names to which this snippet applies, e.g. 'typescript,javascript'."), type: 'string' }, body: { - description: nls.localize('snippetSchema.json.body', 'The snippet content. Use \'$1\', \'${1:defaultText}\' to define cursor positions, use \'$0\' for the final cursor position. Insert variable values with \'${varName}\' and \'${varName:defaultText}\', e.g \'This is file: $TM_FILENAME\'.'), + description: nls.localize('snippetSchema.json.body', 'The snippet content. Use \'$1\', \'${1:defaultText}\' to define cursor positions, use \'$0\' for the final cursor position. Insert variable values with \'${varName}\' and \'${varName:defaultText}\', e.g. \'This is file: $TM_FILENAME\'.'), type: ['string', 'array'], items: { type: 'string' diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 0391626e0ad9..76256dcaedd6 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -5,7 +5,7 @@ import { join } from 'vs/base/common/path'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { combinedDisposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { combinedDisposable, dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import * as resources from 'vs/base/common/resources'; import { endsWith, isFalsyOrWhitespace } from 'vs/base/common/strings'; @@ -114,7 +114,7 @@ namespace snippetExt { } function watch(service: IFileService, resource: URI, callback: (type: FileChangeType, resource: URI) => any): IDisposable { - return combinedDisposable([ + return combinedDisposable( service.watch(resource), service.onFileChanges(e => { for (const change of e.changes) { @@ -123,7 +123,7 @@ function watch(service: IFileService, resource: URI, callback: (type: FileChange } } }) - ]); + ); } class SnippetsService implements ISnippetsService { @@ -295,15 +295,16 @@ class SnippetsService implements ISnippetsService { } private _initFolderSnippets(source: SnippetSource, folder: URI, bucket: IDisposable[]): Promise { - let disposables: IDisposable[] = []; - let addFolderSnippets = (type?: FileChangeType) => { - disposables = dispose(disposables); + const disposables = new DisposableStore(); + const addFolderSnippets = (type?: FileChangeType) => { + disposables.clear(); + if (type === FileChangeType.DELETED) { return Promise.resolve(); } return this._fileService.resolve(folder).then(stat => { for (const entry of stat.children || []) { - disposables.push(this._addSnippetFile(entry.resource, source)); + disposables.add(this._addSnippetFile(entry.resource, source)); } }, err => { this._logService.error(`Failed snippets from folder '${folder.toString()}'`, err); @@ -311,7 +312,7 @@ class SnippetsService implements ISnippetsService { }; bucket.push(watch(this._fileService, folder, addFolderSnippets)); - bucket.push(combinedDisposable(disposables)); + bucket.push(disposables); return addFolderSnippets(); } diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index b894e27e7a73..b621a31d4a7c 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -3,13 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ipcRenderer as ipc } from 'electron'; import { join } from 'vs/base/common/path'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { getTotalHeight, getTotalWidth } from 'vs/base/browser/dom'; import { Color } from 'vs/base/common/color'; import { Event } from 'vs/base/common/event'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { ColorIdentifier, editorBackground, foreground } from 'vs/platform/theme/common/colorRegistry'; @@ -23,6 +23,8 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { URI } from 'vs/base/common/uri'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import * as perf from 'vs/base/common/performance'; class PartsSplash { @@ -39,19 +41,25 @@ class PartsSplash { @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @ITextFileService private readonly _textFileService: ITextFileService, @IEnvironmentService private readonly _envService: IEnvironmentService, - @IBroadcastService private readonly _broadcastService: IBroadcastService, + @IWindowService private readonly windowService: IWindowService, @ILifecycleService lifecycleService: ILifecycleService, @IEditorGroupsService editorGroupsService: IEditorGroupsService, @IConfigurationService configService: IConfigurationService, ) { - lifecycleService.when(LifecyclePhase.Restored).then(_ => this._removePartsSplash()); + lifecycleService.when(LifecyclePhase.Restored).then(_ => { + this._removePartsSplash(); + perf.mark('didRemovePartsSplash'); + }); Event.debounce(Event.any( onDidChangeFullscreen, editorGroupsService.onDidLayout ), () => { }, 800)(this._savePartsSplash, this, this._disposables); configService.onDidChangeConfiguration(e => { - this._didChangeTitleBarStyle = e.affectsConfiguration('window.titleBarStyle'); + if (e.affectsConfiguration('window.titleBarStyle')) { + this._didChangeTitleBarStyle = true; + this._savePartsSplash(); + } }, this, this._disposables); } @@ -96,7 +104,8 @@ class PartsSplash { // the color needs to be in hex const backgroundColor = this._themeService.getTheme().getColor(editorBackground) || themes.WORKBENCH_BACKGROUND(this._themeService.getTheme()); - this._broadcastService.broadcast({ channel: 'vscode:changeColorTheme', payload: JSON.stringify({ baseTheme, background: Color.Format.CSS.formatHex(backgroundColor) }) }); + const payload = JSON.stringify({ baseTheme, background: Color.Format.CSS.formatHex(backgroundColor) }); + ipc.send('vscode:changeColorTheme', this.windowService.windowId, payload); } } diff --git a/src/vs/workbench/contrib/stats/node/workspaceStats.ts b/src/vs/workbench/contrib/stats/node/workspaceStats.ts index 6aa96cb9c944..2c8f13120c95 100644 --- a/src/vs/workbench/contrib/stats/node/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/node/workspaceStats.ts @@ -458,7 +458,7 @@ export class WorkspaceStats implements IWorkbenchContribution { if (PyModulesToLookFor.indexOf(packageName) > -1) { tags['workspace.py.' + packageName] = true; } - // cognitive services has a lot of tiny packages. eg. 'azure-cognitiveservices-search-autosuggest' + // cognitive services has a lot of tiny packages. e.g. 'azure-cognitiveservices-search-autosuggest' if (packageName.indexOf('azure-cognitiveservices') > -1) { tags['workspace.py.azure-cognitiveservices'] = true; } diff --git a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts index 0b7d44d819ec..00dd2a53dab2 100644 --- a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts +++ b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts @@ -214,7 +214,7 @@ export class QuickOpenActionContributor extends ActionBarContributor { return !!task; } - public getActions(context: any): IAction[] { + public getActions(context: any): ReadonlyArray { let actions: Action[] = []; let task = this.getTask(context); if (task && ContributedTask.is(task) || CustomTask.is(task)) { diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index 36aba9f05982..83f3d00b27e0 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -50,7 +50,7 @@ const taskIdentifier: IJSONSchema = { properties: { type: { type: 'string', - description: nls.localize('JsonSchema.tasks.dependsOn.identifier', 'The task indentifier.') + description: nls.localize('JsonSchema.tasks.dependsOn.identifier', 'The task identifier.') } } }; @@ -77,6 +77,17 @@ const dependsOn: IJSONSchema = { ] }; +const dependsOrder: IJSONSchema = { + type: 'string', + enum: ['parallel', 'sequence'], + enumDescriptions: [ + nls.localize('JsonSchema.tasks.dependsOrder.parallel', 'Run all dependsOn tasks in parallel.'), + nls.localize('JsonSchema.tasks.dependsOrder.sequence', 'Run all dependsOn tasks in sequence.'), + ], + default: 'parallel', + description: nls.localize('JsonSchema.tasks.dependsOrder', 'Determines the order of the dependsOn tasks for this task. Note that this property is not recursive.') +}; + const presentation: IJSONSchema = { type: 'object', default: { @@ -353,6 +364,7 @@ let taskConfiguration: IJSONSchema = { }, runOptions: Objects.deepClone(runOptions), dependsOn: Objects.deepClone(dependsOn), + dependsOrder: Objects.deepClone(dependsOrder) } }; @@ -405,6 +417,7 @@ taskDescriptionProperties.command = Objects.deepClone(command); taskDescriptionProperties.args = Objects.deepClone(args); taskDescriptionProperties.isShellCommand = Objects.deepClone(shellCommand); taskDescriptionProperties.dependsOn = dependsOn; +taskDescriptionProperties.dependsOrder = dependsOrder; taskDescriptionProperties.identifier = Objects.deepClone(identifier); taskDescriptionProperties.type = Objects.deepClone(taskType); taskDescriptionProperties.presentation = Objects.deepClone(presentation); @@ -422,7 +435,7 @@ taskDescription.default = { problemMatcher: [] }; definitions.showOutputType.deprecationMessage = nls.localize( - 'JsonSchema.tasks.showOputput.deprecated', + 'JsonSchema.tasks.showOutput.deprecated', 'The property showOutput is deprecated. Use the reveal property inside the presentation property instead. See also the 1.14 release notes.' ); taskDescriptionProperties.echoCommand.deprecationMessage = nls.localize( diff --git a/src/vs/workbench/contrib/tasks/common/media/status-error.svg b/src/vs/workbench/contrib/tasks/common/media/status-error.svg deleted file mode 100644 index 61b16362cb1f..000000000000 --- a/src/vs/workbench/contrib/tasks/common/media/status-error.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/tasks/common/media/status-info.svg b/src/vs/workbench/contrib/tasks/common/media/status-info.svg deleted file mode 100644 index 16306356ba04..000000000000 --- a/src/vs/workbench/contrib/tasks/common/media/status-info.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/tasks/common/media/status-warning.svg b/src/vs/workbench/contrib/tasks/common/media/status-warning.svg deleted file mode 100644 index 5951d69539a6..000000000000 --- a/src/vs/workbench/contrib/tasks/common/media/status-warning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/tasks/common/media/task.contribution.css b/src/vs/workbench/contrib/tasks/common/media/task.contribution.css index 5ffde87453e8..fcf2ad0ee6c2 100644 --- a/src/vs/workbench/contrib/tasks/common/media/task.contribution.css +++ b/src/vs/workbench/contrib/tasks/common/media/task.contribution.css @@ -3,80 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.task-statusbar-runningItem { - display: inline-block; -} - -.task-statusbar-runningItem-label { - display: inline-block; - cursor: pointer; - padding: 0 5px 0 5px; -} - -.task-statusbar-runningItem-label > .octicon { - font-size: 11px; - vertical-align: -webkit-baseline-middle; - height: 19px; -} - -.task-statusbar-item { - display: inline-block; -} - -.task-statusbar-item-icon { - background: url('task.svg') 50% 2px no-repeat; - background-size: 18px; - cursor: pointer; - height: 22px; - width: 24px; - vertical-align: top; -} - -.task-statusbar-item-building { - height: 18px; - padding: 0px 2px 0px 2px; - display: inline-block; - text-align: center; - vertical-align: top; -} - -.task-statusbar-item-label { - display: inline-block; - cursor: pointer; - padding: 0 5px 0 5px; -} - -.task-statusbar-item-label > .task-statusbar-item-label-counter { - display: inline-block; - vertical-align: top; - padding-left: 2px; - padding-right: 2px; -} - -.task-statusbar-item-label > .task-statusbar-item-label-error, -.task-statusbar-item-label > .task-statusbar-item-label-warning, -.task-statusbar-item-label > .task-statusbar-item-label-info { - display: inline-block; - padding-right: 8px; - width: 8px; - height: 22px; -} - -.task-statusbar-item-label > .task-statusbar-item-label-error { - -webkit-mask: url('status-error.svg') no-repeat 50% 50%; - -webkit-mask-size: 11px; -} - -.task-statusbar-item-label > .task-statusbar-item-label-warning { - -webkit-mask: url('status-warning.svg') no-repeat 50% 50%; - -webkit-mask-size: 11px; -} - -.task-statusbar-item-label > .task-statusbar-item-label-info { - -webkit-mask: url('status-info.svg') no-repeat 50% 50%; - -webkit-mask-size: 11px; -} - .monaco-workbench .quick-open-task-configure { background-image: url('configure.svg'); } diff --git a/src/vs/workbench/contrib/tasks/common/media/task.svg b/src/vs/workbench/contrib/tasks/common/media/task.svg deleted file mode 100644 index 7692f495b328..000000000000 --- a/src/vs/workbench/contrib/tasks/common/media/task.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index 729eb45c0222..d396a79dbcaa 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -13,6 +13,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ILineMatcher, createLineMatcher, ProblemMatcher, ProblemMatch, ApplyToKind, WatchingPattern, getResource } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IMarkerService, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { generateUuid } from 'vs/base/common/uuid'; +import { IFileService } from 'vs/platform/files/common/files'; export const enum ProblemCollectorEventKind { BackgroundProcessingBegins = 'backgroundProcessingBegins', @@ -33,7 +34,7 @@ export interface IProblemMatcher { processLine(line: string): void; } -export class AbstractProblemCollector implements IDisposable { +export abstract class AbstractProblemCollector implements IDisposable { private matchers: INumberDictionary; private activeMatcher: ILineMatcher | null; @@ -43,8 +44,9 @@ export class AbstractProblemCollector implements IDisposable { private bufferLength: number; private openModels: IStringDictionary; private modelListeners: IDisposable[]; + private tail: Promise; - // [owner] -> AppyToKind + // [owner] -> ApplyToKind private applyToByOwner: Map; // [owner] -> [resource] -> URI private resourcesToClean: Map>; @@ -55,10 +57,10 @@ export class AbstractProblemCollector implements IDisposable { protected _onDidStateChange: Emitter; - constructor(problemMatchers: ProblemMatcher[], protected markerService: IMarkerService, private modelService: IModelService) { + constructor(problemMatchers: ProblemMatcher[], protected markerService: IMarkerService, private modelService: IModelService, fileService?: IFileService) { this.matchers = Object.create(null); this.bufferLength = 1; - problemMatchers.map(elem => createLineMatcher(elem)).forEach((matcher) => { + problemMatchers.map(elem => createLineMatcher(elem, fileService)).forEach((matcher) => { let length = matcher.matchLength; if (length > this.bufferLength) { this.bufferLength = length; @@ -103,6 +105,19 @@ export class AbstractProblemCollector implements IDisposable { return this._onDidStateChange.event; } + public processLine(line: string) { + if (this.tail) { + const oldTail = this.tail; + this.tail = oldTail.then(() => { + return this.processLineInternal(line); + }); + } else { + this.tail = this.processLineInternal(line); + } + } + + protected abstract async processLineInternal(line: string): Promise; + public dispose() { this.modelListeners.forEach(disposable => disposable.dispose()); } @@ -143,14 +158,14 @@ export class AbstractProblemCollector implements IDisposable { return result; } - protected shouldApplyMatch(result: ProblemMatch): boolean { + protected async shouldApplyMatch(result: ProblemMatch): Promise { switch (result.description.applyTo) { case ApplyToKind.allDocuments: return true; case ApplyToKind.openDocuments: - return !!this.openModels[result.resource.toString()]; + return !!this.openModels[(await result.resource).toString()]; case ApplyToKind.closedDocuments: - return !this.openModels[result.resource.toString()]; + return !this.openModels[(await result.resource).toString()]; default: return true; } @@ -272,9 +287,9 @@ export class AbstractProblemCollector implements IDisposable { protected reportMarkers(): void { this.markers.forEach((markersPerOwner, owner) => { - let develieredMarkersPerOwner = this.getDeliveredMarkersPerOwner(owner); + let deliveredMarkersPerOwner = this.getDeliveredMarkersPerOwner(owner); markersPerOwner.forEach((markers, resource) => { - this.deliverMarkersPerOwnerAndResourceResolved(owner, resource, markers, develieredMarkersPerOwner); + this.deliverMarkersPerOwnerAndResourceResolved(owner, resource, markers, deliveredMarkersPerOwner); }); }); } @@ -333,8 +348,8 @@ export class StartStopProblemCollector extends AbstractProblemCollector implemen private currentOwner: string; private currentResource: string; - constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, _strategy: ProblemHandlingStrategy = ProblemHandlingStrategy.Clean) { - super(problemMatchers, markerService, modelService); + constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, _strategy: ProblemHandlingStrategy = ProblemHandlingStrategy.Clean, fileService?: IFileService) { + super(problemMatchers, markerService, modelService, fileService); let ownerSet: { [key: string]: boolean; } = Object.create(null); problemMatchers.forEach(description => ownerSet[description.owner] = true); this.owners = Object.keys(ownerSet); @@ -343,17 +358,17 @@ export class StartStopProblemCollector extends AbstractProblemCollector implemen }); } - public processLine(line: string): void { + protected async processLineInternal(line: string): Promise { let markerMatch = this.tryFindMarker(line); if (!markerMatch) { return; } let owner = markerMatch.description.owner; - let resource = markerMatch.resource; + let resource = await markerMatch.resource; let resourceAsString = resource.toString(); this.removeResourceToClean(owner, resourceAsString); - let shouldApplyMatch = this.shouldApplyMatch(markerMatch); + let shouldApplyMatch = await this.shouldApplyMatch(markerMatch); if (shouldApplyMatch) { this.recordMarker(markerMatch.marker, owner, resourceAsString); if (this.currentOwner !== owner || this.currentResource !== resourceAsString) { @@ -386,8 +401,8 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement private currentOwner: string | null; private currentResource: string | null; - constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService) { - super(problemMatchers, markerService, modelService); + constructor(problemMatchers: ProblemMatcher[], markerService: IMarkerService, modelService: IModelService, fileService?: IFileService) { + super(problemMatchers, markerService, modelService, fileService); this.problemMatchers = problemMatchers; this.resetCurrentResource(); this.backgroundPatterns = []; @@ -415,19 +430,19 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement } } - public processLine(line: string): void { - if (this.tryBegin(line) || this.tryFinish(line)) { + protected async processLineInternal(line: string): Promise { + if (await this.tryBegin(line) || this.tryFinish(line)) { return; } let markerMatch = this.tryFindMarker(line); if (!markerMatch) { return; } - let resource = markerMatch.resource; + let resource = await markerMatch.resource; let owner = markerMatch.description.owner; let resourceAsString = resource.toString(); this.removeResourceToClean(owner, resourceAsString); - let shouldApplyMatch = this.shouldApplyMatch(markerMatch); + let shouldApplyMatch = await this.shouldApplyMatch(markerMatch); if (shouldApplyMatch) { this.recordMarker(markerMatch.marker, owner, resourceAsString); if (this.currentOwner !== owner || this.currentResource !== resourceAsString) { @@ -442,7 +457,7 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement this.reportMarkersForCurrentResource(); } - private tryBegin(line: string): boolean { + private async tryBegin(line: string): Promise { let result = false; for (const background of this.backgroundPatterns) { let matches = background.begin.regexp.exec(line); @@ -459,7 +474,7 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement let file = matches[background.begin.file!]; if (file) { let resource = getResource(file, background.matcher); - this.recordResourceToClean(owner, resource); + this.recordResourceToClean(owner, await resource); } else { this.recordResourcesToClean(owner); } diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index a52b738698fd..939895074e9d 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -21,11 +21,13 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { Event, Emitter } from 'vs/base/common/event'; +import { IFileService, IFileStat } from 'vs/platform/files/common/files'; export enum FileLocationKind { - Auto, + Default, Relative, - Absolute + Absolute, + AutoDetect } export module FileLocationKind { @@ -35,6 +37,8 @@ export module FileLocationKind { return FileLocationKind.Absolute; } else if (value === 'relative') { return FileLocationKind.Relative; + } else if (value === 'autodetect') { + return FileLocationKind.AutoDetect; } else { return undefined; } @@ -172,7 +176,7 @@ interface ProblemData { } export interface ProblemMatch { - resource: URI; + resource: Promise; marker: IMarkerData; description: ProblemMatcher; } @@ -182,13 +186,32 @@ export interface HandleResult { continue: boolean; } -export function getResource(filename: string, matcher: ProblemMatcher): URI { + +export async function getResource(filename: string, matcher: ProblemMatcher, fileService?: IFileService): Promise { let kind = matcher.fileLocation; let fullPath: string | undefined; if (kind === FileLocationKind.Absolute) { fullPath = filename; } else if ((kind === FileLocationKind.Relative) && matcher.filePrefix) { fullPath = join(matcher.filePrefix, filename); + } else if (kind === FileLocationKind.AutoDetect) { + const matcherClone = Objects.deepClone(matcher); + matcherClone.fileLocation = FileLocationKind.Relative; + if (fileService) { + const relative = await getResource(filename, matcherClone); + let stat: IFileStat | undefined = undefined; + try { + stat = await fileService.resolve(relative); + } catch (ex) { + // Do nothing, we just need to catch file resolution errors. + } + if (stat) { + return relative; + } + } + + matcherClone.fileLocation = FileLocationKind.Absolute; + return getResource(filename, matcherClone); } if (fullPath === undefined) { throw new Error('FileLocationKind is not actionable. Does the matcher have a filePrefix? This should never happen.'); @@ -210,12 +233,12 @@ export interface ILineMatcher { handle(lines: string[], start?: number): HandleResult; } -export function createLineMatcher(matcher: ProblemMatcher): ILineMatcher { +export function createLineMatcher(matcher: ProblemMatcher, fileService?: IFileService): ILineMatcher { let pattern = matcher.pattern; if (Types.isArray(pattern)) { - return new MultiLineMatcher(matcher); + return new MultiLineMatcher(matcher, fileService); } else { - return new SingleLineMatcher(matcher); + return new SingleLineMatcher(matcher, fileService); } } @@ -223,9 +246,11 @@ const endOfLine: string = Platform.OS === Platform.OperatingSystem.Windows ? '\r abstract class AbstractLineMatcher implements ILineMatcher { private matcher: ProblemMatcher; + private fileService?: IFileService; - constructor(matcher: ProblemMatcher) { + constructor(matcher: ProblemMatcher, fileService?: IFileService) { this.matcher = matcher; + this.fileService = fileService; } public handle(lines: string[], start: number = 0): HandleResult { @@ -312,8 +337,8 @@ abstract class AbstractLineMatcher implements ILineMatcher { return undefined; } - protected getResource(filename: string): URI { - return getResource(filename, this.matcher); + protected getResource(filename: string): Promise { + return getResource(filename, this.matcher, this.fileService); } private getLocation(data: ProblemData): Location | null { @@ -389,8 +414,8 @@ class SingleLineMatcher extends AbstractLineMatcher { private pattern: ProblemPattern; - constructor(matcher: ProblemMatcher) { - super(matcher); + constructor(matcher: ProblemMatcher, fileService?: IFileService) { + super(matcher, fileService); this.pattern = matcher.pattern; } @@ -425,8 +450,8 @@ class MultiLineMatcher extends AbstractLineMatcher { private patterns: ProblemPattern[]; private data: ProblemData | null; - constructor(matcher: ProblemMatcher) { - super(matcher); + constructor(matcher: ProblemMatcher, fileService?: IFileService) { + super(matcher, fileService); this.patterns = matcher.pattern; } @@ -1345,7 +1370,7 @@ export class ProblemMatcherParser extends Parser { kind = FileLocationKind.fromString(description.fileLocation); if (kind) { fileLocation = kind; - if (kind === FileLocationKind.Relative) { + if ((kind === FileLocationKind.Relative) || (kind === FileLocationKind.AutoDetect)) { filePrefix = '${workspaceFolder}'; } } @@ -1355,7 +1380,7 @@ export class ProblemMatcherParser extends Parser { kind = FileLocationKind.fromString(values[0]); if (values.length === 1 && kind === FileLocationKind.Absolute) { fileLocation = kind; - } else if (values.length === 2 && kind === FileLocationKind.Relative && values[1]) { + } else if (values.length === 2 && (kind === FileLocationKind.Relative || kind === FileLocationKind.AutoDetect) && values[1]) { fileLocation = kind; filePrefix = values[1]; } @@ -1573,7 +1598,7 @@ export namespace Schemas { oneOf: [ { type: 'string', - enum: ['absolute', 'relative'] + enum: ['absolute', 'relative', 'autoDetect'] }, { type: 'array', diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 2ccc9984f8e1..96abed34502d 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -312,6 +312,11 @@ export interface ConfigurationProperties { */ dependsOn?: string | TaskIdentifier | Array; + /** + * The order the dependsOn tasks should be executed in. + */ + dependsOrder?: string; + /** * Controls the behavior of the used terminal */ @@ -1079,7 +1084,7 @@ namespace CommandConfiguration { value.args = EMPTY_ARRAY; } if (value.suppressTaskName === undefined) { - value.suppressTaskName = false; + value.suppressTaskName = (context.schemaVersion === Tasks.JsonSchemaVersion.V2_0_0); } } @@ -1214,6 +1219,18 @@ namespace TaskDependency { } } +namespace DependsOrder { + export function from(order: string | undefined): Tasks.DependsOrder { + switch (order) { + case Tasks.DependsOrder.sequence: + return Tasks.DependsOrder.sequence; + case Tasks.DependsOrder.parallel: + default: + return Tasks.DependsOrder.parallel; + } + } +} + namespace ConfigurationProperties { const properties: MetaData[] = [ @@ -1269,6 +1286,7 @@ namespace ConfigurationProperties { result.dependsOn = dependsOnValue ? [dependsOnValue] : undefined; } } + result.dependsOrder = DependsOrder.from(external.dependsOrder); if (includeCommandOptions && (external.presentation !== undefined || (external as LegacyCommandProperties).terminal !== undefined)) { result.presentation = CommandConfiguration.PresentationOptions.from(external, context); } @@ -1389,7 +1407,6 @@ namespace ConfiguringTask { } namespace CustomTask { - export function from(this: void, external: CustomTask, context: ParseContext, index: number): Tasks.CustomTask | undefined { if (!external) { return undefined; @@ -1711,7 +1728,7 @@ namespace Globals { } CommandConfiguration.fillDefaults(value.command, context); if (value.suppressTaskName === undefined) { - value.suppressTaskName = false; + value.suppressTaskName = (context.schemaVersion === Tasks.JsonSchemaVersion.V2_0_0); } if (value.promptOnClose === undefined) { value.promptOnClose = true; diff --git a/src/vs/workbench/contrib/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts index a5ebe2354831..2f3b12bf39d0 100644 --- a/src/vs/workbench/contrib/tasks/common/taskSystem.ts +++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts @@ -121,7 +121,7 @@ export interface TaskSystemInfo { resolveVariables(workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet): Promise; } -export interface TaskSystemInfoResovler { +export interface TaskSystemInfoResolver { (workspaceFolder: IWorkspaceFolder): TaskSystemInfo | undefined; } diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 8eb48222aacb..607bc0f1076a 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -431,6 +431,11 @@ export const enum GroupType { user = 'user' } +export const enum DependsOrder { + parallel = 'parallel', + sequence = 'sequence' +} + export interface ConfigurationProperties { /** @@ -478,6 +483,11 @@ export interface ConfigurationProperties { */ dependsOn?: TaskDependency[]; + /** + * The order the dependsOn tasks should be executed in. + */ + dependsOrder?: DependsOrder; + /** * The problem watchers to use for this task */ @@ -607,7 +617,7 @@ export class CustomTask extends CommonTask { type: '$customized'; // CUSTOMIZED_TASK_TYPE /** - * Indicated the source of the task (e.g tasks.json or extension) + * Indicated the source of the task (e.g. tasks.json or extension) */ _source: WorkspaceTaskSource; @@ -714,7 +724,7 @@ export class CustomTask extends CommonTask { export class ConfiguringTask extends CommonTask { /** - * Indicated the source of the task (e.g tasks.json or extension) + * Indicated the source of the task (e.g. tasks.json or extension) */ _source: WorkspaceTaskSource; @@ -740,7 +750,7 @@ export class ConfiguringTask extends CommonTask { export class ContributedTask extends CommonTask { /** - * Indicated the source of the task (e.g tasks.json or extension) + * Indicated the source of the task (e.g. tasks.json or extension) */ _source: ExtensionTaskSource; @@ -807,7 +817,7 @@ export class ContributedTask extends CommonTask { export class InMemoryTask extends CommonTask { /** - * Indicated the source of the task (e.g tasks.json or extension) + * Indicated the source of the task (e.g. tasks.json or extension) */ _source: InMemoryTaskSource; diff --git a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts index 7622c16e6f0b..b499f3e41ca5 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts @@ -14,8 +14,7 @@ import * as Objects from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; import { Action } from 'vs/base/common/actions'; -import * as Dom from 'vs/base/browser/dom'; -import { IDisposable, dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as Types from 'vs/base/common/types'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; @@ -25,13 +24,12 @@ import { ValidationStatus, ValidationState } from 'vs/base/common/parsers'; import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; import { LinkedMap, Touch } from 'vs/base/common/map'; -import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { MenuRegistry, MenuId, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IFileService, IFileStat } from 'vs/platform/files/common/files'; @@ -40,7 +38,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ProblemMatcherRegistry, NamedProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IProgressService2, IProgressOptions, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, IProgressOptions, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -52,8 +50,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { IStatusbarItem, IStatusbarRegistry, Extensions as StatusbarExtensions, StatusbarItemDescriptor } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -85,13 +82,11 @@ import { TerminalTaskSystem } from './terminalTaskSystem'; import { ProcessRunnerDetector } from 'vs/workbench/contrib/tasks/node/processRunnerDetector'; import { QuickOpenActionContributor } from '../browser/quickOpen'; -import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { RunAutomaticTasks, AllowAutomaticTaskRunning, DisallowAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/electron-browser/runAutomaticTasks'; @@ -106,247 +101,118 @@ const actionRegistry = Registry.as(ActionExtensions.Wo actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowAutomaticTaskRunning, AllowAutomaticTaskRunning.ID, AllowAutomaticTaskRunning.LABEL), 'Tasks: Allow Automatic Tasks in Folder', tasksCategory); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowAutomaticTaskRunning, DisallowAutomaticTaskRunning.ID, DisallowAutomaticTaskRunning.LABEL), 'Tasks: Disallow Automatic Tasks in Folder', tasksCategory); - namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; export const TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task"); } -class BuildStatusBarItem extends Themable implements IStatusbarItem { - private activeCount: number; - private icons: HTMLElement[]; +export class TaskStatusBarContributions extends Disposable implements IWorkbenchContribution { + private runningTasksStatusItem: IStatusbarEntryAccessor | undefined; + private activeTasksCount: number = 0; constructor( - @IPanelService private readonly panelService: IPanelService, - @IMarkerService private readonly markerService: IMarkerService, @ITaskService private readonly taskService: ITaskService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IThemeService themeService: IThemeService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService + @IStatusbarService private readonly statusbarService: IStatusbarService, + @IProgressService private readonly progressService: IProgressService ) { - super(themeService); - - this.activeCount = 0; - this.icons = []; - + super(); this.registerListeners(); } private registerListeners(): void { - this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles())); - } + let promise: Promise | undefined = undefined; + let resolver: (value?: void | Thenable) => void; + this.taskService.onDidStateChange(event => { + if (event.kind === TaskEventKind.Changed) { + this.updateRunningTasksStatus(); + } - protected updateStyles(): void { - super.updateStyles(); + if (!this.ignoreEventForUpdateRunningTasksCount(event)) { + switch (event.kind) { + case TaskEventKind.Active: + this.activeTasksCount++; + if (this.activeTasksCount === 1) { + if (!promise) { + promise = new Promise((resolve) => { + resolver = resolve; + }); + } + } + break; + case TaskEventKind.Inactive: + // Since the exiting of the sub process is communicated async we can't order inactive and terminate events. + // So try to treat them accordingly. + if (this.activeTasksCount > 0) { + this.activeTasksCount--; + if (this.activeTasksCount === 0) { + if (promise && resolver!) { + resolver!(); + } + } + } + break; + case TaskEventKind.Terminated: + if (this.activeTasksCount !== 0) { + this.activeTasksCount = 0; + if (promise && resolver!) { + resolver!(); + } + } + break; + } + } - this.icons.forEach(icon => { - icon.style.backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND); + if (promise && (event.kind === TaskEventKind.Active) && (this.activeTasksCount === 1)) { + this.progressService.withProgress({ location: ProgressLocation.Window }, progress => { + progress.report({ message: nls.localize('building', 'Building...') }); + return promise!; + }).then(() => { + promise = undefined; + }); + } }); } - public render(container: HTMLElement): IDisposable { - let callOnDispose: IDisposable[] = []; - - const element = document.createElement('div'); - const label = document.createElement('a'); - const errorIcon = document.createElement('div'); - const warningIcon = document.createElement('div'); - const infoIcon = document.createElement('div'); - const error = document.createElement('div'); - const warning = document.createElement('div'); - const info = document.createElement('div'); - const building = document.createElement('div'); - - const errorTitle = (n: number) => nls.localize('totalErrors', "{0} Errors", n); - const warningTitle = (n: number) => nls.localize('totalWarnings', "{0} Warnings", n); - const infoTitle = (n: number) => nls.localize('totalInfos', "{0} Infos", n); - - Dom.addClass(element, 'task-statusbar-item'); - element.title = nls.localize('problems', "Problems"); - - Dom.addClass(label, 'task-statusbar-item-label'); - element.appendChild(label); - - Dom.addClass(errorIcon, 'task-statusbar-item-label-error'); - Dom.addClass(errorIcon, 'mask-icon'); - label.appendChild(errorIcon); - this.icons.push(errorIcon); - - Dom.addClass(error, 'task-statusbar-item-label-counter'); - error.innerHTML = '0'; - error.title = errorIcon.title = errorTitle(0); - label.appendChild(error); - - Dom.addClass(warningIcon, 'task-statusbar-item-label-warning'); - Dom.addClass(warningIcon, 'mask-icon'); - label.appendChild(warningIcon); - this.icons.push(warningIcon); - - Dom.addClass(warning, 'task-statusbar-item-label-counter'); - warning.innerHTML = '0'; - warning.title = warningIcon.title = warningTitle(0); - label.appendChild(warning); - - Dom.addClass(infoIcon, 'task-statusbar-item-label-info'); - Dom.addClass(infoIcon, 'mask-icon'); - label.appendChild(infoIcon); - this.icons.push(infoIcon); - Dom.hide(infoIcon); - - Dom.addClass(info, 'task-statusbar-item-label-counter'); - label.appendChild(info); - Dom.hide(info); - - Dom.addClass(building, 'task-statusbar-item-building'); - element.appendChild(building); - building.innerHTML = nls.localize('building', 'Building...'); - Dom.hide(building); - - callOnDispose.push(Dom.addDisposableListener(label, 'click', (e: MouseEvent) => { - const panel = this.panelService.getActivePanel(); - if (panel && panel.getId() === Constants.MARKERS_PANEL_ID) { - this.layoutService.setPanelHidden(true); - } else { - this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true); + private async updateRunningTasksStatus(): Promise { + const tasks = await this.taskService.getActiveTasks(); + if (tasks.length === 0) { + if (this.runningTasksStatusItem) { + this.runningTasksStatusItem.dispose(); + this.runningTasksStatusItem = undefined; } - })); + } else { + const itemProps: IStatusbarEntry = { + text: `$(tools) ${tasks.length}`, + tooltip: nls.localize('runningTasks', "Show Running Tasks"), + command: 'workbench.action.tasks.showTasks', + }; - const manyProblems = nls.localize('manyProblems', "10K+"); - const packNumber = (n: number) => n > 9999 ? manyProblems : n > 999 ? n.toString().charAt(0) + 'K' : n.toString(); - let updateLabel = (stats: MarkerStatistics) => { - error.innerHTML = packNumber(stats.errors); - error.title = errorIcon.title = errorTitle(stats.errors); - warning.innerHTML = packNumber(stats.warnings); - warning.title = warningIcon.title = warningTitle(stats.warnings); - if (stats.infos > 0) { - info.innerHTML = packNumber(stats.infos); - info.title = infoIcon.title = infoTitle(stats.infos); - Dom.show(info); - Dom.show(infoIcon); + if (!this.runningTasksStatusItem) { + this.runningTasksStatusItem = this.statusbarService.addEntry(itemProps, 'status.runningTasks', nls.localize('status.runningTasks', "Running Tasks"), StatusbarAlignment.LEFT, 50 /* Medium Priority */); } else { - Dom.hide(info); - Dom.hide(infoIcon); - } - }; - - this.markerService.onMarkerChanged((changedResources) => { - updateLabel(this.markerService.getStatistics()); - }); - - callOnDispose.push(this.taskService.onDidStateChange((event) => { - if (this.ignoreEvent(event)) { - return; - } - switch (event.kind) { - case TaskEventKind.Active: - this.activeCount++; - if (this.activeCount === 1) { - Dom.show(building); - } - break; - case TaskEventKind.Inactive: - // Since the exiting of the sub process is communicated async we can't order inactive and terminate events. - // So try to treat them accordingly. - if (this.activeCount > 0) { - this.activeCount--; - if (this.activeCount === 0) { - Dom.hide(building); - } - } - break; - case TaskEventKind.Terminated: - if (this.activeCount !== 0) { - Dom.hide(building); - this.activeCount = 0; - } - break; + this.runningTasksStatusItem.update(itemProps); } - })); - - container.appendChild(element); - - this.updateStyles(); - - return toDisposable(() => { - callOnDispose = dispose(callOnDispose); - }); + } } - private ignoreEvent(event: TaskEvent): boolean { + private ignoreEventForUpdateRunningTasksCount(event: TaskEvent): boolean { if (!this.taskService.inTerminal()) { return false; } + if (event.group !== TaskGroup.Build) { return true; } + if (!event.__task) { return false; } + return event.__task.configurationProperties.problemMatchers === undefined || event.__task.configurationProperties.problemMatchers.length === 0; } } -class TaskStatusBarItem extends Themable implements IStatusbarItem { - - constructor( - @ITaskService private readonly taskService: ITaskService, - @IThemeService themeService: IThemeService, - ) { - super(themeService); - } - - protected updateStyles(): void { - super.updateStyles(); - } - - public render(container: HTMLElement): IDisposable { - - let callOnDispose: IDisposable[] = []; - const element = document.createElement('a'); - Dom.addClass(element, 'task-statusbar-runningItem'); - - let labelElement = document.createElement('div'); - Dom.addClass(labelElement, 'task-statusbar-runningItem-label'); - element.appendChild(labelElement); - - let label = new OcticonLabel(labelElement); - label.title = nls.localize('runningTasks', "Show Running Tasks"); - - Dom.hide(element); - - callOnDispose.push(Dom.addDisposableListener(labelElement, 'click', (e: MouseEvent) => { - (this.taskService as TaskService).runShowTasks(); - })); - - let updateStatus = (): void => { - this.taskService.getActiveTasks().then(tasks => { - if (tasks.length === 0) { - Dom.hide(element); - } else { - label.text = `$(tools) ${tasks.length}`; - Dom.show(element); - } - }); - }; - - callOnDispose.push(this.taskService.onDidStateChange((event) => { - if (event.kind === TaskEventKind.Changed) { - updateStatus(); - } - })); - - container.appendChild(element); - - this.updateStyles(); - updateStatus(); - - return { - dispose: () => { - callOnDispose = dispose(callOnDispose); - } - }; - } -} +workbenchRegistry.registerWorkbenchContribution(TaskStatusBarContributions, LifecyclePhase.Restored); class ProblemReporter implements TaskConfig.IProblemReporter { @@ -482,13 +348,14 @@ class TaskService extends Disposable implements ITaskService { @IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService, @ITerminalService private readonly terminalService: ITerminalService, @IStorageService private readonly storageService: IStorageService, - @IProgressService2 private readonly progressService: IProgressService2, + @IProgressService private readonly progressService: IProgressService, @IOpenerService private readonly openerService: IOpenerService, @IWindowService private readonly _windowService: IWindowService, @IDialogService private readonly dialogService: IDialogService, @INotificationService private readonly notificationService: INotificationService, @IContextKeyService contextKeyService: IContextKeyService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(); @@ -623,6 +490,15 @@ class TaskService extends Disposable implements ITaskService { CommandsRegistry.registerCommand('workbench.action.tasks.showTasks', () => { this.runShowTasks(); }); + + CommandsRegistry.registerCommand('workbench.action.tasks.toggleProblems', () => { + const panel = this.panelService.getActivePanel(); + if (panel && panel.getId() === Constants.MARKERS_PANEL_ID) { + this.layoutService.setPanelHidden(true); + } else { + this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true); + } + }); } private get workspaceFolders(): IWorkspaceFolder[] { @@ -1358,8 +1234,8 @@ class TaskService extends Disposable implements ITaskService { this._taskSystem = new TerminalTaskSystem( this.terminalService, this.outputService, this.panelService, this.markerService, this.modelService, this.configurationResolverService, this.telemetryService, - this.contextService, this._environmentService, - TaskService.OutputChannelId, + this.contextService, this.environmentService, + TaskService.OutputChannelId, this.fileService, (workspaceFolder: IWorkspaceFolder) => { if (!workspaceFolder) { return undefined; @@ -2710,11 +2586,6 @@ quickOpenRegistry.registerQuickOpenHandler( const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor); -// Status bar -let statusbarRegistry = Registry.as(StatusbarExtensions.Statusbar); -statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(BuildStatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */)); -statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(TaskStatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */)); - // tasks.json validation let schemaId = 'vscode://schemas/tasks'; let schema: IJSONSchema = { diff --git a/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts index 9d10fe268190..c94d0fe6fa2f 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts @@ -17,7 +17,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { isUNC } from 'vs/base/common/extpath'; import { win32 } from 'vs/base/node/processes'; - +import { IFileService } from 'vs/platform/files/common/files'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -29,14 +29,14 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; -import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind } from 'vs/workbench/contrib/tasks/common/problemCollectors'; +import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { Task, CustomTask, ContributedTask, RevealKind, CommandOptions, ShellConfiguration, RuntimeType, PanelKind, - TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString, CommandConfiguration, ExtensionTaskSource, TaskScope, RevealProblemKind + TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString, CommandConfiguration, ExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver, - TelemetryEvent, Triggers, TaskTerminateResponse, TaskSystemInfoResovler, TaskSystemInfo, ResolveSet, ResolvedVariables + TelemetryEvent, Triggers, TaskTerminateResponse, TaskSystemInfoResolver, TaskSystemInfo, ResolveSet, ResolvedVariables } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { URI } from 'vs/base/common/uri'; @@ -154,7 +154,7 @@ export class TerminalTaskSystem implements ITaskSystem { private terminals: IStringDictionary; private idleTaskTerminals: LinkedMap; private sameTaskTerminals: IStringDictionary; - private taskSystemInfoResolver: TaskSystemInfoResovler; + private taskSystemInfoResolver: TaskSystemInfoResolver; private lastTask: VerifiedTask; private currentTask: VerifiedTask; private isRerun: boolean; @@ -171,7 +171,8 @@ export class TerminalTaskSystem implements ITaskSystem { private contextService: IWorkspaceContextService, private environmentService: IWorkbenchEnvironmentService, private outputChannelId: string, - taskSystemInfoResolver: TaskSystemInfoResovler, + private fileService: IFileService, + taskSystemInfoResolver: TaskSystemInfoResolver, ) { this.activeTasks = Object.create(null); @@ -332,10 +333,11 @@ export class TerminalTaskSystem implements ITaskSystem { return Promise.all(promises); } - private executeTask(task: Task, resolver: ITaskResolver, trigger: string): Promise { + private async executeTask(task: Task, resolver: ITaskResolver, trigger: string): Promise { let promises: Promise[] = []; if (task.configurationProperties.dependsOn) { - task.configurationProperties.dependsOn.forEach((dependency) => { + for (let index in task.configurationProperties.dependsOn) { + const dependency = task.configurationProperties.dependsOn[index]; let dependencyTask = resolver.resolve(dependency.workspaceFolder, dependency.task!); if (dependencyTask) { let key = dependencyTask.getMapKey(); @@ -344,6 +346,9 @@ export class TerminalTaskSystem implements ITaskSystem { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); promise = this.executeTask(dependencyTask, resolver, trigger); } + if (task.configurationProperties.dependsOrder === DependsOrder.sequence) { + promise = Promise.resolve(await promise); + } promises.push(promise); } else { this.log(nls.localize('dependencyFailed', @@ -353,7 +358,7 @@ export class TerminalTaskSystem implements ITaskSystem { )); this.showOutput(); } - }); + } } if ((ContributedTask.is(task) || CustomTask.is(task)) && (task.command)) { @@ -424,12 +429,12 @@ export class TerminalTaskSystem implements ITaskSystem { variables.forEach(variable => variablesArray.push(variable)); return new Promise((resolve, reject) => { - this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks').then(resolvedVariablesMap => { + this.configurationResolverService.resolveWithInteraction(workspaceFolder, variablesArray, 'tasks').then(async resolvedVariablesMap => { if (resolvedVariablesMap) { if (isProcess) { let processVarValue: string; if (Platform.isWindows) { - processVarValue = win32.findExecutable( + processVarValue = await win32.findExecutable( this.configurationResolverService.resolve(workspaceFolder, CommandString.value(task.command.name!)), cwd ? this.configurationResolverService.resolve(workspaceFolder, cwd) : undefined, envPath ? envPath.split(path.delimiter).map(p => this.configurationResolverService.resolve(workspaceFolder, p)) : undefined @@ -509,7 +514,7 @@ export class TerminalTaskSystem implements ITaskSystem { if (task.configurationProperties.isBackground) { promise = new Promise((resolve, reject) => { const problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); - let watchingProblemMatcher = new WatchingProblemCollector(problemMatchers, this.markerService, this.modelService); + let watchingProblemMatcher = new WatchingProblemCollector(problemMatchers, this.markerService, this.modelService, this.fileService); let toDispose: IDisposable[] | undefined = []; let eventCounter: number = 0; toDispose.push(watchingProblemMatcher.onDidStateChange((event) => { @@ -630,7 +635,7 @@ export class TerminalTaskSystem implements ITaskSystem { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); let problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); - let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService); + let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService, ProblemHandlingStrategy.Clean, this.fileService); const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers); const onData = terminal.onLineData((line) => { startStopProblemMatcher.processLine(line); @@ -1230,7 +1235,7 @@ export class TerminalTaskSystem implements ITaskSystem { matcher = value; } if (!matcher) { - this.appendOutput(nls.localize('unkownProblemMatcher', 'Problem matcher {0} can\'t be resolved. The matcher will be ignored')); + this.appendOutput(nls.localize('unknownProblemMatcher', 'Problem matcher {0} can\'t be resolved. The matcher will be ignored')); return; } let taskSystemInfo: TaskSystemInfo | undefined = resolver.taskSystemInfo; diff --git a/src/vs/workbench/contrib/terminal/browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css index 361924fdbd20..e09e08d08a6e 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css @@ -128,7 +128,7 @@ } .xterm.enable-mouse-events { - /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ + /* When mouse events are enabled (e.g. tmux), revert to the standard pointer cursor */ cursor: default; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index e3a75356f3ab..1fd01ace06b6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -260,15 +260,10 @@ configurationRegistry.registerConfiguration({ default: 'inherited' }, 'terminal.integrated.windowsEnableConpty': { - description: nls.localize('terminal.integrated.windowsEnableConpty', "Whether to use ConPTY for Windows terminal process communication. Winpty will be used if this is false. Note that ConPTY will be disabled regardless of this setting when the Windows 10 build number is lower than 18309 or when you're running the 32-bit VS Code client under 64-bit Windows."), + description: nls.localize('terminal.integrated.windowsEnableConpty', "Whether to use ConPTY for Windows terminal process communication (requires Windows 10 build number 18309+). Winpty will be used if this is false."), type: 'boolean', default: true }, - 'terminal.integrated.enableLatencyMitigation': { - description: nls.localize('terminal.integrated.enableLatencyMitigation', "Whether to enable the latency mitigation feature for high-latency terminals."), - type: 'boolean', - default: false - }, 'terminal.integrated.experimentalRefreshOnResume': { description: nls.localize('terminal.integrated.experimentalRefreshOnResume', "An experimental setting that will refresh the terminal renderer when the system is resumed."), type: 'boolean', @@ -397,8 +392,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(MoveToLineEndTer mac: { primary: KeyMod.CtrlCmd | KeyCode.RightArrow } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Move To Line End', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SplitTerminalAction, SplitTerminalAction.ID, SplitTerminalAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_5], + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_5, mac: { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH, secondary: [KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_5] diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index dd4b778bc3cb..34c76d2ea612 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -3,8 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Terminal as XTermTerminal } from 'vscode-xterm'; -import { ITerminalInstance, IWindowsShellHelper, ITerminalProcessManager, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; +import { Terminal as XTermTerminal } from 'xterm'; +import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; +import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; +import { ITerminalInstance, IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment, Platform } from 'vs/base/common/platform'; @@ -14,8 +16,9 @@ export interface ITerminalInstanceService { _serviceBrand: any; getXtermConstructor(): Promise; + getXtermWebLinksConstructor(): Promise; + getXtermSearchConstructor(): Promise; createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper; - createTerminalProcessManager(id: number, configHelper: ITerminalConfigHelper): ITerminalProcessManager; createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess; getDefaultShell(p: Platform): string; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index c114669abcfc..7fea9ff67409 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -748,10 +748,10 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem { ) { super(null, action, terminalService.getTabLabels().map(label => { text: label }), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') }); - this.toDispose.push(terminalService.onInstancesChanged(this._updateItems, this)); - this.toDispose.push(terminalService.onActiveTabChanged(this._updateItems, this)); - this.toDispose.push(terminalService.onInstanceTitleChanged(this._updateItems, this)); - this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); + this._register(terminalService.onInstancesChanged(this._updateItems, this)); + this._register(terminalService.onActiveTabChanged(this._updateItems, this)); + this._register(terminalService.onInstanceTitleChanged(this._updateItems, this)); + this._register(attachSelectBoxStyler(this.selectBox, themeService)); } private _updateItems(): void { @@ -1041,7 +1041,7 @@ export class QuickOpenActionTermContributor extends ActionBarContributor { super(); } - public getActions(context: any): IAction[] { + public getActions(context: any): ReadonlyArray { const actions: Action[] = []; if (context.element instanceof TerminalEntry) { actions.push(this.instantiationService.createInstance(RenameTerminalQuickOpenAction, RenameTerminalQuickOpenAction.ID, RenameTerminalQuickOpenAction.LABEL, context.element)); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts index 145e54e24373..a9b29119ad07 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Terminal, IMarker } from 'vscode-xterm'; +import { Terminal, IMarker } from 'xterm'; import { ITerminalCommandTracker } from 'vs/workbench/contrib/terminal/common/terminal'; import { IDisposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 8e99da0d64d7..e80dea6307c5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -10,7 +10,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITerminalConfiguration, ITerminalFont, IShellLaunchConfig, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; import Severity from 'vs/base/common/severity'; -import { Terminal as XTermTerminal } from 'vscode-xterm'; +import { Terminal as XTermTerminal } from 'xterm'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal'; import { mergeDefaultShellPathAndArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index b403ebba1083..4e8db284f99f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -32,16 +32,17 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ISearchOptions, Terminal as XTermTerminal, IBuffer } from 'vscode-xterm'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; +import { Terminal as XTermTerminal, IBuffer } from 'xterm'; +import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer const SLOW_CANVAS_RENDER_THRESHOLD = 50; const NUMBER_OF_FRAMES_TO_MEASURE = 20; - export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ TERMINAL_COMMAND_ID.CLEAR_SELECTION, TERMINAL_COMMAND_ID.CLEAR, @@ -148,6 +149,8 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ 'workbench.action.toggleMaximizedPanel' ]; +let xtermConstructor: Promise | undefined; + export class TerminalInstance implements ITerminalInstance { private static readonly EOL_REGEX = /\r?\n/g; @@ -166,6 +169,7 @@ export class TerminalInstance implements ITerminalInstance { private _title: string; private _wrapperElement: HTMLDivElement; private _xterm: XTermTerminal; + private _xtermSearch: SearchAddon | undefined; private _xtermElement: HTMLDivElement; private _terminalHasTextContextKey: IContextKey; private _cols: number; @@ -391,11 +395,26 @@ export class TerminalInstance implements ITerminalInstance { return TerminalInstance._lastKnownDimensions; } + private async _getXtermConstructor(): Promise { + if (xtermConstructor) { + return xtermConstructor; + } + xtermConstructor = new Promise(async (resolve) => { + const Terminal = await this._terminalInstanceService.getXtermConstructor(); + // Localize strings + Terminal.strings.blankLine = nls.localize('terminal.integrated.a11yBlankLine', 'Blank line'); + Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input'); + Terminal.strings.tooMuchOutput = nls.localize('terminal.integrated.a11yTooMuchOutput', 'Too much output to announce, navigate to rows manually to read'); + resolve(Terminal); + }); + return xtermConstructor; + } + /** * Create xterm.js instance and attach data listeners. */ protected async _createXterm(): Promise { - const Terminal = await this._terminalInstanceService.getXtermConstructor(); + const Terminal = await this._getXtermConstructor(); const font = this._configHelper.getFont(undefined, true); const config = this._configHelper.config; this._xterm = new Terminal({ @@ -414,9 +433,11 @@ export class TerminalInstance implements ITerminalInstance { macOptionClickForcesSelection: config.macOptionClickForcesSelection, rightClickSelectsWord: config.rightClickBehavior === 'selectWord', // TODO: Guess whether to use canvas or dom better - rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType, - // TODO: Remove this once the setting is removed upstream - experimentalCharAtlas: 'dynamic' + rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType + }); + this._terminalInstanceService.getXtermSearchConstructor().then(Addon => { + this._xtermSearch = new Addon(); + this._xterm.loadAddon(this._xtermSearch); }); if (this._shellLaunchConfig.initialText) { this._xterm.writeln(this._shellLaunchConfig.initialText); @@ -595,19 +616,6 @@ export class TerminalInstance implements ITerminalInstance { if (this._processManager) { this._widgetManager = new TerminalWidgetManager(this._wrapperElement); this._processManager.onProcessReady(() => this._linkHandler.setWidgetManager(this._widgetManager)); - - this._processManager.onProcessReady(() => { - if (this._configHelper.config.enableLatencyMitigation) { - if (!this._processManager) { - return; - } - this._processManager.getLatency().then(latency => { - if (latency > 20 && (this._xterm as any).typeAheadInit) { - (this._xterm as any).typeAheadInit(this._processManager, this._themeService); - } - }); - } - }); } else if (this._shellLaunchConfig.isRendererOnly) { this._widgetManager = new TerminalWidgetManager(this._wrapperElement); this._linkHandler.setWidgetManager(this._widgetManager); @@ -714,11 +722,17 @@ export class TerminalInstance implements ITerminalInstance { } public findNext(term: string, searchOptions: ISearchOptions): boolean { - return this._xterm.findNext(term, searchOptions); + if (!this._xtermSearch) { + return false; + } + return this._xtermSearch.findNext(term, searchOptions); } public findPrevious(term: string, searchOptions: ISearchOptions): boolean { - return this._xterm.findPrevious(term, searchOptions); + if (!this._xtermSearch) { + return false; + } + return this._xtermSearch.findPrevious(term, searchOptions); } public notifyFindWidgetFocusChanged(isFocused: boolean): void { @@ -775,7 +789,7 @@ export class TerminalInstance implements ITerminalInstance { public rendererExit(exitCode: number): void { // The use of this API is for cases where there is no backing process behind a terminal - // instance (eg. a custom execution task). + // instance (e.g. a custom execution task). if (!this.shellLaunchConfig.isRendererOnly) { throw new Error('rendererExit is only expected to be called on a renderer only terminal'); } @@ -915,7 +929,7 @@ export class TerminalInstance implements ITerminalInstance { } protected _createProcess(): void { - this._processManager = this._terminalInstanceService.createTerminalProcessManager(this._id, this._configHelper); + this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._id, this._configHelper); this._processManager.onProcessReady(() => this._onProcessIdReady.fire(this)); this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode)); this._processManager.onProcessData(data => this._onData.fire(data)); @@ -959,7 +973,7 @@ export class TerminalInstance implements ITerminalInstance { /** * Called when either a process tied to a terminal has exited or when a terminal renderer - * simulates a process exiting (eg. custom execution task). + * simulates a process exiting (e.g. custom execution task). * @param exitCode The exit code of the process, this is undefined when the terminal was exited * through user action. */ @@ -1123,9 +1137,17 @@ export class TerminalInstance implements ITerminalInstance { } private _sendLineData(buffer: IBuffer, lineIndex: number): void { - let lineData = buffer.getLine(lineIndex)!.translateToString(true); - while (lineIndex >= 0 && buffer.getLine(lineIndex--)!.isWrapped) { - lineData = buffer.getLine(lineIndex)!.translateToString(false) + lineData; + let line = buffer.getLine(lineIndex); + if (!line) { + return; + } + let lineData = line.translateToString(true); + while (lineIndex > 0 && line.isWrapped) { + line = buffer.getLine(--lineIndex); + if (!line) { + break; + } + lineData = line.translateToString(false) + lineData; } this._onLineData.fire(lineData); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index ad4dcd43fd11..51852cd461ca 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -14,9 +14,10 @@ import { ITerminalService, ITerminalProcessManager } from 'vs/workbench/contrib/ import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IFileService } from 'vs/platform/files/common/files'; -import { ILinkMatcherOptions } from 'vscode-xterm'; +import { ILinkMatcherOptions } from 'xterm'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { posix, win32 } from 'vs/base/common/path'; +import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; const pathPrefix = '(\\.\\.?|\\~)'; const pathSeparatorClause = '\\/'; @@ -64,13 +65,14 @@ interface IPath { } export class TerminalLinkHandler { - private _hoverDisposables: IDisposable[] = []; + private readonly _hoverDisposables = new DisposableStore(); private _mouseMoveDisposable: IDisposable; private _widgetManager: TerminalWidgetManager; private _processCwd: string; private _gitDiffPreImagePattern: RegExp; private _gitDiffPostImagePattern: RegExp; private readonly _tooltipCallback: (event: MouseEvent, uri: string) => boolean | void; + private readonly _leaveCallback: () => void; constructor( private _xterm: any, @@ -80,6 +82,7 @@ export class TerminalLinkHandler { @IEditorService private readonly _editorService: IEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ITerminalService private readonly _terminalService: ITerminalService, + @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @IFileService private readonly _fileService: IFileService ) { // Matches '--- a/src/file1', capturing 'src/file1' in group 1 @@ -98,6 +101,11 @@ export class TerminalLinkHandler { this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); } }; + this._leaveCallback = () => { + if (this._widgetManager) { + this._widgetManager.closeMessage(); + } + }; this.registerWebLinkHandler(); if (this._platform) { @@ -118,11 +126,7 @@ export class TerminalLinkHandler { const options: ILinkMatcherOptions = { matchIndex, tooltipCallback: this._tooltipCallback, - leaveCallback: () => { - if (this._widgetManager) { - this._widgetManager.closeMessage(); - } - }, + leaveCallback: this._leaveCallback, willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e), priority: CUSTOM_LINK_PRIORITY }; @@ -133,18 +137,16 @@ export class TerminalLinkHandler { } public registerWebLinkHandler(): void { - const wrappedHandler = this._wrapLinkHandler(uri => { - this._handleHypertextLink(uri); - }); - this._xterm.webLinksInit(wrappedHandler, { - validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateWebLink(uri, callback), - tooltipCallback: this._tooltipCallback, - leaveCallback: () => { - if (this._widgetManager) { - this._widgetManager.closeMessage(); - } - }, - willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e) + this._terminalInstanceService.getXtermWebLinksConstructor().then((WebLinksAddon) => { + const wrappedHandler = this._wrapLinkHandler(uri => { + this._handleHypertextLink(uri); + }); + this._xterm.loadAddon(new WebLinksAddon(wrappedHandler, { + validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateWebLink(uri, callback), + tooltipCallback: this._tooltipCallback, + leaveCallback: this._leaveCallback, + willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e) + })); }); } @@ -155,11 +157,7 @@ export class TerminalLinkHandler { this._xterm.registerLinkMatcher(this._localLinkRegex, wrappedHandler, { validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback), tooltipCallback: this._tooltipCallback, - leaveCallback: () => { - if (this._widgetManager) { - this._widgetManager.closeMessage(); - } - }, + leaveCallback: this._leaveCallback, willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e), priority: LOCAL_LINK_PRIORITY }); @@ -173,11 +171,7 @@ export class TerminalLinkHandler { matchIndex: 1, validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback), tooltipCallback: this._tooltipCallback, - leaveCallback: () => { - if (this._widgetManager) { - this._widgetManager.closeMessage(); - } - }, + leaveCallback: this._leaveCallback, willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e), priority: LOCAL_LINK_PRIORITY }; @@ -187,7 +181,8 @@ export class TerminalLinkHandler { public dispose(): void { this._xterm = null; - this._hoverDisposables = dispose(this._hoverDisposables); + + this._hoverDisposables.dispose(); this._mouseMoveDisposable = dispose(this._mouseMoveDisposable); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 647b4e4a58bd..df0f611fc85f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -5,7 +5,6 @@ import * as platform from 'vs/base/common/platform'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal'; import { ILogService } from 'vs/platform/log/common/log'; import { Emitter, Event } from 'vs/base/common/event'; @@ -48,7 +47,6 @@ export class TerminalProcessManager implements ITerminalProcessManager { private _process: ITerminalChildProcess | null = null; private _preLaunchInputQueue: string[] = []; - private _disposables: IDisposable[] = []; private _latency: number = -1; private _latencyRequest: Promise; private _latencyLastMeasured: number = 0; @@ -96,12 +94,6 @@ export class TerminalProcessManager implements ITerminalProcessManager { this._process.shutdown(immediate); this._process = null; } - this._disposables.forEach(d => d.dispose()); - this._disposables.length = 0; - } - - public addDisposable(disposable: IDisposable) { - this._disposables.push(disposable); } public createProcess( diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 201fed9470c6..611bf6fc94b0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -50,6 +50,16 @@ export abstract class TerminalService extends CommonTerminalService implements I } public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance { + if (shell.runInBackground) { + const instance = this.createInstance(this._terminalFocusContextKey, + this.configHelper, + undefined, + shell, + true); + this._backgroundedTerminalInstances.push(instance); + this._initInstanceListeners(instance); + return instance; + } const terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalFocusContextKey, this.configHelper, @@ -68,6 +78,24 @@ export abstract class TerminalService extends CommonTerminalService implements I return instance; } + protected _showBackgroundTerminal(instance: ITerminalInstance): void { + this._backgroundedTerminalInstances.splice(this._backgroundedTerminalInstances.indexOf(instance), 1); + instance.shellLaunchConfig.runInBackground = false; + const terminalTab = this._instantiationService.createInstance(TerminalTab, + this._terminalFocusContextKey, + this.configHelper, + this._terminalContainer, + instance); + this._terminalTabs.push(terminalTab); + terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); + terminalTab.addDisposable(terminalTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged)); + if (this.terminalInstances.length === 1) { + // It's the first instance so it should be made active automatically + this.setActiveInstanceByIndex(0); + } + this._onInstancesChanged.fire(); + } + public focusFindWidget(): Promise { return this.showPanel(false).then(() => { const panel = this._panelService.getActivePanel() as TerminalPanel; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts index a0a064985ce7..4962be96b569 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts @@ -225,7 +225,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, private _container: HTMLElement, - shellLaunchConfig: IShellLaunchConfig, + shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance, @ITerminalService private readonly _terminalService: ITerminalService, @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService ) { @@ -233,12 +233,17 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._onDisposed = new Emitter(); this._onInstancesChanged = new Emitter(); - const instance = this._terminalService.createInstance( - terminalFocusContextKey, - configHelper, - undefined, - shellLaunchConfig, - true); + let instance: ITerminalInstance; + if ('id' in shellLaunchConfigOrInstance) { + instance = shellLaunchConfigOrInstance; + } else { + instance = this._terminalService.createInstance( + terminalFocusContextKey, + configHelper, + undefined, + shellLaunchConfigOrInstance, + true); + } this._terminalInstances.push(instance); this._initInstanceListeners(instance); this._activeInstanceIndex = 0; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts b/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts deleted file mode 100644 index 95f9c387fcec..000000000000 --- a/src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts +++ /dev/null @@ -1,136 +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 { Terminal as XTermTerminal } from 'vscode-xterm'; -import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; - -export interface ITerminalCore { - buffer: any; -} - -export interface ITypeAheadAddonTerminal { - _core: ITerminalCore; - on: any; - write(data: string): void; - cols: number; - - __typeAheadQueue: string[]; - __typeAheadState: TypeAheadState; - __typeAheadCurrentYBase: number; - __typeAheadCurrentY: number; -} - -enum TypeAheadState { - /** - * The normal state, ready to type if it starts. - */ - Normal, - /** - * Something happens such that we cannot make a good guess on what to print, - * wait until the cursor row changes before proceeding. - */ - AwaitingRowChange -} - -function isCharPrintable(data: string): boolean { - const code = data.charCodeAt(0); - return data.length === 1 && code >= 32 && code <= 126; -} - -function init(terminal: any, processManager: ITerminalProcessManager, themeService: IThemeService): void { - const t = terminal as ITypeAheadAddonTerminal; - - t.__typeAheadQueue = []; - t.__typeAheadState = TypeAheadState.Normal; - t.__typeAheadCurrentYBase = 0; - t.__typeAheadCurrentY = 0; - - function typeAhead(data: string): void { - for (let i = 0; i < data.length; i++) { - t.__typeAheadQueue.push(data[i]); - } - t.write(data); - } - - t.on('cursormove', () => { - // Reset if the cursor row changed - if (t._core.buffer.ybase !== t.__typeAheadCurrentYBase || t._core.buffer.y !== t.__typeAheadCurrentY) { - t.__typeAheadCurrentYBase = t._core.buffer.ybase; - t.__typeAheadCurrentY = t._core.buffer.y; - t.__typeAheadState = TypeAheadState.Normal; - } - }); - - t.on('data', (data: string) => { - // Exit if we're waiting for a row change - if (t.__typeAheadState === TypeAheadState.AwaitingRowChange) { - return; - } - - // Only enable in the normal buffer - if (!t._core.buffer._hasScrollback) { - return; - } - - // // Handle enter - // if (data === '\r') { - // typeAhead('\r\n'); - // return; - // } - - // // Left arrow - // if (data === '\x1b[D') { - // // TODO: How to stop it from going beyond prompt? - // typeAhead(String.fromCharCode(8)); - // } - - // // Right arrow - // if (data === '\x1b[C') { - // // TODO: How to stop it from going beyond prompt? - // typeAhead('\x1b[C'); - // } - - // // Backspace (DEL) - // if (data.charCodeAt(0) === 127) { - // // TODO: This would require knowing the prompt length to be able to shift everything - // } - - if (!isCharPrintable(data)) { - t.__typeAheadState = TypeAheadState.AwaitingRowChange; - return; - } - - if (t._core.buffer.x === t.cols - 1) { - // TODO: Does the space get added on Windows/Linux too? - data += ' \r'; - } - typeAhead(data); - }); - - processManager.onBeforeProcessData(event => { - let consumeCount = 0; - for (let i = 0; i < event.data.length; i++) { - if (t.__typeAheadQueue[0] === event.data[i]) { - t.__typeAheadQueue.shift(); - consumeCount++; - } else { - t.__typeAheadQueue.length = 0; - break; - } - } - if (consumeCount === event.data.length) { - event.data = ''; - } else if (consumeCount > 0) { - event.data = event.data.substr(consumeCount); - } - }); -} - -export function apply(terminalConstructor: typeof XTermTerminal) { - (terminalConstructor.prototype).typeAheadInit = function (processManager: ITerminalProcessManager, themeService: IThemeService): void { - init(this, processManager, themeService); - }; -} diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 41eb68db93d7..046c59ff820e 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -101,7 +101,6 @@ export interface ITerminalConfiguration { experimentalBufferImpl: 'JsArray' | 'TypedArray'; splitCwd: 'workspaceRoot' | 'initial' | 'inherited'; windowsEnableConpty: boolean; - enableLatencyMitigation: boolean; experimentalRefreshOnResume: boolean; } @@ -162,7 +161,7 @@ export interface IShellLaunchConfig { env?: ITerminalEnvironment; /** - * Whether to ignore a custom cwd from the `terminal.integrated.cwd` settings key (eg. if the + * Whether to ignore a custom cwd from the `terminal.integrated.cwd` settings key (e.g. if the * shell is being launched by an extension). */ ignoreConfigurationCwd?: boolean; @@ -192,6 +191,15 @@ export interface IShellLaunchConfig { * provided as nothing will be inherited from the process or any configuration. */ strictEnv?: boolean; + + /** + * When enabled the terminal will run the process as normal but not be surfaced to the user + * until `Terminal.show` is called. The typical usage for this is when you need to run + * something that may need interactivity but only want to tell the user about it when + * interaction is needed. Note that the terminals will still be exposed to all extensions + * as normal. + */ + runInBackground?: boolean; } export interface ITerminalService { @@ -426,7 +434,7 @@ export interface ITerminalInstance { /** * Whether to disable layout for the terminal. This is useful when the size of the terminal is - * being manipulating (eg. adding a split pane) and we want the terminal to ignore particular + * being manipulating (e.g. adding a split pane) and we want the terminal to ignore particular * resize events. */ disableLayout: boolean; @@ -663,7 +671,6 @@ export interface ITerminalProcessManager extends IDisposable { readonly onProcessTitle: Event; readonly onProcessExit: Event; - addDisposable(disposable: IDisposable): void; dispose(immediate?: boolean): void; createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): void; write(data: string): void; diff --git a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts index 9b979770091f..861ae33eb7a6 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts @@ -37,7 +37,7 @@ export const TERMINAL_BORDER_COLOR = registerColor('terminal.border', { hc: PANEL_BORDER }, nls.localize('terminal.border', 'The color of the border that separates split panes within the terminal. This defaults to panel.border.')); -const ansiColorMap = { +export const ansiColorMap = { 'terminal.ansiBlack': { index: 0, defaults: { diff --git a/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts index 35b2ef71043e..44abad724a08 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts @@ -5,37 +5,36 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import * as nls from 'vs/nls'; let hasReceivedResponse: boolean = false; -export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerminalProcessExtHostProxy { - private _disposables: IDisposable[] = []; - - private readonly _onProcessData = new Emitter(); - public get onProcessData(): Event { return this._onProcessData.event; } - private readonly _onProcessExit = new Emitter(); - public get onProcessExit(): Event { return this._onProcessExit.event; } - private readonly _onProcessIdReady = new Emitter(); - public get onProcessIdReady(): Event { return this._onProcessIdReady.event; } - private readonly _onProcessTitleChanged = new Emitter(); - public get onProcessTitleChanged(): Event { return this._onProcessTitleChanged.event; } - - private readonly _onInput = new Emitter(); - public get onInput(): Event { return this._onInput.event; } - private readonly _onResize: Emitter<{ cols: number, rows: number }> = new Emitter<{ cols: number, rows: number }>(); - public get onResize(): Event<{ cols: number, rows: number }> { return this._onResize.event; } - private readonly _onShutdown = new Emitter(); - public get onShutdown(): Event { return this._onShutdown.event; } - private readonly _onRequestInitialCwd = new Emitter(); - public get onRequestInitialCwd(): Event { return this._onRequestInitialCwd.event; } - private readonly _onRequestCwd = new Emitter(); - public get onRequestCwd(): Event { return this._onRequestCwd.event; } - private readonly _onRequestLatency = new Emitter(); - public get onRequestLatency(): Event { return this._onRequestLatency.event; } +export class TerminalProcessExtHostProxy extends Disposable implements ITerminalChildProcess, ITerminalProcessExtHostProxy { + + private readonly _onProcessData = this._register(new Emitter()); + public readonly onProcessData: Event = this._onProcessData.event; + private readonly _onProcessExit = this._register(new Emitter()); + public readonly onProcessExit: Event = this._onProcessExit.event; + private readonly _onProcessIdReady = this._register(new Emitter()); + public readonly onProcessIdReady: Event = this._onProcessIdReady.event; + private readonly _onProcessTitleChanged = this._register(new Emitter()); + public readonly onProcessTitleChanged: Event = this._onProcessTitleChanged.event; + + private readonly _onInput = this._register(new Emitter()); + public readonly onInput: Event = this._onInput.event; + private readonly _onResize: Emitter<{ cols: number, rows: number }> = this._register(new Emitter<{ cols: number, rows: number }>()); + public readonly onResize: Event<{ cols: number, rows: number }> = this._onResize.event; + private readonly _onShutdown = this._register(new Emitter()); + public readonly onShutdown: Event = this._onShutdown.event; + private readonly _onRequestInitialCwd = this._register(new Emitter()); + public readonly onRequestInitialCwd: Event = this._onRequestInitialCwd.event; + private readonly _onRequestCwd = this._register(new Emitter()); + public readonly onRequestCwd: Event = this._onRequestCwd.event; + private readonly _onRequestLatency = this._register(new Emitter()); + public readonly onRequestLatency: Event = this._onRequestLatency.event; private _pendingInitialCwdRequests: ((value?: string | Thenable) => void)[] = []; private _pendingCwdRequests: ((value?: string | Thenable) => void)[] = []; @@ -51,6 +50,7 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm @ITerminalService private readonly _terminalService: ITerminalService, @IRemoteAgentService readonly remoteAgentService: IRemoteAgentService ) { + super(); remoteAgentService.getEnvironment().then(env => { if (!env) { throw new Error('Could not fetch environment'); @@ -62,11 +62,6 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm } } - public dispose(): void { - this._disposables.forEach(d => d.dispose()); - this._disposables.length = 0; - } - public emitData(data: string): void { this._onProcessData.fire(data); } diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index 2d04dff02cad..e8620d0459a4 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -30,6 +30,7 @@ export abstract class TerminalService implements ITerminalService { protected _findWidgetVisible: IContextKey; protected _terminalContainer: HTMLElement; protected _terminalTabs: ITerminalTab[] = []; + protected _backgroundedTerminalInstances: ITerminalInstance[] = []; protected get _terminalInstances(): ITerminalInstance[] { return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), []); } @@ -103,8 +104,8 @@ export abstract class TerminalService implements ITerminalService { protected abstract _getWslPath(path: string): Promise; protected abstract _getWindowsBuildNumber(): number; + protected abstract _showBackgroundTerminal(instance: ITerminalInstance): void; - public abstract refreshActiveTab(): void; public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; public abstract createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance; public abstract getDefaultShell(platform: Platform): string; @@ -206,6 +207,11 @@ export abstract class TerminalService implements ITerminalService { } } + public refreshActiveTab(): void { + // Fire active instances changed + this._onActiveTabChanged.fire(); + } + public getActiveTab(): ITerminalTab | null { if (this._activeTabIndex < 0 || this._activeTabIndex >= this._terminalTabs.length) { return null; @@ -222,6 +228,15 @@ export abstract class TerminalService implements ITerminalService { } public getInstanceFromId(terminalId: number): ITerminalInstance { + let bgIndex = -1; + this._backgroundedTerminalInstances.forEach((terminalInstance, i) => { + if (terminalInstance.id === terminalId) { + bgIndex = i; + } + }); + if (bgIndex !== -1) { + return this._backgroundedTerminalInstances[bgIndex]; + } return this.terminalInstances[this._getIndexFromId(terminalId)]; } @@ -230,6 +245,11 @@ export abstract class TerminalService implements ITerminalService { } public setActiveInstance(terminalInstance: ITerminalInstance): void { + // If this was a runInBackground terminal created by the API this was triggered by show, + // in which case we need to create the terminal tab + if (terminalInstance.shellLaunchConfig.runInBackground) { + this._showBackgroundTerminal(terminalInstance); + } this.setActiveInstanceByIndex(this._getIndexFromId(terminalInstance.id)); } @@ -441,15 +461,14 @@ export abstract class TerminalService implements ITerminalService { public preparePathForTerminalAsync(originalPath: string, executable: string, title: string): Promise { return new Promise(c => { - const exe = executable; - if (!exe) { + if (!executable) { c(originalPath); return; } const hasSpace = originalPath.indexOf(' ') !== -1; - const pathBasename = basename(exe, '.exe'); + const pathBasename = basename(executable, '.exe'); const isPowerShell = pathBasename === 'pwsh' || title === 'pwsh' || pathBasename === 'powershell' || @@ -463,7 +482,9 @@ export abstract class TerminalService implements ITerminalService { if (isWindows) { // 17063 is the build number where wsl path was introduced. // Update Windows uriPath to be executed in WSL. - if (((exe.indexOf('wsl') !== -1) || ((exe.indexOf('bash.exe') !== -1) && (exe.indexOf('git') === -1))) && (this._getWindowsBuildNumber() >= 17063)) { + const lowerExecutable = executable.toLowerCase(); + if (this._getWindowsBuildNumber() >= 17063 && + (lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1))) { c(this._getWslPath(originalPath)); return; } else if (hasSpace) { diff --git a/src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts b/src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts new file mode 100644 index 000000000000..fc594ac05532 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/common/terminalShellConfig.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Platform } from 'vs/base/common/platform'; + +export function registerShellConfiguration(getDefaultShell?: (p: Platform) => string): void { + const configurationRegistry = Registry.as(Extensions.Configuration); + configurationRegistry.registerConfiguration({ + id: 'terminal', + order: 100, + title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), + type: 'object', + properties: { + 'terminal.integrated.shell.linux': { + markdownDescription: + getDefaultShell + ? nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(Platform.Linux)) + : nls.localize('terminal.integrated.shell.linux.noDefault', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.shell.osx': { + markdownDescription: + getDefaultShell + ? nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(Platform.Mac)) + : nls.localize('terminal.integrated.shell.osx.noDefault', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: ['string', 'null'], + default: null + }, + 'terminal.integrated.shell.windows': { + markdownDescription: + getDefaultShell + ? nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(Platform.Windows)) + : nls.localize('terminal.integrated.shell.windows.noDefault', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: ['string', 'null'], + default: null + } + } + }); +} diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts index e746c3d018d8..fa765e401b66 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts @@ -3,41 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as platform from 'vs/base/common/platform'; -import * as nls from 'vs/nls'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Registry } from 'vs/platform/registry/common/platform'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/electron-browser/terminalInstanceService'; import { TerminalService } from 'vs/workbench/contrib/terminal/electron-browser/terminalService'; import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; +import { registerShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalShellConfig'; -const configurationRegistry = Registry.as(Extensions.Configuration); -configurationRegistry.registerConfiguration({ - id: 'terminal', - order: 100, - title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), - type: 'object', - properties: { - 'terminal.integrated.shell.linux': { - markdownDescription: nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(platform.Platform.Linux)), - type: ['string', 'null'], - default: null - }, - 'terminal.integrated.shell.osx': { - markdownDescription: nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(platform.Platform.Mac)), - type: ['string', 'null'], - default: null - }, - 'terminal.integrated.shell.windows': { - markdownDescription: nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(platform.Platform.Windows)), - type: ['string', 'null'], - default: null - } - } -}); - +registerShellConfiguration(getDefaultShell); registerSingleton(ITerminalService, TerminalService, true); registerSingleton(ITerminalInstanceService, TerminalInstanceService, true); diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index 953aabe3612d..eac1551a76ef 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -3,19 +3,20 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { Terminal as XTermTerminal } from 'vscode-xterm'; -import { ITerminalInstance, IWindowsShellHelper, ITerminalConfigHelper, ITerminalProcessManager, IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalInstance, IWindowsShellHelper, IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal'; import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/node/windowsShellHelper'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import { IProcessEnvironment, Platform } from 'vs/base/common/platform'; import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; -import * as typeAheadAddon from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon'; import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; +import { Terminal as XTermTerminal } from 'xterm'; +import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links'; +import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; let Terminal: typeof XTermTerminal; +let WebLinksAddon: typeof XTermWebLinksAddon; +let SearchAddon: typeof XTermSearchAddon; /** * A service used by TerminalInstance (and components owned by it) that allows it to break its @@ -32,25 +33,27 @@ export class TerminalInstanceService implements ITerminalInstanceService { public async getXtermConstructor(): Promise { if (!Terminal) { - Terminal = (await import('vscode-xterm')).Terminal; - // Enable xterm.js addons - Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/search/search')); - Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/webLinks/webLinks')); - Terminal.applyAddon(typeAheadAddon); - // Localize strings - Terminal.strings.blankLine = nls.localize('terminal.integrated.a11yBlankLine', 'Blank line'); - Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input'); - Terminal.strings.tooMuchOutput = nls.localize('terminal.integrated.a11yTooMuchOutput', 'Too much output to announce, navigate to rows manually to read'); + Terminal = (await import('xterm')).Terminal; } return Terminal; } - public createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper { - return new WindowsShellHelper(shellProcessId, instance, xterm); + public async getXtermWebLinksConstructor(): Promise { + if (!WebLinksAddon) { + WebLinksAddon = (await import('xterm-addon-web-links')).WebLinksAddon; + } + return WebLinksAddon; + } + + public async getXtermSearchConstructor(): Promise { + if (!SearchAddon) { + SearchAddon = (await import('xterm-addon-search')).SearchAddon; + } + return SearchAddon; } - public createTerminalProcessManager(id: number, configHelper: ITerminalConfigHelper): ITerminalProcessManager { - return this._instantiationService.createInstance(TerminalProcessManager, id, configHelper); + public createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper { + return new WindowsShellHelper(shellProcessId, instance, xterm); } public createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts index 93af8cda0ae2..15b139c0518d 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts @@ -100,11 +100,6 @@ export class TerminalService extends BrowserTerminalService implements ITerminal return getDefaultShell(p); } - public refreshActiveTab(): void { - // Fire active instances changed - this._onActiveTabChanged.fire(); - } - public selectDefaultWindowsShell(): Promise { return this._detectWindowsShells().then(shells => { const options: IPickOptions = { diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index e8d802cda2e0..983afd4c04f9 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -54,14 +54,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { this._initialCwd = cwd; - // Only use ConPTY when the client is non WoW64 (see #72190) and the Windows build number is at least 18309 (for - // stability/performance reasons) - const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); - const useConpty = windowsEnableConpty && - process.platform === 'win32' && - !is32ProcessOn64Windows && - getWindowsBuildNumber() >= 18309; - + const useConpty = windowsEnableConpty && process.platform === 'win32' && getWindowsBuildNumber() >= 18309; const options: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions = { name: shellName, cwd, @@ -200,6 +193,9 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { if (this._isDisposed) { return; } + if (typeof cols !== 'number' || typeof rows !== 'number' || isNaN(cols) || isNaN(rows)) { + return; + } // Ensure that cols and rows are always >= 1, this prevents a native // exception in winpty. if (this._ptyProcess) { diff --git a/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts index 59f2815e4893..dea63ca619c5 100644 --- a/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts +++ b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts @@ -6,7 +6,7 @@ import * as platform from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; import { ITerminalInstance, IWindowsShellHelper } from 'vs/workbench/contrib/terminal/common/terminal'; -import { Terminal as XTermTerminal } from 'vscode-xterm'; +import { Terminal as XTermTerminal } from 'xterm'; import WindowsProcessTreeType = require('windows-process-tree'); const SHELL_EXECUTABLES = [ diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts index 87d25074eb0d..fd7c7226ceeb 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Terminal, TerminalCore } from 'vscode-xterm'; +import { Terminal, TerminalCore } from 'xterm'; import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker'; import { isWindows } from 'vs/base/common/platform'; diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts index cf3f5a002cef..3c98380fc644 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { Platform, OperatingSystem } from 'vs/base/common/platform'; import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import * as strings from 'vs/base/common/strings'; +import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; class TestTerminalLinkHandler extends TerminalLinkHandler { public get localLinkRegex(): RegExp { @@ -24,10 +25,34 @@ class TestTerminalLinkHandler extends TerminalLinkHandler { } class TestXterm { - public webLinksInit() { } + public loadAddon() { } public registerLinkMatcher() { } } +class MockTerminalInstanceService implements ITerminalInstanceService { + _serviceBrand: any; + getXtermConstructor(): Promise { + throw new Error('Method not implemented.'); + } + async getXtermWebLinksConstructor(): Promise { + return (await import('xterm-addon-web-links')).WebLinksAddon; + } + getXtermSearchConstructor(): Promise { + throw new Error('Method not implemented.'); + } + createWindowsShellHelper(): any { + throw new Error('Method not implemented.'); + } + createTerminalProcess(): any { + throw new Error('Method not implemented.'); + } + getDefaultShell(p: Platform): string { + throw new Error('Method not implemented.'); + } + + +} + interface LinkFormatInfo { urlFormat: string; line?: string; @@ -40,7 +65,7 @@ suite('Workbench - TerminalLinkHandler', () => { const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, { os: OperatingSystem.Windows, userHome: '' - } as any, null!, null!, null!, null!, null!); + } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -115,7 +140,7 @@ suite('Workbench - TerminalLinkHandler', () => { const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, { os: OperatingSystem.Linux, userHome: '' - } as any, null!, null!, null!, null!, null!); + } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -182,7 +207,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\Me' - } as any, null!, null!, null!, null!, null!); + } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); linkHandler.processCwd = 'C:\\base'; assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base\\src\\file1'); @@ -195,7 +220,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\M e' - } as any, null!, null!, null!, null!, null!); + } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); linkHandler.processCwd = 'C:\\base dir'; assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base dir\\src\\file1'); @@ -209,7 +234,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, { os: OperatingSystem.Linux, userHome: '/home/me' - } as any, null!, null!, null!, null!, null!); + } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); linkHandler.processCwd = '/base'; assert.equal(linkHandler.preprocessPath('./src/file1'), '/base/src/file1'); @@ -222,7 +247,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, { os: OperatingSystem.Linux, userHome: '/home/me' - } as any, null!, null!, null!, null!, null!); + } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); assert.equal(linkHandler.preprocessPath('./src/file1'), null); assert.equal(linkHandler.preprocessPath('src/file2'), null); @@ -236,7 +261,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, { os: OperatingSystem.Linux, userHome: '' - } as any, null!, null!, null!, null!, null!); + } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); function assertAreGoodMatches(matches: RegExpMatchArray | null) { if (matches) { diff --git a/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts b/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts index fc451a175466..0409f628d5aa 100644 --- a/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts +++ b/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts @@ -11,14 +11,14 @@ import { IStringDictionary } from 'vs/base/common/collections'; suite('Workbench - TerminalEnvironment', () => { test('addTerminalEnvironmentKeys', () => { - const env = { FOO: 'bar' }; + const env: { [key: string]: any } = { FOO: 'bar' }; const locale = 'en-au'; terminalEnvironment.addTerminalEnvironmentKeys(env, '1.2.3', locale, true); assert.equal(env['TERM_PROGRAM'], 'vscode'); assert.equal(env['TERM_PROGRAM_VERSION'], '1.2.3'); assert.equal(env['LANG'], 'en_AU.UTF-8', 'LANG is equal to the requested locale with UTF-8'); - const env2 = { FOO: 'bar' }; + const env2: { [key: string]: any } = { FOO: 'bar' }; terminalEnvironment.addTerminalEnvironmentKeys(env2, '1.2.3', undefined, true); assert.equal(env2['LANG'], 'en_US.UTF-8', 'LANG is equal to en_US.UTF-8 as fallback.'); // More info on issue #14586 diff --git a/src/vs/workbench/contrib/update/electron-browser/update.contribution.ts b/src/vs/workbench/contrib/update/electron-browser/update.contribution.ts index 61546d19bb7d..b91003dab889 100644 --- a/src/vs/workbench/contrib/update/electron-browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/electron-browser/update.contribution.ts @@ -11,7 +11,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { IGlobalActivityRegistry, GlobalActivityExtensions } from 'vs/workbench/common/activity'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, Win3264BitContribution, Linux32BitContribution } from './update'; +import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, Win3264BitContribution } from './update'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; const workbench = Registry.as(WorkbenchExtensions.Workbench); @@ -24,13 +24,6 @@ if (platform.isWindows) { } } -// TODO@ben remove me after a while -if (platform.isLinux) { - if (process.arch === 'ia32') { - workbench.registerWorkbenchContribution(Linux32BitContribution, LifecyclePhase.Restored); - } -} - Registry.as(GlobalActivityExtensions) .registerActivity(UpdateContribution); diff --git a/src/vs/workbench/contrib/update/electron-browser/update.ts b/src/vs/workbench/contrib/update/electron-browser/update.ts index 488a9e49590c..4b8838a8a523 100644 --- a/src/vs/workbench/contrib/update/electron-browser/update.ts +++ b/src/vs/workbench/contrib/update/electron-browser/update.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import severity from 'vs/base/common/severity'; import { IAction, Action } from 'vs/base/common/actions'; -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import pkg from 'vs/platform/product/node/package'; import product from 'vs/platform/product/node/product'; @@ -219,53 +219,6 @@ export class Win3264BitContribution implements IWorkbenchContribution { } } -export class Linux32BitContribution implements IWorkbenchContribution { - - private static readonly KEY = 'update/linux32-64bits'; - private static readonly URL = 'https://code.visualstudio.com/updates/v1_32#_linux-32-bit-support-ends-soon'; - private static readonly INSIDER_URL = 'https://github.com/Microsoft/vscode-docs/blob/vnext/release-notes/v1_32.md#linux-32-bit-support-ends-soon'; - - constructor( - @IStorageService storageService: IStorageService, - @INotificationService notificationService: INotificationService, - @IEnvironmentService environmentService: IEnvironmentService - ) { - if (environmentService.disableUpdates) { - return; - } - - const neverShowAgain = new NeverShowAgain(Linux32BitContribution.KEY, storageService); - - if (!neverShowAgain.shouldShow()) { - return; - } - - const url = product.quality === 'insider' - ? Linux32BitContribution.INSIDER_URL - : Linux32BitContribution.URL; - - const handle = notificationService.prompt( - severity.Info, - nls.localize('linux64bits', "{0} for 32-bit Linux will soon be discontinued. Please update to the 64-bit version.", product.nameShort, url), - [{ - label: nls.localize('learnmore', "Learn More"), - run: () => { - window.open(url); - } - }, - { - label: nls.localize('neveragain', "Don't Show Again"), - isSecondary: true, - run: () => { - neverShowAgain.action.run(handle); - neverShowAgain.action.dispose(); - } - }], - { sticky: true } - ); - } -} - class CommandAction extends Action { constructor( @@ -277,7 +230,7 @@ class CommandAction extends Action { } } -export class UpdateContribution implements IGlobalActivity { +export class UpdateContribution extends Disposable implements IGlobalActivity { private static readonly showCommandsId = 'workbench.action.showCommands'; private static readonly openSettingsId = 'workbench.action.openSettings'; @@ -293,7 +246,6 @@ export class UpdateContribution implements IGlobalActivity { private state: UpdateState; private badgeDisposable: IDisposable = Disposable.None; - private disposables: IDisposable[] = []; constructor( @IStorageService private readonly storageService: IStorageService, @@ -305,9 +257,10 @@ export class UpdateContribution implements IGlobalActivity { @IActivityService private readonly activityService: IActivityService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { + super(); this.state = updateService.state; - updateService.onStateChange(this.onUpdateStateChange, this, this.disposables); + this._register(updateService.onStateChange(this.onUpdateStateChange, this)); this.onUpdateStateChange(this.updateService.state); /* @@ -585,8 +538,4 @@ export class UpdateContribution implements IGlobalActivity { this.updateService.quitAndInstall()); } } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index 4f436e03c24d..ad8e9589366c 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./watermark'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { isMacintosh, OS } from 'vs/base/common/platform'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -120,9 +120,8 @@ const folderEntries = [ const WORKBENCH_TIPS_ENABLED_KEY = 'workbench.tips.enabled'; -export class WatermarkContribution implements IWorkbenchContribution { +export class WatermarkContribution extends Disposable implements IWorkbenchContribution { - private toDispose: IDisposable[] = []; private watermark: HTMLElement; private enabled: boolean; private workbenchState: WorkbenchState; @@ -135,6 +134,7 @@ export class WatermarkContribution implements IWorkbenchContribution { @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService ) { + super(); this.workbenchState = contextService.getWorkbenchState(); lifecycleService.onShutdown(this.dispose, this); @@ -142,7 +142,7 @@ export class WatermarkContribution implements IWorkbenchContribution { if (this.enabled) { this.create(); } - this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { + this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(WORKBENCH_TIPS_ENABLED_KEY)) { const enabled = this.configurationService.getValue(WORKBENCH_TIPS_ENABLED_KEY); if (enabled !== this.enabled) { @@ -155,7 +155,7 @@ export class WatermarkContribution implements IWorkbenchContribution { } } })); - this.toDispose.push(this.contextService.onDidChangeWorkbenchState(e => { + this._register(this.contextService.onDidChangeWorkbenchState(e => { const previousWorkbenchState = this.workbenchState; this.workbenchState = this.contextService.getWorkbenchState(); @@ -189,8 +189,8 @@ export class WatermarkContribution implements IWorkbenchContribution { }; update(); dom.prepend(container.firstElementChild as HTMLElement, this.watermark); - this.toDispose.push(this.keybindingService.onDidUpdateKeybindings(update)); - this.toDispose.push(this.editorGroupsService.onDidLayout(dimension => this.handleEditorPartSize(container, dimension))); + this._register(this.keybindingService.onDidUpdateKeybindings(update)); + this._register(this.editorGroupsService.onDidLayout(dimension => this.handleEditorPartSize(container, dimension))); this.handleEditorPartSize(container, this.editorGroupsService.dimension); } @@ -215,10 +215,6 @@ export class WatermarkContribution implements IWorkbenchContribution { this.destroy(); this.create(); } - - public dispose(): void { - this.toDispose = dispose(this.toDispose); - } } Registry.as(WorkbenchExtensions.Workbench) diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index eb035a02e642..ab6a0eda7d0e 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -6,7 +6,7 @@ import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -33,7 +33,7 @@ export class WebviewEditor extends BaseEditor { private _content?: HTMLElement; private _webviewContent: HTMLElement | undefined; - private _webviewFocusTrackerDisposables: IDisposable[] = []; + private readonly _webviewFocusTrackerDisposables = new DisposableStore(); private _onFocusWindowHandler?: IDisposable; private readonly _onDidFocusWebview = this._register(new Emitter()); @@ -89,7 +89,7 @@ export class WebviewEditor extends BaseEditor { this._content = undefined; } - this._webviewFocusTrackerDisposables = dispose(this._webviewFocusTrackerDisposables); + this._webviewFocusTrackerDisposables.dispose(); if (this._onFocusWindowHandler) { this._onFocusWindowHandler.dispose(); @@ -304,14 +304,14 @@ export class WebviewEditor extends BaseEditor { } private trackFocus() { - this._webviewFocusTrackerDisposables = dispose(this._webviewFocusTrackerDisposables); + this._webviewFocusTrackerDisposables.clear(); // Track focus in webview content const webviewContentFocusTracker = DOM.trackFocus(this._webviewContent!); - this._webviewFocusTrackerDisposables.push(webviewContentFocusTracker); - this._webviewFocusTrackerDisposables.push(webviewContentFocusTracker.onDidFocus(() => this._onDidFocusWebview.fire())); + this._webviewFocusTrackerDisposables.add(webviewContentFocusTracker); + this._webviewFocusTrackerDisposables.add(webviewContentFocusTracker.onDidFocus(() => this._onDidFocusWebview.fire())); // Track focus in webview element - this._webviewFocusTrackerDisposables.push(this._webview!.onDidFocus(() => this._onDidFocusWebview.fire())); + this._webviewFocusTrackerDisposables.add(this._webview!.onDidFocus(() => this._onDidFocusWebview.fire())); } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 65b46d808aa1..a85720164bcf 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -327,4 +327,4 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput { } return super.resolve(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/webview/browser/webviewService.ts b/src/vs/workbench/contrib/webview/browser/webviewService.ts new file mode 100644 index 000000000000..0f2cc310e728 --- /dev/null +++ b/src/vs/workbench/contrib/webview/browser/webviewService.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWebviewService, Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/common/webview'; + +export class NullWebviewService implements IWebviewService { + + _serviceBrand: any; + + createWebview(_options: WebviewOptions, _contentOptions: WebviewContentOptions): Webview { + throw new Error('not supported'); + } +} diff --git a/src/vs/workbench/contrib/webview/common/webview.ts b/src/vs/workbench/contrib/webview/common/webview.ts index c3f0a5a9da8d..357d26648a70 100644 --- a/src/vs/workbench/contrib/webview/common/webview.ts +++ b/src/vs/workbench/contrib/webview/common/webview.ts @@ -9,6 +9,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import * as modes from 'vs/editor/common/modes'; +import { IDisposable } from 'vs/base/common/lifecycle'; /** * Set when the find widget in a webview is visible. @@ -45,7 +46,7 @@ export interface WebviewContentOptions { readonly portMappings?: ReadonlyArray; } -export interface Webview { +export interface Webview extends IDisposable { html: string; options: WebviewContentOptions; @@ -68,8 +69,6 @@ export interface Webview { layout(): void; mountTo(parent: HTMLElement): void; focus(): void; - dispose(): void; - reload(): void; selectAll(): void; diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 2b9d8448adac..62c2974fcdb1 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -361,10 +361,10 @@ interface WebviewContent { } export class WebviewElement extends Disposable implements Webview { - private _webview: Electron.WebviewTag; + private _webview: Electron.WebviewTag | undefined; private _ready: Promise; - private _webviewFindWidget: WebviewFindWidget; + private _webviewFindWidget: WebviewFindWidget | undefined; private _findStarted: boolean = false; private content: WebviewContent; @@ -404,8 +404,8 @@ export class WebviewElement extends Disposable implements Webview { this._webview.src = 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E'; this._ready = new Promise(resolve => { - const subscription = this._register(addDisposableListener(this._webview, 'ipc-message', (event) => { - if (event.channel === 'webview-ready') { + const subscription = this._register(addDisposableListener(this._webview!, 'ipc-message', (event) => { + if (this._webview && event.channel === 'webview-ready') { // console.info('[PID Webview] ' event.args[0]); addClass(this._webview, 'ready'); // can be found by debug command @@ -447,7 +447,7 @@ export class WebviewElement extends Disposable implements Webview { this.layout(); // Workaround for https://github.com/electron/electron/issues/14474 - if (this._focused || document.activeElement === this._webview) { + if (this._webview && (this._focused || document.activeElement === this._webview)) { this._webview.blur(); this._webview.focus(); } @@ -456,6 +456,10 @@ export class WebviewElement extends Disposable implements Webview { console.error('embedded page crashed'); })); this._register(addDisposableListener(this._webview, 'ipc-message', (event) => { + if (!this._webview) { + return; + } + switch (event.channel) { case 'onmessage': if (event.args && event.args.length) { @@ -509,10 +513,14 @@ export class WebviewElement extends Disposable implements Webview { } this.style(themeService.getTheme()); - themeService.onThemeChange(this.style, this, this._toDispose); + this._register(themeService.onThemeChange(this.style, this)); } public mountTo(parent: HTMLElement) { + if (!this._webview) { + return; + } + if (this._webviewFindWidget) { parent.appendChild(this._webviewFindWidget.getDomNode()!); } @@ -524,10 +532,13 @@ export class WebviewElement extends Disposable implements Webview { if (this._webview.parentElement) { this._webview.parentElement.removeChild(this._webview); } + this._webview = undefined; } - this._webview = undefined!; - this._webviewFindWidget = undefined!; + if (this._webviewFindWidget) { + this._webviewFindWidget.dispose(); + this._webviewFindWidget = undefined; + } super.dispose(); } @@ -545,7 +556,11 @@ export class WebviewElement extends Disposable implements Webview { private _send(channel: string, ...args: any[]): void { this._ready - .then(() => this._webview.send(channel, ...args)) + .then(() => { + if (this._webview) { + this._webview.send(channel, ...args); + } + }) .catch(err => console.error(err)); } @@ -604,6 +619,9 @@ export class WebviewElement extends Disposable implements Webview { } public focus(): void { + if (!this._webview) { + return; + } this._webview.focus(); this._send('focus'); @@ -661,6 +679,9 @@ export class WebviewElement extends Disposable implements Webview { } public layout(): void { + if (!this._webview) { + return; + } const contents = this._webview.getWebContents(); if (!contents || contents.isDestroyed()) { return; @@ -679,7 +700,7 @@ export class WebviewElement extends Disposable implements Webview { } public startFind(value: string, options?: Electron.FindInPageOptions) { - if (!value) { + if (!value || !this._webview) { return; } @@ -706,6 +727,10 @@ export class WebviewElement extends Disposable implements Webview { * @param value The string to search for. Empty strings are ignored. */ public find(value: string, previous: boolean): void { + if (!this._webview) { + return; + } + // Searching with an empty value will throw an exception if (!value) { return; @@ -721,6 +746,9 @@ export class WebviewElement extends Disposable implements Webview { } public stopFind(keepSelection?: boolean): void { + if (!this._webview) { + return; + } this._findStarted = false; this._webview.stopFindInPage(keepSelection ? 'keepSelection' : 'clearSelection'); } @@ -742,27 +770,39 @@ export class WebviewElement extends Disposable implements Webview { } public selectAll() { - this._webview.selectAll(); + if (this._webview) { + this._webview.selectAll(); + } } public copy() { - this._webview.copy(); + if (this._webview) { + this._webview.copy(); + } } public paste() { - this._webview.paste(); + if (this._webview) { + this._webview.paste(); + } } public cut() { - this._webview.cut(); + if (this._webview) { + this._webview.cut(); + } } public undo() { - this._webview.undo(); + if (this._webview) { + this._webview.undo(); + } } public redo() { - this._webview.redo(); + if (this._webview) { + this._webview.redo(); + } } } diff --git a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts index ed9b2cd7936a..a48253928e28 100644 --- a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts +++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts @@ -15,7 +15,7 @@ import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -150,9 +150,8 @@ export class HideWelcomeOverlayAction extends Action { } } -class WelcomeOverlay { +class WelcomeOverlay extends Disposable { - private _toDispose: IDisposable[] = []; private _overlayVisible: IContextKey; private _overlay: HTMLElement; @@ -163,6 +162,7 @@ class WelcomeOverlay { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { + super(); this._overlayVisible = OVERLAY_VISIBLE.bindTo(this._contextKeyService); this.create(); } @@ -177,7 +177,7 @@ class WelcomeOverlay { this._overlay.style.display = 'none'; this._overlay.tabIndex = -1; - this._toDispose.push(dom.addStandardDisposableListener(this._overlay, 'click', () => this.hide())); + this._register(dom.addStandardDisposableListener(this._overlay, 'click', () => this.hide())); this.commandService.onWillExecuteCommand(() => this.hide()); dom.append(this._overlay, $('.commandPalettePlaceholder')); @@ -214,7 +214,7 @@ class WelcomeOverlay { } private updateProblemsKey() { - const problems = document.querySelector('.task-statusbar-item'); + const problems = document.querySelector('div[id="workbench.parts.statusbar"] .statusbar-item.left .octicon.octicon-warning'); const key = this._overlay.querySelector('.key.problems') as HTMLElement; if (problems instanceof HTMLElement) { const target = problems.getBoundingClientRect(); @@ -237,10 +237,6 @@ class WelcomeOverlay { this._overlayVisible.reset(); } } - - dispose() { - this._toDispose = dispose(this._toDispose); - } } // {SQL CARBON EDIT} diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 9104521bc00f..0ad4931e5f0d 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -26,7 +26,7 @@ import { IExtensionEnablementService, IExtensionManagementService, IExtensionGal // {{SQL CARBON EDIT}} - Redirect to ADS welcome page import { used } from 'sql/workbench/contrib/welcome/page/browser/az_data_welcome_page'; import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lifecycle'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { splitName } from 'vs/base/common/labels'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor, focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; @@ -246,9 +246,7 @@ const keymapStrings: Strings = { const welcomeInputTypeId = 'workbench.editors.welcomePageInput'; -class WelcomePage { - - private disposables: IDisposable[] = []; +class WelcomePage extends Disposable { readonly editorInput: WalkThroughInput; @@ -268,7 +266,8 @@ class WelcomePage { @ILifecycleService lifecycleService: ILifecycleService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { - this.disposables.push(lifecycleService.onShutdown(() => this.dispose())); + super(); + this._register(lifecycleService.onShutdown(() => this.dispose())); const recentlyOpened = this.windowService.getRecentlyOpened(); const installedExtensions = this.instantiationService.invokeFunction(getInstalledExtensions); @@ -323,14 +322,14 @@ class WelcomePage { ul.append(...listEntries, moreRecent); }; updateEntries(); - this.disposables.push(this.labelService.onDidChangeFormatters(updateEntries)); + this._register(this.labelService.onDidChangeFormatters(updateEntries)); }).then(undefined, onUnexpectedError); this.addExtensionList(container, '.extensionPackList', extensionPacks, extensionPackStrings); this.addExtensionList(container, '.keymapList', keymapExtensions, keymapStrings); this.updateInstalledExtensions(container, installedExtensions); - this.disposables.push(this.instantiationService.invokeFunction(onExtensionChanged)(ids => { + this._register(this.instantiationService.invokeFunction(onExtensionChanged)(ids => { for (const id of ids) { if (container.querySelector(`.installExtension[data-extension="${id.id}"], .enabledExtension[data-extension="${id.id}"]`)) { const installedExtensions = this.instantiationService.invokeFunction(getInstalledExtensions); @@ -595,10 +594,6 @@ class WelcomePage { }); }).then(undefined, onUnexpectedError); } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } export class WelcomeInputFactory implements IEditorInputFactory { diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 5febf0f2ead5..f365a8538f4f 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -8,7 +8,7 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { EditorOptions, IEditorMemento } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -56,7 +56,7 @@ export class WalkThroughPart extends BaseEditor { static readonly ID: string = 'workbench.editor.walkThroughPart'; - private disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); private contentDisposables: IDisposable[] = []; private content: HTMLDivElement; private scrollbar: DomScrollableElement; @@ -92,13 +92,13 @@ export class WalkThroughPart extends BaseEditor { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto }); - this.disposables.push(this.scrollbar); + this.disposables.add(this.scrollbar); container.appendChild(this.scrollbar.getDomNode()); this.registerFocusHandlers(); this.registerClickHandler(); - this.disposables.push(this.scrollbar.onScroll(e => this.updatedScrollPosition())); + this.disposables.add(this.scrollbar.onScroll(e => this.updatedScrollPosition())); } private updatedScrollPosition() { @@ -120,16 +120,16 @@ export class WalkThroughPart extends BaseEditor { } private registerFocusHandlers() { - this.disposables.push(this.addEventListener(this.content, 'mousedown', e => { + this.disposables.add(this.addEventListener(this.content, 'mousedown', e => { this.focus(); })); - this.disposables.push(this.addEventListener(this.content, 'focus', e => { + this.disposables.add(this.addEventListener(this.content, 'focus', e => { this.editorFocus.set(true); })); - this.disposables.push(this.addEventListener(this.content, 'blur', e => { + this.disposables.add(this.addEventListener(this.content, 'blur', e => { this.editorFocus.reset(); })); - this.disposables.push(this.addEventListener(this.content, 'focusin', e => { + this.disposables.add(this.addEventListener(this.content, 'focusin', e => { // Work around scrolling as side-effect of setting focus on the offscreen zone widget (#18929) if (e.target instanceof HTMLElement && e.target.classList.contains('zone-widget-container')) { const scrollPosition = this.scrollbar.getScrollPosition(); @@ -514,7 +514,7 @@ export class WalkThroughPart extends BaseEditor { dispose(): void { this.editorFocus.reset(); this.contentDisposables = dispose(this.contentDisposables); - this.disposables = dispose(this.disposables); + this.disposables.dispose(); super.dispose(); } } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts index d8e02a8ec521..1f558f35ea9c 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts @@ -6,7 +6,7 @@ import * as strings from 'vs/base/common/strings'; import { EditorInput, EditorModel, ITextEditorModel } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { IReference, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as marked from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; @@ -46,8 +46,6 @@ export interface WalkThroughInputOptions { export class WalkThroughInput extends EditorInput { - private disposables: IDisposable[] = []; - private promise: Promise | null = null; private maxTopScroll = 0; @@ -139,8 +137,6 @@ export class WalkThroughInput extends EditorInput { } dispose(): void { - this.disposables = dispose(this.disposables); - if (this.promise) { this.promise.then(model => model.dispose()); this.promise = null; diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 4116bbcbdb53..8f388b135329 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -21,7 +21,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; -import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; +import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext, RemoteFileDialogContext, IsFullscreenContext } from 'vs/workbench/browser/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { LogStorageAction } from 'vs/platform/storage/node/storageService'; @@ -376,7 +376,8 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/electron-brow group: '1_toggle_view', command: { id: ToggleFullScreenAction.ID, - title: nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen") + title: nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "&&Full Screen"), + toggled: IsFullscreenContext }, order: 1 }); diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index a2d7daece988..092ccb0370b8 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -23,7 +23,6 @@ import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { webFrame } from 'electron'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log'; import { StorageService } from 'vs/platform/storage/node/storageService'; import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc'; @@ -49,6 +48,7 @@ import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } f import { DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationExportHelper'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService'; +import { SpdLogService } from 'vs/platform/log/node/spdlogService'; class CodeRendererMain extends Disposable { @@ -230,7 +230,7 @@ class CodeRendererMain extends Disposable { // Multi-root workspace if (this.configuration.workspace) { - return Promise.resolve(this.configuration.workspace); + return this.configuration.workspace; } // Single-folder workspace @@ -260,7 +260,7 @@ class CodeRendererMain extends Disposable { // Return early the folder is not local if (folderUri.scheme !== Schemas.file) { - return Promise.resolve({ id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri }); + return { id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri }; } function computeLocalDiskFolderId(folder: URI, stat: fs.Stats): string { @@ -303,7 +303,7 @@ class CodeRendererMain extends Disposable { const configurationFileService = new ConfigurationFileService(); configurationFileService.fileService = fileService; - const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); + const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); try { await workspaceService.initialize(payload); @@ -334,7 +334,7 @@ class CodeRendererMain extends Disposable { } private createLogService(mainProcessService: IMainProcessService, environmentService: IWorkbenchEnvironmentService): ILogService { - const spdlogService = createBufferSpdLogService(`renderer${this.configuration.windowId}`, this.configuration.logLevel, environmentService.logsPath); + const spdlogService = new SpdLogService(`renderer${this.configuration.windowId}`, environmentService.logsPath, this.configuration.logLevel); const consoleLogService = new ConsoleLogService(this.configuration.logLevel); const logService = new MultiplexLogService([consoleLogService, spdlogService]); const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel')); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 068ced2e256f..75e0a967bfca 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -28,7 +28,7 @@ import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction } from 'vs/ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { fillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; @@ -61,7 +61,7 @@ export class ElectronWindow extends Disposable { private touchBarMenu?: IMenu; private touchBarUpdater: RunOnceScheduler; - private touchBarDisposables: IDisposable[]; + private readonly touchBarDisposables = this._register(new DisposableStore()); private lastInstalledTouchedBar: ICommandAction[][]; private previousConfiguredZoomLevel: number; @@ -95,8 +95,6 @@ export class ElectronWindow extends Disposable { ) { super(); - this.touchBarDisposables = []; - this.pendingFoldersToAdd = []; this.addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100)); @@ -314,22 +312,24 @@ export class ElectronWindow extends Disposable { this.integrityService.isPure().then(res => this.titleService.updateProperties({ isPure: res.isPure })); // Root warning - this.lifecycleService.when(LifecyclePhase.Restored).then(async () => { - let isAdmin: boolean; + this.lifecycleService.when(LifecyclePhase.Restored).then(() => { + let isAdminPromise: Promise; if (isWindows) { - const isElevated = await import('native-is-elevated'); - isAdmin = isElevated(); + isAdminPromise = import('native-is-elevated').then(isElevated => isElevated()); // not using async here due to https://github.com/microsoft/vscode/issues/74321 } else { - isAdmin = isRootUser(); + isAdminPromise = Promise.resolve(isRootUser()); } - // Update title - this.titleService.updateProperties({ isAdmin }); + return isAdminPromise.then(isAdmin => { - // Show warning message (unix only) - if (isAdmin && !isWindows) { - this.notificationService.warn(nls.localize('runningAsRoot', "It is not recommended to run {0} as root user.", product.nameShort)); - } + // Update title + this.titleService.updateProperties({ isAdmin }); + + // Show warning message (unix only) + if (isAdmin && !isWindows) { + this.notificationService.warn(nls.localize('runningAsRoot', "It is not recommended to run {0} as root user.", product.nameShort)); + } + }); }); // Touchbar menu (if enabled) @@ -350,20 +350,20 @@ export class ElectronWindow extends Disposable { } // Dispose old - this.touchBarDisposables = dispose(this.touchBarDisposables); + this.touchBarDisposables.clear(); this.touchBarMenu = undefined; // Create new (delayed) this.touchBarUpdater = new RunOnceScheduler(() => this.doUpdateTouchbarMenu(), 300); - this.touchBarDisposables.push(this.touchBarUpdater); + this.touchBarDisposables.add(this.touchBarUpdater); this.touchBarUpdater.schedule(); } private doUpdateTouchbarMenu(): void { if (!this.touchBarMenu) { this.touchBarMenu = this.editorService.invokeWithinEditorContext(accessor => this.menuService.createMenu(MenuId.TouchBarContext, accessor.get(IContextKeyService))); - this.touchBarDisposables.push(this.touchBarMenu); - this.touchBarDisposables.push(this.touchBarMenu.onDidChange(() => this.touchBarUpdater.schedule())); + this.touchBarDisposables.add(this.touchBarMenu); + this.touchBarDisposables.add(this.touchBarMenu.onDidChange(() => this.touchBarUpdater.schedule())); } const actions: Array = []; @@ -531,10 +531,4 @@ export class ElectronWindow extends Disposable { // Otherwise open all return this.editorService.openEditors(resources); } - - dispose(): void { - this.touchBarDisposables = dispose(this.touchBarDisposables); - - super.dispose(); - } } diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index ef880c0dca99..1632bc80087b 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -352,7 +352,7 @@ class BackupFileServiceImpl implements IBackupFileService { export class InMemoryBackupFileService implements IBackupFileService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private backups: Map = new Map(); diff --git a/src/vs/workbench/services/broadcast/common/broadcast.ts b/src/vs/workbench/services/broadcast/common/broadcast.ts deleted file mode 100644 index a6a915102030..000000000000 --- a/src/vs/workbench/services/broadcast/common/broadcast.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; - -export const IBroadcastService = createDecorator('broadcastService'); - -export interface IBroadcast { - channel: string; - payload: any; -} - -export interface IBroadcastService { - _serviceBrand: any; - - onBroadcast: Event; - - broadcast(b: IBroadcast): void; -} - -export class NullBroadcastService implements IBroadcastService { - _serviceBrand: any; - onBroadcast: Event = Event.None; - broadcast(_b: IBroadcast): void { - - } -} diff --git a/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts b/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts deleted file mode 100644 index d278ceba6a3b..000000000000 --- a/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts +++ /dev/null @@ -1,51 +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, Emitter } from 'vs/base/common/event'; -import { ipcRenderer as ipc } from 'electron'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IBroadcastService, IBroadcast } from 'vs/workbench/services/broadcast/common/broadcast'; - -class BroadcastService extends Disposable implements IBroadcastService { - _serviceBrand: any; - - private readonly _onBroadcast: Emitter = this._register(new Emitter()); - get onBroadcast(): Event { return this._onBroadcast.event; } - - private windowId: number; - - constructor( - @IWindowService readonly windowService: IWindowService, - @ILogService private readonly logService: ILogService - ) { - super(); - - this.windowId = windowService.windowId; - - this.registerListeners(); - } - - private registerListeners(): void { - ipc.on('vscode:broadcast', (event: unknown, b: IBroadcast) => { - this.logService.trace(`Received broadcast from main in window ${this.windowId}: `, b); - - this._onBroadcast.fire(b); - }); - } - - broadcast(b: IBroadcast): void { - this.logService.trace(`Sending broadcast to main from window ${this.windowId}: `, b); - - ipc.send('vscode:broadcast', this.windowId, { - channel: b.channel, - payload: b.payload - }); - } -} - -registerSingleton(IBroadcastService, BroadcastService, true); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 01ae4ffdab74..f064b553b7f6 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -21,8 +21,8 @@ import { extname, join } from 'vs/base/common/path'; import { equals } from 'vs/base/common/objects'; import { Schemas } from 'vs/base/common/network'; import { IConfigurationModel, compare } from 'vs/platform/configuration/common/configuration'; -import { createSHA1 } from 'vs/base/browser/hash'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { hash } from 'vs/base/common/hash'; export class RemoteUserConfiguration extends Disposable { @@ -672,7 +672,7 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati readonly onDidChange: Event = this._onDidChange.event; private configurationModel: ConfigurationModel; - private readonly key: Thenable; + private readonly key: ConfigurationKey; constructor( folder: URI, @@ -680,14 +680,13 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati private readonly configurationCache: IConfigurationCache ) { super(); - this.key = createSHA1(join(folder.path, configFolderRelativePath)).then(key => ({ type: 'folder', key })); + this.key = { type: 'folder', key: hash(join(folder.path, configFolderRelativePath)).toString(16) }; this.configurationModel = new ConfigurationModel(); } async loadConfiguration(): Promise { try { - const key = await this.key; - const contents = await this.configurationCache.read(key); + const contents = await this.configurationCache.read(this.key); const parsed: IConfigurationModel = JSON.parse(contents.toString()); this.configurationModel = new ConfigurationModel(parsed.contents, parsed.keys, parsed.overrides); } catch (e) { @@ -696,11 +695,10 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati } async updateConfiguration(configurationModel: ConfigurationModel): Promise { - const key = await this.key; if (configurationModel.keys.length) { - await this.configurationCache.write(key, JSON.stringify(configurationModel.toJSON())); + await this.configurationCache.write(this.key, JSON.stringify(configurationModel.toJSON())); } else { - await this.configurationCache.remove(key); + await this.configurationCache.remove(this.key); } } diff --git a/src/vs/workbench/services/configuration/browser/configurationCache.ts b/src/vs/workbench/services/configuration/browser/configurationCache.ts new file mode 100644 index 000000000000..4089b4d5cbbb --- /dev/null +++ b/src/vs/workbench/services/configuration/browser/configurationCache.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/configuration/common/configuration'; + +export class ConfigurationCache implements IConfigurationCache { + + constructor() { + } + + async read(key: ConfigurationKey): Promise { + return ''; + } + + async write(key: ConfigurationKey, content: string): Promise { + } + + async remove(key: ConfigurationKey): Promise { + } +} \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index 73725d8b0e74..404feb75ef9a 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -528,7 +528,7 @@ export class ConfigurationEditingService { private getConfigurationFileResource(target: EditableConfigurationTarget, config: IConfigurationValue, relativePath: string, resource: URI | null | undefined): URI | null { if (target === EditableConfigurationTarget.USER_LOCAL) { - return URI.file(this.environmentService.appSettingsPath); + return this.environmentService.settingsResource; } if (target === EditableConfigurationTarget.USER_REMOTE) { return this.remoteSettingsResource; diff --git a/src/vs/workbench/services/configuration/common/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts index c897ba7570c1..122041d4dd04 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -39,10 +39,11 @@ export class JSONEditingService implements IJSONEditingService { return Promise.resolve(this.queue.queue(() => this.doWriteConfiguration(resource, value, save))); // queue up writes to prevent race conditions } - private doWriteConfiguration(resource: URI, value: IJSONValue, save: boolean): Promise { - return this.resolveAndValidate(resource, save) - .then(reference => this.writeToBuffer(reference.object.textEditorModel, value) - .then(() => reference.dispose())); + private async doWriteConfiguration(resource: URI, value: IJSONValue, save: boolean): Promise { + const reference = await this.resolveAndValidate(resource, save); + await this.writeToBuffer(reference.object.textEditorModel, value); + + reference.dispose(); } private async writeToBuffer(model: ITextModel, value: IJSONValue): Promise { @@ -97,21 +98,21 @@ export class JSONEditingService implements IJSONEditingService { return parseErrors.length > 0; } - private resolveAndValidate(resource: URI, checkDirty: boolean): Promise> { - return this.resolveModelReference(resource) - .then(reference => { - const model = reference.object.textEditorModel; - - if (this.hasParseErrors(model)) { - return this.reject>(JSONEditingErrorCode.ERROR_INVALID_FILE); - } - - // Target cannot be dirty if not writing into buffer - if (checkDirty && this.textFileService.isDirty(resource)) { - return this.reject>(JSONEditingErrorCode.ERROR_FILE_DIRTY); - } - return reference; - }); + private async resolveAndValidate(resource: URI, checkDirty: boolean): Promise> { + const reference = await this.resolveModelReference(resource); + + const model = reference.object.textEditorModel; + + if (this.hasParseErrors(model)) { + return this.reject>(JSONEditingErrorCode.ERROR_INVALID_FILE); + } + + // Target cannot be dirty if not writing into buffer + if (checkDirty && this.textFileService.isDirty(resource)) { + return this.reject>(JSONEditingErrorCode.ERROR_FILE_DIRTY); + } + + return reference; } private reject(code: JSONEditingErrorCode): Promise { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index a711f8b6b853..25318c58f5ca 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -47,7 +47,7 @@ class SettingsTestEnvironmentService extends EnvironmentService { super(args, _execPath); } - get appSettingsPath(): string { return this.customAppSettingsHome; } + get settingsResource(): URI { return URI.file(this.customAppSettingsHome); } } suite('ConfigurationEditingService', () => { @@ -106,7 +106,7 @@ suite('ConfigurationEditingService', () => { instantiationService.stub(IEnvironmentService, environmentService); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache: new ConfigurationCache(environmentService) }, new ConfigurationFileService(), remoteAgentService); + const workspaceService = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache: new ConfigurationCache(environmentService) }, new ConfigurationFileService(), remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => { instantiationService.stub(IConfigurationService, workspaceService); @@ -187,7 +187,7 @@ suite('ConfigurationEditingService', () => { test('do not notify error', () => { instantiationService.stub(ITextFileService, 'isDirty', true); const target = sinon.stub(); - instantiationService.stub(INotificationService, { prompt: target, _serviceBrand: null, notify: null!, error: null!, info: null!, warn: null! }); + instantiationService.stub(INotificationService, { prompt: target, _serviceBrand: null!, notify: null!, error: null!, info: null!, warn: null!, status: null! }); return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key: 'configurationEditing.service.testSetting', value: 'value' }, { donotNotifyError: true }) .then(() => assert.fail('Should fail with ERROR_CONFIGURATION_FILE_DIRTY error.'), (error: ConfigurationEditingError) => { diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 6c0b3a728db2..8e9d1d088611 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -44,6 +44,7 @@ import { ConfigurationCache } from 'vs/workbench/services/configuration/node/con import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IConfigurationCache } from 'vs/workbench/services/configuration/common/configuration'; +import { VSBuffer } from 'vs/base/common/buffer'; class SettingsTestEnvironmentService extends EnvironmentService { @@ -51,7 +52,7 @@ class SettingsTestEnvironmentService extends EnvironmentService { super(args, _execPath); } - get appSettingsPath(): string { return this.customAppSettingsHome; } + get settingsResource(): URI { return URI.file(this.customAppSettingsHome); } } function setUpFolderWorkspace(folderName: string): Promise<{ parentDir: string, folderDir: string }> { @@ -153,7 +154,7 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { const configurationFileService = new ConfigurationFileService(); configurationFileService.fileService = fileService; const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve() }; - testObject = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache, remoteAuthority }, configurationFileService, remoteAgentService); + testObject = new WorkspaceService({ userSettingsResource: environmentService.settingsResource, configurationCache, remoteAuthority }, configurationFileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); instantiationService.stub(IEnvironmentService, environmentService); @@ -247,26 +248,26 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { return promise; }); - // test('update remote settings', async () => { - // registerRemoteFileSystemProvider(); - // resolveRemoteEnvironment(); - // await initialize(); - // assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'isSet'); - // const promise = new Promise((c, e) => { - // testObject.onDidChangeConfiguration(event => { - // try { - // assert.equal(event.source, ConfigurationTarget.USER); - // assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']); - // assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue'); - // c(); - // } catch (error) { - // e(error); - // } - // }); - // }); - // fs.writeFileSync(remoteSettingsFile, '{ "configurationService.remote.machineSetting": "remoteValue" }'); - // return promise; - // }); + test('update remote settings', async () => { + registerRemoteFileSystemProvider(); + resolveRemoteEnvironment(); + await initialize(); + assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'isSet'); + const promise = new Promise((c, e) => { + testObject.onDidChangeConfiguration(event => { + try { + assert.equal(event.source, ConfigurationTarget.USER); + assert.deepEqual(event.affectedKeys, ['configurationService.remote.machineSetting']); + assert.equal(testObject.getValue('configurationService.remote.machineSetting'), 'remoteValue'); + c(); + } catch (error) { + e(error); + } + }); + }); + await instantiationService.get(IFileService).writeFile(URI.file(remoteSettingsFile), VSBuffer.fromString('{ "configurationService.remote.machineSetting": "remoteValue" }')); + return promise; + }); test('machine settings in local user settings does not override defaults', async () => { fs.writeFileSync(globalSettingsFile, '{ "configurationService.remote.machineSetting": "globalValue" }'); diff --git a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index f3a66bf4e455..d29975910a01 100644 --- a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -115,7 +115,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService } } - private createMenu(delegate: IContextMenuDelegate, entries: Array, onHide: () => void): IContextMenuItem[] { + private createMenu(delegate: IContextMenuDelegate, entries: ReadonlyArray, onHide: () => void): IContextMenuItem[] { const actionRunner = delegate.actionRunner || new ActionRunner(); return entries.map(entry => this.createMenuItem(delegate, entry, actionRunner, onHide)); diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index dec166f6973b..c3c233a680f1 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -7,7 +7,7 @@ import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IDecorationsService, IDecoration, IResourceDecorationChangeEvent, IDecorationsProvider, IDecorationData } from './decorations'; import { TernarySearchTree } from 'vs/base/common/map'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { isThenable } from 'vs/base/common/async'; import { LinkedList } from 'vs/base/common/linkedList'; import { createStyleSheet, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; @@ -95,20 +95,20 @@ class DecorationRule { } } -class DecorationStyles { +class DecorationStyles extends Disposable { - private readonly _disposables: IDisposable[]; private readonly _styleElement = createStyleSheet(); private readonly _decorationRules = new Map(); constructor( private _themeService: IThemeService, ) { - this._disposables = [this._themeService.onThemeChange(this._onThemeChange, this)]; + super(); + this._register(this._themeService.onThemeChange(this._onThemeChange, this)); } dispose(): void { - dispose(this._disposables); + super.dispose(); const parent = this._styleElement.parentElement; if (parent) { diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index e9821d103088..281cf67b9e02 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -86,6 +86,7 @@ export class FileDialogService implements IFileDialogService { private shouldUseSimplified(schema: string): boolean { const setting = this.configurationService.getValue('files.simpleDialog.enable'); + return (schema !== Schemas.file) || (setting === true); } @@ -93,7 +94,7 @@ export class FileDialogService implements IFileDialogService { return schema !== Schemas.file ? [schema, Schemas.file] : [schema]; } - pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { + async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -103,21 +104,23 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => { - if (uri) { - return (this.fileService.resolve(uri)).then(stat => { - const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; - return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); - }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + + if (uri) { + const stat = await this.fileService.resolve(uri); + + const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; + return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options)); } - pickFileAndOpen(options: IPickAndOpenOptions): Promise { + async pickFileAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -127,18 +130,19 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFile.title', 'Open File'); const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => { - if (uri) { - return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options)); } - pickFolderAndOpen(options: IPickAndOpenOptions): Promise { + async pickFolderAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -148,18 +152,19 @@ export class FileDialogService implements IFileDialogService { if (this.shouldUseSimplified(schema)) { const title = nls.localize('openFolder.title', 'Open Folder'); const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }).then(uri => { - if (uri) { - return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options)); } - pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { + async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); if (!options.defaultUri) { @@ -170,12 +175,13 @@ export class FileDialogService implements IFileDialogService { const title = nls.localize('openWorkspace.title', 'Open Workspace'); const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well - return this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }).then(uri => { - if (uri) { - return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - return undefined; - }); + + const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + + return; } return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); @@ -190,33 +196,34 @@ export class FileDialogService implements IFileDialogService { }; } - showSaveDialog(options: ISaveDialogOptions): Promise { + async showSaveDialog(options: ISaveDialogOptions): Promise { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { options.availableFileSystems = [schema]; // by default only allow saving in the own file system } + return this.saveRemoteResource(options); } - return this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)).then(result => { - if (result) { - return URI.file(result); - } + const result = await this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)); + if (result) { + return URI.file(result); + } - return undefined; - }); + return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check } - showOpenDialog(options: IOpenDialogOptions): Promise { + async showOpenDialog(options: IOpenDialogOptions): Promise { const schema = this.getFileSystemSchema(options); if (this.shouldUseSimplified(schema)) { if (!options.availableFileSystems) { options.availableFileSystems = [schema]; // by default only allow loading in the own file system } - return this.pickRemoteResource(options).then(uri => { - return uri ? [uri] : undefined; - }); + + const uri = await this.pickRemoteResource(options); + + return uri ? [uri] : undefined; } const defaultUri = options.defaultUri; @@ -243,16 +250,20 @@ export class FileDialogService implements IFileDialogService { newOptions.properties!.push('multiSelections'); } - return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined); + const result = await this.windowService.showOpenDialog(newOptions); + + return result ? result.map(URI.file) : undefined; } private pickRemoteResource(options: IOpenDialogOptions): Promise { const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); + return remoteFileDialog.showOpenDialog(options); } private saveRemoteResource(options: ISaveDialogOptions): Promise { const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); + return remoteFileDialog.showSaveDialog(options); } @@ -263,7 +274,6 @@ export class FileDialogService implements IFileDialogService { private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string { return options.availableFileSystems && options.availableFileSystems[0] || options.defaultUri && options.defaultUri.scheme || this.getSchemeFilterForWindow(); } - } function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean { diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts index 604bb31337d5..8c52d45c4b21 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts @@ -28,6 +28,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { isValidBasename } from 'vs/base/common/extpath'; import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; +import { Emitter } from 'vs/base/common/event'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; interface FileQuickPickItem extends IQuickPickItem { uri: URI; @@ -60,6 +62,11 @@ export class RemoteFileDialog { private badPath: string | undefined; private remoteAgentEnvironment: IRemoteAgentEnvironment | null; private separator: string; + private onBusyChangeEmitter = new Emitter(); + + protected disposables: IDisposable[] = [ + this.onBusyChangeEmitter + ]; constructor( @IFileService private readonly fileService: IFileService, @@ -79,6 +86,17 @@ export class RemoteFileDialog { this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService); } + set busy(busy: boolean) { + if (this.filePickBox.busy !== busy) { + this.filePickBox.busy = busy; + this.onBusyChangeEmitter.fire(busy); + } + } + + get busy(): boolean { + return this.filePickBox.busy; + } + public async showOpenDialog(options: IOpenDialogOptions = {}): Promise { this.scheme = this.getScheme(options.defaultUri, options.availableFileSystems); this.userHome = await this.getUserHome(); @@ -185,7 +203,7 @@ export class RemoteFileDialog { return new Promise(async (resolve) => { this.filePickBox = this.quickInputService.createQuickPick(); - this.filePickBox.busy = true; + this.busy = true; this.filePickBox.matchOnLabel = false; this.filePickBox.autoFocusOnList = false; this.filePickBox.ignoreFocusOut = true; @@ -203,7 +221,7 @@ export class RemoteFileDialog { } } - let isResolving = false; + let isResolving: number = 0; let isAcceptHandled = false; this.currentFolder = homedir; this.userEnteredPathSegment = ''; @@ -218,15 +236,16 @@ export class RemoteFileDialog { resolve(uri); dialog.contextKey.set(false); dialog.filePickBox.dispose(); + dispose(dialog.disposables); } this.filePickBox.onDidCustom(() => { - if (isAcceptHandled || this.filePickBox.busy) { + if (isAcceptHandled || this.busy) { return undefined; // {{SQL CARBON EDIT}} @todo anthonydresser return to return; when we do strict null checks } isAcceptHandled = true; - isResolving = true; + isResolving++; if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) { this.options.availableFileSystems.shift(); } @@ -243,25 +262,38 @@ export class RemoteFileDialog { } }); - this.filePickBox.onDidAccept(_ => { - if (isAcceptHandled || this.filePickBox.busy) { + function handleAccept(dialog: RemoteFileDialog) { + if (dialog.busy) { + // Save the accept until the file picker is not busy. + dialog.onBusyChangeEmitter.event((busy: boolean) => { + if (!busy) { + handleAccept(dialog); + } + }); + return; + } else if (isAcceptHandled) { return; } isAcceptHandled = true; - isResolving = true; - this.onDidAccept().then(resolveValue => { + isResolving++; + dialog.onDidAccept().then(resolveValue => { if (resolveValue) { - this.filePickBox.hide(); - doResolve(this, resolveValue); - } else if (this.hidden) { - doResolve(this, undefined); + dialog.filePickBox.hide(); + doResolve(dialog, resolveValue); + } else if (dialog.hidden) { + doResolve(dialog, undefined); } else { - isResolving = false; + isResolving--; isAcceptHandled = false; } }); + } + + this.filePickBox.onDidAccept(_ => { + handleAccept(this); }); + this.filePickBox.onDidChangeActive(i => { isAcceptHandled = false; // update input box to match the first selected item @@ -296,7 +328,7 @@ export class RemoteFileDialog { }); this.filePickBox.onDidHide(() => { this.hidden = true; - if (!isResolving) { + if (isResolving === 0) { doResolve(this, undefined); } }); @@ -309,7 +341,7 @@ export class RemoteFileDialog { } else { this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length]; } - this.filePickBox.busy = false; + this.busy = false; }); } @@ -340,14 +372,15 @@ export class RemoteFileDialog { const relativePath = resources.relativePath(currentDisplayUri, directUri); const isSameRoot = (this.filePickBox.value.length > 1 && currentPath.length > 1) ? equalsIgnoreCase(this.filePickBox.value.substr(0, 2), currentPath.substr(0, 2)) : false; if (relativePath && isSameRoot) { - return resources.joinPath(this.currentFolder, relativePath); + const path = resources.joinPath(this.currentFolder, relativePath); + return resources.hasTrailingPathSeparator(directUri) ? resources.addTrailingPathSeparator(path) : path; } else { return directUri; } } private async onDidAccept(): Promise { - this.filePickBox.busy = true; + this.busy = true; if (this.filePickBox.activeItems.length === 1) { const item = this.filePickBox.selectedItems[0]; if (item.isFolder) { @@ -357,10 +390,9 @@ export class RemoteFileDialog { // When possible, cause the update to happen by modifying the input box. // This allows all input box updates to happen first, and uses the same code path as the user typing. const newPath = this.pathFromUri(item.uri); - if (startsWithIgnoreCase(newPath, this.filePickBox.value)) { - const insertValue = newPath.substring(this.filePickBox.value.length, newPath.length); - this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length]; - this.insertText(newPath, insertValue); + if (startsWithIgnoreCase(newPath, this.filePickBox.value) && (equalsIgnoreCase(item.label, resources.basename(item.uri)))) { + this.filePickBox.valueSelection = [this.pathFromUri(this.currentFolder).length, this.filePickBox.value.length]; + this.insertText(newPath, item.label); } else if ((item.label === '..') && startsWithIgnoreCase(this.filePickBox.value, newPath)) { this.filePickBox.valueSelection = [newPath.length, this.filePickBox.value.length]; this.insertText(newPath, ''); @@ -368,11 +400,13 @@ export class RemoteFileDialog { await this.updateItems(item.uri, true); } } + this.filePickBox.busy = false; return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check } } else { // If the items have updated, don't try to resolve if ((await this.tryUpdateItems(this.filePickBox.value, this.filePickBoxValue())) !== UpdateResult.NotUpdated) { + this.filePickBox.busy = false; return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check } } @@ -388,10 +422,10 @@ export class RemoteFileDialog { resolveValue = this.addPostfix(resolveValue); } if (await this.validate(resolveValue)) { - this.filePickBox.busy = false; + this.busy = false; return resolveValue; } - this.filePickBox.busy = false; + this.busy = false; return undefined; } @@ -470,7 +504,7 @@ export class RemoteFileDialog { } private setAutoComplete(startingValue: string, startingBasename: string, quickPickItem: FileQuickPickItem, force: boolean = false): boolean { - if (this.filePickBox.busy) { + if (this.busy) { // We're in the middle of something else. Doing an auto complete now can result jumbled or incorrect autocompletes. this.userEnteredPathSegment = startingBasename; this.autoCompletePathSegment = ''; @@ -494,9 +528,6 @@ export class RemoteFileDialog { // Changing the active items will trigger the onDidActiveItemsChanged. Clear the autocomplete first, then set it after. this.autoCompletePathSegment = ''; this.filePickBox.activeItems = [quickPickItem]; - this.autoCompletePathSegment = this.trimTrailingSlash(itemBasename.substr(startingBasename.length)); - this.insertText(startingValue + this.autoCompletePathSegment, this.autoCompletePathSegment); - this.filePickBox.valueSelection = [startingValue.length, this.filePickBox.value.length]; return true; } else if (force && (!equalsIgnoreCase(quickPickItem.label, (this.userEnteredPathSegment + this.autoCompletePathSegment)))) { this.userEnteredPathSegment = ''; @@ -641,7 +672,7 @@ export class RemoteFileDialog { } private async updateItems(newFolder: URI, force: boolean = false, trailing?: string) { - this.filePickBox.busy = true; + this.busy = true; this.userEnteredPathSegment = trailing ? trailing : ''; this.autoCompletePathSegment = ''; const newValue = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true); @@ -663,7 +694,7 @@ export class RemoteFileDialog { // If there is trailing, we don't move the cursor. If there is no trailing, cursor goes at the end. this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length]; } - this.filePickBox.busy = false; + this.busy = false; }); } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index e7a83af3516b..c46326567e90 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -658,18 +658,17 @@ export class DelegatingEditorService extends EditorService { this.editorOpenHandler = handler; } - protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { + protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { if (!this.editorOpenHandler) { return super.doOpenEditor(group, editor, options); } - return this.editorOpenHandler(group, editor, options).then(control => { - if (control) { - return control; // the opening was handled, so return early - } + const control = await this.editorOpenHandler(group, editor, options); + if (control) { + return control; // the opening was handled, so return early + } - return super.doOpenEditor(group, editor, options); - }); + return super.doOpenEditor(group, editor, options); } } diff --git a/src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts index 8db1f8421dda..4755baa5ee87 100644 --- a/src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/node/extensionEnablementService.ts @@ -14,8 +14,9 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { isUndefinedOrNull } from 'vs/base/common/types'; import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IProductService } from 'vs/platform/product/common/product'; const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled'; const ENABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/enabled'; @@ -36,6 +37,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IConfigurationService private readonly configurationService: IConfigurationService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IProductService private readonly productService: IProductService, ) { super(); this.storageManger = this._register(new StorageManager(storageService)); @@ -137,7 +139,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension return disabledExtensions.some(id => areSameExtensions({ id }, extension.identifier)); } if (this.environmentService.configuration.remoteAuthority) { - const server = isUIExtension(extension.manifest, this.configurationService) ? this.extensionManagementServerService.localExtensionManagementServer : this.extensionManagementServerService.remoteExtensionManagementServer; + const server = isUIExtension(extension.manifest, this.productService, this.configurationService) ? this.extensionManagementServerService.localExtensionManagementServer : this.extensionManagementServerService.remoteExtensionManagementServer; return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server; } return false; diff --git a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts index aa1f950c7837..a35380c732b4 100644 --- a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts @@ -15,6 +15,7 @@ import { IExtensionContributions, ExtensionType, IExtension } from 'vs/platform/ import { isUndefinedOrNull } from 'vs/base/common/types'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ProductService } from 'vs/platform/product/node/productService'; function storageService(instantiationService: TestInstantiationService): IStorageService { let service = instantiationService.get(IStorageService); @@ -38,7 +39,9 @@ export class TestExtensionEnablementService extends ExtensionEnablementService { instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, { configuration: Object.create(null) } as IWorkbenchEnvironmentService), instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService), - instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService)); + instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService), + new ProductService() + ); } public reset(): void { diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts new file mode 100644 index 000000000000..de1dcd86b55e --- /dev/null +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IProductService } from 'vs/platform/product/common/product'; +import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { browserWebSocketFactory } from 'vs/platform/remote/browser/browserWebSocketFactory'; +import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager'; +import { RemoteExtensionHostClient, IInitDataProvider } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; +import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; + +export class ExtensionService extends AbstractExtensionService implements IExtensionService { + + private _remoteExtensionsEnvironmentData: IRemoteAgentEnvironment | null; + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @INotificationService notificationService: INotificationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ITelemetryService telemetryService: ITelemetryService, + @IExtensionEnablementService extensionEnablementService: IExtensionEnablementService, + @IFileService fileService: IFileService, + @IProductService productService: IProductService, + @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + ) { + super( + instantiationService, + notificationService, + environmentService, + telemetryService, + extensionEnablementService, + fileService, + productService, + ); + + this._remoteExtensionsEnvironmentData = null; + this._initialize(); + } + + private _createProvider(remoteAuthority: string): IInitDataProvider { + return { + remoteAuthority: remoteAuthority, + getInitData: () => { + return this.whenInstalledExtensionsRegistered().then(() => { + return this._remoteExtensionsEnvironmentData!; + }); + } + }; + } + + protected _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] { + const result: ExtensionHostProcessManager[] = []; + + const remoteAgentConnection = this._remoteAgentService.getConnection()!; + const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), browserWebSocketFactory); + const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); + result.push(remoteExtHostProcessManager); + + return result; + } + + protected async _scanAndHandleExtensions(): Promise { + // fetch the remote environment + const remoteEnv = (await this._remoteAgentService.getEnvironment())!; + + // enable or disable proposed API per extension + this._checkEnableProposedApi(remoteEnv.extensions); + + // remove disabled extensions + remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension)); + + // save for remote extension's init data + this._remoteExtensionsEnvironmentData = remoteEnv; + + // this._handleExtensionPoints(([]).concat(remoteEnv.extensions).concat(localExtensions)); + const result = this._registry.deltaExtensions(remoteEnv.extensions, []); + if (result.removedDueToLooping.length > 0) { + this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', '))); + } + + this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions()); + } + + public _onExtensionHostExit(code: number): void { + console.log(`vscode:exit`, code); + // ipc.send('vscode:exit', code); + } +} + +registerSingleton(IExtensionService, ExtensionService); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts new file mode 100644 index 000000000000..146fc36ea074 --- /dev/null +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -0,0 +1,498 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { Barrier } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import * as perf from 'vs/base/common/performance'; +import { isEqualOrParent } from 'vs/base/common/resources'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { BetterMergeId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; +import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; +import { IProductService } from 'vs/platform/product/common/product'; + +const hasOwnProperty = Object.hasOwnProperty; +const NO_OP_VOID_PROMISE = Promise.resolve(undefined); + +export abstract class AbstractExtensionService extends Disposable implements IExtensionService { + + public _serviceBrand: any; + + protected readonly _onDidRegisterExtensions: Emitter = this._register(new Emitter()); + public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event; + + protected readonly _onDidChangeExtensionsStatus: Emitter = this._register(new Emitter()); + public readonly onDidChangeExtensionsStatus: Event = this._onDidChangeExtensionsStatus.event; + + protected readonly _onDidChangeExtensions: Emitter = this._register(new Emitter()); + public readonly onDidChangeExtensions: Event = this._onDidChangeExtensions.event; + + protected readonly _onWillActivateByEvent = this._register(new Emitter()); + public readonly onWillActivateByEvent: Event = this._onWillActivateByEvent.event; + + protected readonly _onDidChangeResponsiveChange = this._register(new Emitter()); + public readonly onDidChangeResponsiveChange: Event = this._onDidChangeResponsiveChange.event; + + protected readonly _registry: ExtensionDescriptionRegistry; + private readonly _installedExtensionsReady: Barrier; + protected readonly _isDev: boolean; + private readonly _extensionsMessages: Map; + protected readonly _allRequestedActivateEvents: { [activationEvent: string]: boolean; }; + private readonly _proposedApiController: ProposedApiController; + private readonly _isExtensionDevHost: boolean; + protected readonly _isExtensionDevTestFromCli: boolean; + + // --- Members used per extension host process + protected _extensionHostProcessManagers: ExtensionHostProcessManager[]; + protected _extensionHostActiveExtensions: Map; + private _extensionHostProcessActivationTimes: Map; + private _extensionHostExtensionRuntimeErrors: Map; + + constructor( + @IInstantiationService protected readonly _instantiationService: IInstantiationService, + @INotificationService protected readonly _notificationService: INotificationService, + @IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService, + @ITelemetryService protected readonly _telemetryService: ITelemetryService, + @IExtensionEnablementService protected readonly _extensionEnablementService: IExtensionEnablementService, + @IFileService protected readonly _fileService: IFileService, + @IProductService protected readonly _productService: IProductService + ) { + super(); + + // help the file service to activate providers by activating extensions by file system event + this._register(this._fileService.onWillActivateFileSystemProvider(e => { + e.join(this.activateByEvent(`onFileSystem:${e.scheme}`)); + })); + + this._registry = new ExtensionDescriptionRegistry([]); + this._installedExtensionsReady = new Barrier(); + this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; + this._extensionsMessages = new Map(); + this._allRequestedActivateEvents = Object.create(null); + this._proposedApiController = new ProposedApiController(this._environmentService, this._productService); + + this._extensionHostProcessManagers = []; + this._extensionHostActiveExtensions = new Map(); + this._extensionHostProcessActivationTimes = new Map(); + this._extensionHostExtensionRuntimeErrors = new Map(); + + const devOpts = parseExtensionDevOptions(this._environmentService); + this._isExtensionDevHost = devOpts.isExtensionDevHost; + this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli; + } + + protected async _initialize(): Promise { + perf.mark('willLoadExtensions'); + this._startExtensionHostProcess(true, []); + this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions')); + await this._scanAndHandleExtensions(); + this._releaseBarrier(); + } + + private _releaseBarrier(): void { + perf.mark('extensionHostReady'); + this._installedExtensionsReady.open(); + this._onDidRegisterExtensions.fire(undefined); + this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier)); + } + + private _stopExtensionHostProcess(): void { + let previouslyActivatedExtensionIds: ExtensionIdentifier[] = []; + this._extensionHostActiveExtensions.forEach((value) => { + previouslyActivatedExtensionIds.push(value); + }); + + for (const manager of this._extensionHostProcessManagers) { + manager.dispose(); + } + this._extensionHostProcessManagers = []; + this._extensionHostActiveExtensions = new Map(); + this._extensionHostProcessActivationTimes = new Map(); + this._extensionHostExtensionRuntimeErrors = new Map(); + + if (previouslyActivatedExtensionIds.length > 0) { + this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds); + } + } + + private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void { + this._stopExtensionHostProcess(); + + const processManagers = this._createExtensionHosts(isInitialStart, initialActivationEvents); + processManagers.forEach((processManager) => { + processManager.onDidExit(([code, signal]) => this._onExtensionHostCrashOrExit(processManager, code, signal)); + processManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); }); + this._extensionHostProcessManagers.push(processManager); + }); + } + + private _onExtensionHostCrashOrExit(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void { + + // Unexpected termination + if (!this._isExtensionDevHost) { + this._onExtensionHostCrashed(extensionHost, code, signal); + return; + } + + this._onExtensionHostExit(code); + } + + protected _onExtensionHostCrashed(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void { + console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal); + this._stopExtensionHostProcess(); + } + + //#region IExtensionService + + public canAddExtension(extension: IExtensionDescription): boolean { + return false; + } + + public canRemoveExtension(extension: IExtensionDescription): boolean { + return false; + } + + public restartExtensionHost(): void { + this._stopExtensionHostProcess(); + this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)); + } + + public startExtensionHost(): void { + this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)); + } + + public stopExtensionHost(): void { + this._stopExtensionHostProcess(); + } + + public activateByEvent(activationEvent: string): Promise { + if (this._installedExtensionsReady.isOpen()) { + // Extensions have been scanned and interpreted + + // Record the fact that this activationEvent was requested (in case of a restart) + this._allRequestedActivateEvents[activationEvent] = true; + + if (!this._registry.containsActivationEvent(activationEvent)) { + // There is no extension that is interested in this activation event + return NO_OP_VOID_PROMISE; + } + + return this._activateByEvent(activationEvent); + } else { + // Extensions have not been scanned yet. + + // Record the fact that this activationEvent was requested (in case of a restart) + this._allRequestedActivateEvents[activationEvent] = true; + + return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent)); + } + } + + private _activateByEvent(activationEvent: string): Promise { + const result = Promise.all( + this._extensionHostProcessManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent)) + ).then(() => { }); + this._onWillActivateByEvent.fire({ + event: activationEvent, + activation: result + }); + return result; + } + + public whenInstalledExtensionsRegistered(): Promise { + return this._installedExtensionsReady.wait(); + } + + public getExtensions(): Promise { + return this._installedExtensionsReady.wait().then(() => { + return this._registry.getAllExtensionDescriptions(); + }); + } + + public getExtension(id: string): Promise { + return this._installedExtensionsReady.wait().then(() => { + return this._registry.getExtensionDescription(id); + }); + } + + public readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]> { + return this._installedExtensionsReady.wait().then(() => { + let availableExtensions = this._registry.getAllExtensionDescriptions(); + + let result: ExtensionPointContribution[] = [], resultLen = 0; + for (let i = 0, len = availableExtensions.length; i < len; i++) { + let desc = availableExtensions[i]; + + if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) { + result[resultLen++] = new ExtensionPointContribution(desc, desc.contributes[extPoint.name]); + } + } + + return result; + }); + } + + public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { + let result: { [id: string]: IExtensionsStatus; } = Object.create(null); + if (this._registry) { + const extensions = this._registry.getAllExtensionDescriptions(); + for (const extension of extensions) { + const extensionKey = ExtensionIdentifier.toKey(extension.identifier); + result[extension.identifier.value] = { + messages: this._extensionsMessages.get(extensionKey) || [], + activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey), + runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey) || [], + }; + } + } + return result; + } + + public getInspectPort(): number { + return 0; + } + + //#endregion + + // --- impl + + protected _checkEnableProposedApi(extensions: IExtensionDescription[]): void { + for (let extension of extensions) { + this._proposedApiController.updateEnableProposedApi(extension); + } + } + + private _isExtensionUnderDevelopment(extension: IExtensionDescription): boolean { + if (this._environmentService.isExtensionDevelopment) { + const extDevLocs = this._environmentService.extensionDevelopmentLocationURI; + if (extDevLocs) { + const extLocation = extension.extensionLocation; + for (let p of extDevLocs) { + if (isEqualOrParent(extLocation, p)) { + return true; + } + } + } + } + return false; + } + + protected _isEnabled(extension: IExtensionDescription): boolean { + if (this._isExtensionUnderDevelopment(extension)) { + // Never disable extensions under development + return true; + } + + if (ExtensionIdentifier.equals(extension.identifier, BetterMergeId)) { + // Check if this is the better merge extension which was migrated to a built-in extension + return false; + } + + return this._extensionEnablementService.isEnabled(toExtension(extension)); + } + + protected _doHandleExtensionPoints(affectedExtensions: IExtensionDescription[]): void { + const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null); + for (let extensionDescription of affectedExtensions) { + if (extensionDescription.contributes) { + for (let extPointName in extensionDescription.contributes) { + if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) { + affectedExtensionPoints[extPointName] = true; + } + } + } + } + + const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); + const availableExtensions = this._registry.getAllExtensionDescriptions(); + const extensionPoints = ExtensionsRegistry.getExtensionPoints(); + for (let i = 0, len = extensionPoints.length; i < len; i++) { + if (affectedExtensionPoints[extensionPoints[i].name]) { + AbstractExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); + } + } + } + + private _handleExtensionPointMessage(msg: IMessage) { + const extensionKey = ExtensionIdentifier.toKey(msg.extensionId); + + if (!this._extensionsMessages.has(extensionKey)) { + this._extensionsMessages.set(extensionKey, []); + } + this._extensionsMessages.get(extensionKey)!.push(msg); + + const extension = this._registry.getExtensionDescription(msg.extensionId); + const strMsg = `[${msg.extensionId.value}]: ${msg.message}`; + if (extension && extension.isUnderDevelopment) { + // This message is about the extension currently being developed + this._showMessageToUser(msg.type, strMsg); + } else { + this._logMessageInConsole(msg.type, strMsg); + } + + if (!this._isDev && msg.extensionId) { + const { type, extensionId, extensionPointId, message } = msg; + /* __GDPR__ + "extensionsMessage" : { + "type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "extensionId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "extensionPointId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, + "message": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } + */ + this._telemetryService.publicLog('extensionsMessage', { + type, extensionId: extensionId.value, extensionPointId, message + }); + } + } + + private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void { + let users: IExtensionPointUser[] = [], usersLen = 0; + for (let i = 0, len = availableExtensions.length; i < len; i++) { + let desc = availableExtensions[i]; + + if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) { + users[usersLen++] = { + description: desc, + value: desc.contributes[extensionPoint.name], + collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name) + }; + } + } + + extensionPoint.acceptUsers(users); + } + + private _showMessageToUser(severity: Severity, msg: string): void { + if (severity === Severity.Error || severity === Severity.Warning) { + this._notificationService.notify({ severity, message: msg }); + } else { + this._logMessageInConsole(severity, msg); + } + } + + private _logMessageInConsole(severity: Severity, msg: string): void { + if (severity === Severity.Error) { + console.error(msg); + } else if (severity === Severity.Warning) { + console.warn(msg); + } else { + console.log(msg); + } + } + + //#region Called by extension host + + public _logOrShowMessage(severity: Severity, msg: string): void { + if (this._isDev) { + this._showMessageToUser(severity, msg); + } else { + this._logMessageInConsole(severity, msg); + } + } + + public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + const results = await Promise.all( + this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent)) + ); + const activated = results.some(e => e); + if (!activated) { + throw new Error(`Unknown extension ${extensionId.value}`); + } + } + + public _onWillActivateExtension(extensionId: ExtensionIdentifier): void { + this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId); + } + + public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void { + this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent)); + this._onDidChangeExtensionsStatus.fire([extensionId]); + } + + public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void { + const extensionKey = ExtensionIdentifier.toKey(extensionId); + if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) { + this._extensionHostExtensionRuntimeErrors.set(extensionKey, []); + } + this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err); + this._onDidChangeExtensionsStatus.fire([extensionId]); + } + + //#endregion + + protected abstract _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[]; + protected abstract _scanAndHandleExtensions(): Promise; + public abstract _onExtensionHostExit(code: number): void; +} + +class ProposedApiController { + + private readonly enableProposedApiFor: string | string[]; + private readonly enableProposedApiForAll: boolean; + private readonly productAllowProposedApi: Set; + + constructor( + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IProductService productService: IProductService + ) { + this.enableProposedApiFor = environmentService.args['enable-proposed-api'] || []; + if (this.enableProposedApiFor.length) { + // Make enabled proposed API be lowercase for case insensitive comparison + if (Array.isArray(this.enableProposedApiFor)) { + this.enableProposedApiFor = this.enableProposedApiFor.map(id => id.toLowerCase()); + } else { + this.enableProposedApiFor = this.enableProposedApiFor.toLowerCase(); + } + } + + this.enableProposedApiForAll = !environmentService.isBuilt || + (!!environmentService.extensionDevelopmentLocationURI && productService.nameLong !== 'Visual Studio Code') || + (this.enableProposedApiFor.length === 0 && 'enable-proposed-api' in environmentService.args); + + this.productAllowProposedApi = new Set(); + if (isNonEmptyArray(productService.extensionAllowedProposedApi)) { + productService.extensionAllowedProposedApi.forEach((id) => this.productAllowProposedApi.add(ExtensionIdentifier.toKey(id))); + } + } + + public updateEnableProposedApi(extension: IExtensionDescription): void { + if (this._allowProposedApiFromProduct(extension.identifier)) { + // fast lane -> proposed api is available to all extensions + // that are listed in product.json-files + extension.enableProposedApi = true; + + } else if (extension.enableProposedApi && !extension.isBuiltin) { + if ( + !this.enableProposedApiForAll && + this.enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0 + ) { + extension.enableProposedApi = false; + console.error(`Extension '${extension.identifier.value} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`); + + } else { + // proposed api is available when developing or when an extension was explicitly + // spelled out via a command line argument + console.warn(`Extension '${extension.identifier.value}' uses PROPOSED API which is subject to change and removal without notice.`); + } + } + } + + private _allowProposedApiFromProduct(id: ExtensionIdentifier): boolean { + return this.productAllowProposedApi.has(ExtensionIdentifier.toKey(id)); + } +} diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index 33f1773a8149..262e4e3dcd69 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -35,7 +35,7 @@ const NO_OP_VOID_PROMISE = Promise.resolve(undefined); export class ExtensionHostProcessManager extends Disposable { - public readonly onDidCrash: Event<[number, string | null]>; + public readonly onDidExit: Event<[number, string | null]>; private readonly _onDidChangeResponsiveState: Emitter = this._register(new Emitter()); public readonly onDidChangeResponsiveState: Event = this._onDidChangeResponsiveState.event; @@ -54,6 +54,7 @@ export class ExtensionHostProcessManager extends Disposable { private _resolveAuthorityAttempt: number; constructor( + public readonly isLocal: boolean, extensionHostProcessWorker: IExtensionHostStarter, private readonly _remoteAuthority: string, initialActivationEvents: string[], @@ -66,7 +67,7 @@ export class ExtensionHostProcessManager extends Disposable { this._extensionHostProcessCustomers = []; this._extensionHostProcessWorker = extensionHostProcessWorker; - this.onDidCrash = this._extensionHostProcessWorker.onCrashed; + this.onDidExit = this._extensionHostProcessWorker.onExit; this._extensionHostProcessProxy = this._extensionHostProcessWorker.start()!.then( (protocol) => { return { value: this._createExtensionHostCustomers(protocol) }; diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index 663d522d8380..b06566e88786 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -12,6 +12,7 @@ export interface IExtHostReadyMessage { export interface IExtHostSocketMessage { type: 'VSCODE_EXTHOST_IPC_SOCKET'; initialDataChunk: string; + skipWebSocketFrames: boolean; } export const enum MessageType { diff --git a/src/vs/workbench/services/extensions/common/extensionPoints.ts b/src/vs/workbench/services/extensions/common/extensionPoints.ts new file mode 100644 index 000000000000..f5291468d213 --- /dev/null +++ b/src/vs/workbench/services/extensions/common/extensionPoints.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Severity } from 'vs/platform/notification/common/notification'; + +export interface Translations { + [id: string]: string; +} + +export namespace Translations { + export function equals(a: Translations, b: Translations): boolean { + if (a === b) { + return true; + } + let aKeys = Object.keys(a); + let bKeys: Set = new Set(); + for (let key of Object.keys(b)) { + bKeys.add(key); + } + if (aKeys.length !== bKeys.size) { + return false; + } + + for (let key of aKeys) { + if (a[key] !== b[key]) { + return false; + } + bKeys.delete(key); + } + return bKeys.size === 0; + } +} + +export interface ILog { + error(source: string, message: string): void; + warn(source: string, message: string): void; + info(source: string, message: string): void; +} + +export class Logger implements ILog { + + private readonly _messageHandler: (severity: Severity, source: string, message: string) => void; + + constructor( + messageHandler: (severity: Severity, source: string, message: string) => void + ) { + this._messageHandler = messageHandler; + } + + public error(source: string, message: string): void { + this._messageHandler(Severity.Error, source, message); + } + + public warn(source: string, message: string): void { + this._messageHandler(Severity.Warning, source, message); + } + + public info(source: string, message: string): void { + this._messageHandler(Severity.Info, source, message); + } +} diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index a234d6c84647..3f66df503d53 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -84,7 +84,8 @@ export interface IExtensionHostProfile { } export interface IExtensionHostStarter { - readonly onCrashed: Event<[number, string | null]>; + readonly onExit: Event<[number, string | null]>; + start(): Promise | null; getInspectPort(): number | undefined; dispose(): void; diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index f38eb6b5080e..4bd648152133 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -341,6 +341,19 @@ export const schema = { pattern: EXTENSION_IDENTIFIER_PATTERN } }, + extensionKind: { + description: nls.localize('extensionKind', "Define the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote."), + type: 'string', + enum: [ + 'ui', + 'workspace' + ], + enumDescriptions: [ + nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), + nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") + ], + default: 'workspace' + }, scripts: { type: 'object', properties: { diff --git a/src/vs/workbench/services/extensions/node/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts similarity index 86% rename from src/vs/workbench/services/extensions/node/extensionsUtil.ts rename to src/vs/workbench/services/extensions/common/extensionsUtil.ts index 8fe148d90219..9b4699fc75f6 100644 --- a/src/vs/workbench/services/extensions/node/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -8,9 +8,9 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import product from 'vs/platform/product/node/product'; +import { IProductService } from 'vs/platform/product/common/product'; -export function isUIExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { +export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { const uiContributions = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name); const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); const extensionKind = getExtensionKind(manifest, configurationService); @@ -19,7 +19,7 @@ export function isUIExtension(manifest: IExtensionManifest, configurationService case 'workspace': return false; default: { // Tagged as UI extension in product - if (isNonEmptyArray(product.uiExtensions) && product.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { + if (isNonEmptyArray(productService.uiExtensions) && productService.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { return true; } // Not an UI extension if it has main diff --git a/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts index 1dc53d5a8e59..e82f6fb6bf32 100644 --- a/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/common/inactiveExtensionUrlHandler.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { EnablementState, IExtensionEnablementService, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -70,10 +70,10 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), true); } - this.disposable = combinedDisposable([ + this.disposable = combinedDisposable( urlService.registerHandler(this), toDisposable(() => clearInterval(interval)) - ]); + ); } async handleURL(uri: URI, confirmed?: boolean): Promise { diff --git a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts similarity index 86% rename from src/vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient.ts rename to src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 4ba888e0572d..26e1a16b614a 100644 --- a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -3,17 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ipcRenderer as ipc } from 'electron'; import { Emitter, Event } from 'vs/base/common/event'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService } from 'vs/platform/log/common/log'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; -import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; +import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions, IWebSocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; @@ -28,8 +24,8 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { VSBuffer } from 'vs/base/common/buffer'; -import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory'; import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug'; +import { IProductService } from 'vs/platform/product/common/product'; export interface IInitDataProvider { readonly remoteAuthority: string; @@ -38,28 +34,28 @@ export interface IInitDataProvider { export class RemoteExtensionHostClient extends Disposable implements IExtensionHostStarter { - private _onCrashed: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>()); - public readonly onCrashed: Event<[number, string | null]> = this._onCrashed.event; + private _onExit: Emitter<[number, string | null]> = this._register(new Emitter<[number, string | null]>()); + public readonly onExit: Event<[number, string | null]> = this._onExit.event; private _protocol: PersistentProtocol | null; private readonly _isExtensionDevHost: boolean; - private readonly _isExtensionDevTestFromCli: boolean; private _terminating: boolean; constructor( private readonly _allExtensions: Promise, private readonly _initDataProvider: IInitDataProvider, + private readonly _webSocketFactory: IWebSocketFactory, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IWindowService private readonly _windowService: IWindowService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @ILogService private readonly _logService: ILogService, @ILabelService private readonly _labelService: ILabelService, @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService + @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService, + @IProductService private readonly _productService: IProductService ) { super(); this._protocol = null; @@ -69,14 +65,13 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH const devOpts = parseExtensionDevOptions(this._environmentService); this._isExtensionDevHost = devOpts.isExtensionDevHost; - this._isExtensionDevTestFromCli = devOpts.isExtensionDevTestFromCli; } public start(): Promise { const options: IConnectionOptions = { isBuilt: this._environmentService.isBuilt, - commit: product.commit, - webSocketFactory: nodeWebSocketFactory, + commit: this._productService.commit, + webSocketFactory: this._webSocketFactory, addressProvider: { getAddress: async () => { const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(this._initDataProvider.remoteAuthority); @@ -168,20 +163,7 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH return; } - // Unexpected termination - if (!this._isExtensionDevHost) { - this._onCrashed.fire([0, null]); - } - - // Expected development extension termination: When the extension host goes down we also shutdown the window - else if (!this._isExtensionDevTestFromCli) { - this._windowService.closeWindow(); - } - - // When CLI testing make sure to exit with proper exit code - else { - ipc.send('vscode:exit', 0); - } + this._onExit.fire([0, null]); } private _createExtHostInitData(isExtensionDevelopmentDebug: boolean): Promise { @@ -191,15 +173,15 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier); const workspace = this._contextService.getWorkspace(); const r: IInitData = { - commit: product.commit, - version: pkg.version, + commit: this._productService.commit, + version: this._productService.version, parentPid: remoteExtensionHostData.pid, environment: { isExtensionDevelopmentDebug, appRoot: remoteExtensionHostData.appRoot, appSettingsHome: remoteExtensionHostData.appSettingsHome, - appName: product.nameLong, - appUriScheme: product.urlProtocol, + appName: this._productService.nameLong, + appUriScheme: this._productService.urlProtocol, appLanguage: platform.language, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 399a45d82112..b82afa42adb8 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -21,7 +21,8 @@ import pkg from 'vs/platform/product/node/package'; import product from 'vs/platform/product/node/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, ILog, IRelaxedExtensionDescription, Translations } from 'vs/workbench/services/extensions/node/extensionPoints'; +import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, IRelaxedExtensionDescription } from 'vs/workbench/services/extensions/node/extensionPoints'; +import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints'; interface IExtensionCacheData { input: ExtensionScannerInput; @@ -387,26 +388,3 @@ class NullLogger implements ILog { public info(source: string, message: string): void { } } - -export class Logger implements ILog { - - private readonly _messageHandler: (severity: Severity, source: string, message: string) => void; - - constructor( - messageHandler: (severity: Severity, source: string, message: string) => void - ) { - this._messageHandler = messageHandler; - } - - public error(source: string, message: string): void { - this._messageHandler(Severity.Error, source, message); - } - - public warn(source: string, message: string): void { - this._messageHandler(Severity.Warning, source, message); - } - - public info(source: string, message: string): void { - this._messageHandler(Severity.Info, source, message); - } -} diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 60355932d618..b735a0247f81 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -5,13 +5,12 @@ import * as nls from 'vs/nls'; import { ChildProcess, fork } from 'child_process'; -import { ipcRenderer as ipc } from 'electron'; import { Server, Socket, createServer } from 'net'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import pkg from 'vs/platform/product/node/package'; @@ -42,10 +41,10 @@ import { isEqualOrParent } from 'vs/base/common/resources'; export class ExtensionHostProcessWorker implements IExtensionHostStarter { - private readonly _onCrashed: Emitter<[number, string]> = new Emitter<[number, string]>(); - public readonly onCrashed: Event<[number, string]> = this._onCrashed.event; + private readonly _onExit: Emitter<[number, string]> = new Emitter<[number, string]>(); + public readonly onExit: Event<[number, string]> = this._onExit.event; - private readonly _toDispose: IDisposable[]; + private readonly _toDispose = new DisposableStore(); private readonly _isExtensionDevHost: boolean; private readonly _isExtensionDevDebug: boolean; @@ -92,16 +91,15 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._extensionHostConnection = null; this._messageProtocol = null; - this._toDispose = []; - this._toDispose.push(this._onCrashed); - this._toDispose.push(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e))); - this._toDispose.push(this._lifecycleService.onShutdown(reason => this.terminate())); - this._toDispose.push(this._extensionHostDebugService.onClose(event => { + this._toDispose.add(this._onExit); + this._toDispose.add(this._lifecycleService.onWillShutdown(e => this._onWillShutdown(e))); + this._toDispose.add(this._lifecycleService.onShutdown(reason => this.terminate())); + this._toDispose.add(this._extensionHostDebugService.onClose(event => { if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) { this._windowService.closeWindow(); } })); - this._toDispose.push(this._extensionHostDebugService.onReload(event => { + this._toDispose.add(this._extensionHostDebugService.onReload(event => { if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) { this._windowService.reloadWindow(); } @@ -109,7 +107,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { const globalExitListener = () => this.terminate(); process.once('exit', globalExitListener); - this._toDispose.push(toDisposable(() => { + this._toDispose.add(toDisposable(() => { process.removeListener('exit', globalExitListener); })); } @@ -451,20 +449,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { return; } - // Unexpected termination - if (!this._isExtensionDevHost) { - this._onCrashed.fire([code, signal]); - } - - // Expected development extension termination: When the extension host goes down we also shutdown the window - else if (!this._isExtensionDevTestFromCli) { - this._windowService.closeWindow(); - } - - // When CLI testing make sure to exit with proper exit code - else { - ipc.send('vscode:exit', code); - } + this._onExit.fire([code, signal]); } public enableInspector(): Promise { @@ -489,7 +474,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } this._terminating = true; - dispose(this._toDispose); + this._toDispose.dispose(); if (!this._messageProtocol) { // .start() was not called diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index fe3aa6314c71..aee8233572b2 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -3,62 +3,37 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ipcRenderer as ipc } from 'electron'; +import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; +import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory'; +import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService'; import * as nls from 'vs/nls'; import * as path from 'vs/base/common/path'; -import { ipcRenderer as ipc } from 'electron'; -import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { Barrier, runWhenIdle } from 'vs/base/common/async'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import * as perf from 'vs/base/common/performance'; -import { isEqualOrParent } from 'vs/base/common/resources'; +import { runWhenIdle } from 'vs/base/common/async'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IExtensionEnablementService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionHostClient'; +import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService, ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import pkg from 'vs/platform/product/node/package'; -import product from 'vs/platform/product/node/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser, schema } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; -import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; -import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; -import { CachedExtensionScanner, Logger } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager'; import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Schemas } from 'vs/base/common/network'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; -import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { PersistenConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; - -const hasOwnProperty = Object.hasOwnProperty; -const NO_OP_VOID_PROMISE = Promise.resolve(undefined); - -schema.properties.engines.properties.vscode.default = `^${pkg.version}`; - -let productAllowProposedApi: Set | null = null; -function allowProposedApiFromProduct(id: ExtensionIdentifier): boolean { - // create set if needed - if (!productAllowProposedApi) { - productAllowProposedApi = new Set(); - if (isNonEmptyArray(product.extensionAllowedProposedApi)) { - product.extensionAllowedProposedApi.forEach((id) => productAllowProposedApi!.add(ExtensionIdentifier.toKey(id))); - } - } - return productAllowProposedApi.has(ExtensionIdentifier.toKey(id)); -} +import { IProductService } from 'vs/platform/product/common/product'; +import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints'; class DeltaExtensionsQueueItem { constructor( @@ -67,80 +42,38 @@ class DeltaExtensionsQueueItem { ) { } } -export class ExtensionService extends Disposable implements IExtensionService { - - public _serviceBrand: any; +export class ExtensionService extends AbstractExtensionService implements IExtensionService { - private _remoteExtensionsEnvironmentData: Map; + private readonly _remoteExtensionsEnvironmentData: Map; private readonly _extensionHostLogsLocation: URI; - private readonly _registry: ExtensionDescriptionRegistry; - private readonly _installedExtensionsReady: Barrier; - private readonly _isDev: boolean; - private readonly _extensionsMessages: Map; - private _allRequestedActivateEvents: { [activationEvent: string]: boolean; }; private readonly _extensionScanner: CachedExtensionScanner; private _deltaExtensionsQueue: DeltaExtensionsQueueItem[]; - private readonly _onDidRegisterExtensions: Emitter = this._register(new Emitter()); - public readonly onDidRegisterExtensions = this._onDidRegisterExtensions.event; - - private readonly _onDidChangeExtensionsStatus: Emitter = this._register(new Emitter()); - public readonly onDidChangeExtensionsStatus: Event = this._onDidChangeExtensionsStatus.event; - - private readonly _onDidChangeExtensions: Emitter = this._register(new Emitter()); - public readonly onDidChangeExtensions: Event = this._onDidChangeExtensions.event; - - private readonly _onWillActivateByEvent = this._register(new Emitter()); - public readonly onWillActivateByEvent: Event = this._onWillActivateByEvent.event; - - private readonly _onDidChangeResponsiveChange = this._register(new Emitter()); - public readonly onDidChangeResponsiveChange: Event = this._onDidChangeResponsiveChange.event; - - // --- Members used per extension host process - private _extensionHostProcessManagers: ExtensionHostProcessManager[]; - private _extensionHostActiveExtensions: Map; - private _extensionHostProcessActivationTimes: Map; - private _extensionHostExtensionRuntimeErrors: Map; - constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @INotificationService private readonly _notificationService: INotificationService, - @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, - @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService, + @IInstantiationService instantiationService: IInstantiationService, + @INotificationService notificationService: INotificationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ITelemetryService telemetryService: ITelemetryService, + @IExtensionEnablementService extensionEnablementService: IExtensionEnablementService, + @IFileService fileService: IFileService, + @IProductService productService: IProductService, @IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService, - @IWindowService private readonly _windowService: IWindowService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, - @IFileService fileService: IFileService + @IWindowService protected readonly _windowService: IWindowService, ) { - super(); - - // help the file service to activate providers by activating extensions by file system event - this._register(fileService.onWillActivateFileSystemProvider(e => { - e.join(this.activateByEvent(`onFileSystem:${e.scheme}`)); - })); - - this._remoteExtensionsEnvironmentData = new Map(); - - this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${_windowService.windowId}`)); - this._registry = new ExtensionDescriptionRegistry([]); - this._installedExtensionsReady = new Barrier(); - this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; - this._extensionsMessages = new Map(); - this._allRequestedActivateEvents = Object.create(null); - this._extensionScanner = this._instantiationService.createInstance(CachedExtensionScanner); - this._deltaExtensionsQueue = []; - - this._extensionHostProcessManagers = []; - this._extensionHostActiveExtensions = new Map(); - this._extensionHostProcessActivationTimes = new Map(); - this._extensionHostExtensionRuntimeErrors = new Map(); - - this._startDelayed(this._lifecycleService); + super( + instantiationService, + notificationService, + environmentService, + telemetryService, + extensionEnablementService, + fileService, + productService, + ); if (this._extensionEnablementService.allUserExtensionsDisabled) { this._notificationService.prompt(Severity.Info, nls.localize('extensionsDisabled', "All installed extensions are temporarily disabled. Reload the window to return to the previous state."), [{ @@ -151,6 +84,12 @@ export class ExtensionService extends Disposable implements IExtensionService { }]); } + this._remoteExtensionsEnvironmentData = new Map(); + + this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.windowId}`)); + this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner); + this._deltaExtensionsQueue = []; + this._register(this._extensionEnablementService.onEnablementChanged((extensions) => { let toAdd: IExtension[] = []; let toRemove: string[] = []; @@ -181,8 +120,24 @@ export class ExtensionService extends Disposable implements IExtensionService { this._handleDeltaExtensions(new DeltaExtensionsQueueItem([], [event.identifier.id])); } })); + + // delay extension host creation and extension scanning + // until the workbench is running. we cannot defer the + // extension host more (LifecyclePhase.Restored) because + // some editors require the extension host to restore + // and this would result in a deadlock + // see https://github.com/Microsoft/vscode/issues/41322 + this._lifecycleService.when(LifecyclePhase.Ready).then(() => { + // reschedule to ensure this runs after restoring viewlets, panels, and editors + runWhenIdle(() => { + this._initialize(); + }, 50 /*max delay*/); + }); } + + //#region deltaExtensions + private _inHandleDeltaExtensions = false; private async _handleDeltaExtensions(item: DeltaExtensionsQueueItem): Promise { this._deltaExtensionsQueue.push(item); @@ -274,26 +229,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } private _rehandleExtensionPoints(extensionDescriptions: IExtensionDescription[]): void { - const affectedExtensionPoints: { [extPointName: string]: boolean; } = Object.create(null); - for (let extensionDescription of extensionDescriptions) { - if (extensionDescription.contributes) { - for (let extPointName in extensionDescription.contributes) { - if (hasOwnProperty.call(extensionDescription.contributes, extPointName)) { - affectedExtensionPoints[extPointName] = true; - } - } - } - } - - const messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); - - const availableExtensions = this._registry.getAllExtensionDescriptions(); - const extensionPoints = ExtensionsRegistry.getExtensionPoints(); - for (let i = 0, len = extensionPoints.length; i < len; i++) { - if (affectedExtensionPoints[extensionPoints[i].name]) { - ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); - } - } + this._doHandleExtensionPoints(extensionDescriptions); } public canAddExtension(extension: IExtensionDescription): boolean { @@ -383,76 +319,20 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - private _startDelayed(lifecycleService: ILifecycleService): void { - // delay extension host creation and extension scanning - // until the workbench is running. we cannot defer the - // extension host more (LifecyclePhase.Restored) because - // some editors require the extension host to restore - // and this would result in a deadlock - // see https://github.com/Microsoft/vscode/issues/41322 - lifecycleService.when(LifecyclePhase.Ready).then(() => { - // reschedule to ensure this runs after restoring viewlets, panels, and editors - runWhenIdle(() => { - perf.mark('willLoadExtensions'); - this._startExtensionHostProcess(true, []); - this._scanAndHandleExtensions(); - this.whenInstalledExtensionsRegistered().then(() => perf.mark('didLoadExtensions')); - }, 50 /*max delay*/); - }); - } - - public dispose(): void { - super.dispose(); - this._onWillActivateByEvent.dispose(); - this._onDidChangeResponsiveChange.dispose(); - } - - public restartExtensionHost(): void { - this._stopExtensionHostProcess(); - this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)); - } - - public startExtensionHost(): void { - this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)); - } - - public stopExtensionHost(): void { - this._stopExtensionHostProcess(); - } - - private _stopExtensionHostProcess(): void { - let previouslyActivatedExtensionIds: ExtensionIdentifier[] = []; - this._extensionHostActiveExtensions.forEach((value) => { - previouslyActivatedExtensionIds.push(value); - }); - - for (const manager of this._extensionHostProcessManagers) { - manager.dispose(); - } - this._extensionHostProcessManagers = []; - this._extensionHostActiveExtensions = new Map(); - this._extensionHostProcessActivationTimes = new Map(); - this._extensionHostExtensionRuntimeErrors = new Map(); - - if (previouslyActivatedExtensionIds.length > 0) { - this._onDidChangeExtensionsStatus.fire(previouslyActivatedExtensionIds); - } - } + //#endregion private _createProvider(remoteAuthority: string): IInitDataProvider { return { remoteAuthority: remoteAuthority, getInitData: () => { - return this._installedExtensionsReady.wait().then(() => { + return this.whenInstalledExtensionsRegistered().then(() => { return this._remoteExtensionsEnvironmentData.get(remoteAuthority)!; }); } }; } - private _startExtensionHostProcess(isInitialStart: boolean, initialActivationEvents: string[]): void { - this._stopExtensionHostProcess(); - + protected _createExtensionHosts(isInitialStart: boolean, initialActivationEvents: string[]): ExtensionHostProcessManager[] { let autoStart: boolean; let extensions: Promise; if (isInitialStart) { @@ -464,27 +344,25 @@ export class ExtensionService extends Disposable implements IExtensionService { extensions = this.getExtensions().then((extensions) => extensions.filter(ext => ext.extensionLocation.scheme === Schemas.file)); } + const result: ExtensionHostProcessManager[] = []; const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation); - const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, extHostProcessWorker, null, initialActivationEvents); - extHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, true)); - extHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); }); - this._extensionHostProcessManagers.push(extHostProcessManager); + const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, true, extHostProcessWorker, null, initialActivationEvents); + result.push(extHostProcessManager); const remoteAgentConnection = this._remoteAgentService.getConnection(); if (remoteAgentConnection) { - const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority)); - const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); - remoteExtHostProcessManager.onDidCrash(([code, signal]) => this._onExtensionHostCrashed(code, signal, false)); - remoteExtHostProcessManager.onDidChangeResponsiveState((responsiveState) => { this._onDidChangeResponsiveChange.fire({ isResponsive: responsiveState === ResponsiveState.Responsive }); }); - this._extensionHostProcessManagers.push(remoteExtHostProcessManager); + const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), nodeWebSocketFactory); + const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); + result.push(remoteExtHostProcessManager); } + + return result; } - private _onExtensionHostCrashed(code: number, signal: string | null, showNotification: boolean): void { - console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal); - this._stopExtensionHostProcess(); + protected _onExtensionHostCrashed(extensionHost: ExtensionHostProcessManager, code: number, signal: string | null): void { + super._onExtensionHostCrashed(extensionHost, code, signal); - if (showNotification) { + if (extensionHost.isLocal) { if (code === 55) { this._notificationService.prompt( Severity.Error, @@ -502,118 +380,19 @@ export class ExtensionService extends Disposable implements IExtensionService { return; } - let message = nls.localize('extensionService.crash', "Extension host terminated unexpectedly."); - if (code === 87) { - message = nls.localize('extensionService.unresponsiveCrash', "Extension host terminated because it was not responsive."); - } - - this._notificationService.prompt(Severity.Error, message, + this._notificationService.prompt(Severity.Error, nls.localize('extensionService.crash', "Extension host terminated unexpectedly."), [{ label: nls.localize('devTools', "Open Developer Tools"), run: () => this._windowService.openDevTools() }, { label: nls.localize('restart', "Restart Extension Host"), - run: () => this._startExtensionHostProcess(false, Object.keys(this._allRequestedActivateEvents)) + run: () => this.startExtensionHost() }] ); } } - // ---- begin IExtensionService - - public activateByEvent(activationEvent: string): Promise { - if (this._installedExtensionsReady.isOpen()) { - // Extensions have been scanned and interpreted - - // Record the fact that this activationEvent was requested (in case of a restart) - this._allRequestedActivateEvents[activationEvent] = true; - - if (!this._registry.containsActivationEvent(activationEvent)) { - // There is no extension that is interested in this activation event - return NO_OP_VOID_PROMISE; - } - - return this._activateByEvent(activationEvent); - } else { - // Extensions have not been scanned yet. - - // Record the fact that this activationEvent was requested (in case of a restart) - this._allRequestedActivateEvents[activationEvent] = true; - - return this._installedExtensionsReady.wait().then(() => this._activateByEvent(activationEvent)); - } - } - - private _activateByEvent(activationEvent: string): Promise { - const result = Promise.all( - this._extensionHostProcessManagers.map(extHostManager => extHostManager.activateByEvent(activationEvent)) - ).then(() => { }); - this._onWillActivateByEvent.fire({ - event: activationEvent, - activation: result - }); - return result; - } - - public whenInstalledExtensionsRegistered(): Promise { - return this._installedExtensionsReady.wait(); - } - - public getExtensions(): Promise { - return this._installedExtensionsReady.wait().then(() => { - return this._registry.getAllExtensionDescriptions(); - }); - } - - public getExtension(id: string): Promise { - return this._installedExtensionsReady.wait().then(() => { - return this._registry.getExtensionDescription(id); - }); - } - - public readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]> { - return this._installedExtensionsReady.wait().then(() => { - let availableExtensions = this._registry.getAllExtensionDescriptions(); - - let result: ExtensionPointContribution[] = [], resultLen = 0; - for (let i = 0, len = availableExtensions.length; i < len; i++) { - let desc = availableExtensions[i]; - - if (desc.contributes && hasOwnProperty.call(desc.contributes, extPoint.name)) { - result[resultLen++] = new ExtensionPointContribution(desc, desc.contributes[extPoint.name]); - } - } - - return result; - }); - } - - public getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { - let result: { [id: string]: IExtensionsStatus; } = Object.create(null); - if (this._registry) { - const extensions = this._registry.getAllExtensionDescriptions(); - for (const extension of extensions) { - const extensionKey = ExtensionIdentifier.toKey(extension.identifier); - result[extension.identifier.value] = { - messages: this._extensionsMessages.get(extensionKey) || [], - activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey), - runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey) || [], - }; - } - } - return result; - } - - public getInspectPort(): number { - if (this._extensionHostProcessManagers.length > 0) { - return this._extensionHostProcessManagers[0].getInspectPort(); - } - return 0; - } - - // ---- end IExtensionService - // --- impl private createLogger(): Logger { @@ -642,7 +421,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - private async _scanAndHandleExtensions(): Promise { + protected async _scanAndHandleExtensions(): Promise { this._extensionScanner.startScanningExtensions(this.createLogger()); const remoteAuthority = this._environmentService.configuration.remoteAuthority; @@ -650,6 +429,12 @@ export class ExtensionService extends Disposable implements IExtensionService { let localExtensions = await this._extensionScanner.scannedExtensions; + // enable or disable proposed API per extension + this._checkEnableProposedApi(localExtensions); + + // remove disabled extensions + localExtensions = localExtensions.filter(extension => this._isEnabled(extension)); + if (remoteAuthority) { let resolvedAuthority: ResolvedAuthority; @@ -693,37 +478,28 @@ export class ExtensionService extends Disposable implements IExtensionService { // fetch the remote environment const remoteEnv = (await this._remoteAgentService.getEnvironment())!; - // revive URIs - remoteEnv.extensions.forEach((extension) => { - (extension).extensionLocation = URI.revive(extension.extensionLocation); - }); + // enable or disable proposed API per extension + this._checkEnableProposedApi(remoteEnv.extensions); + + // remove disabled extensions + remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension)); // remove UI extensions from the remote extensions - remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._configurationService)); + remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._productService, this._configurationService)); // remove non-UI extensions from the local extensions - localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._configurationService)); + localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._productService, this._configurationService)); // in case of overlap, the remote wins const isRemoteExtension = new Set(); remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier))); localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier))); - // compute enabled extensions - const enabledExtensions = await this._getRuntimeExtensions(([]).concat(remoteEnv.extensions).concat(localExtensions)); - - // remove disabled extensions - const isEnabled = new Set(); - enabledExtensions.forEach(extension => isEnabled.add(ExtensionIdentifier.toKey(extension.identifier))); - remoteEnv.extensions = remoteEnv.extensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier))); - localExtensions = localExtensions.filter(extension => isEnabled.has(ExtensionIdentifier.toKey(extension.identifier))); - // save for remote extension's init data this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv); - this._handleExtensionPoints(enabledExtensions); + this._handleExtensionPoints(([]).concat(remoteEnv.extensions).concat(localExtensions)); extensionHost.start(localExtensions.map(extension => extension.identifier)); - this._releaseBarrier(); } else { await this._startLocalExtensionHost(extensionHost, localExtensions); @@ -731,11 +507,8 @@ export class ExtensionService extends Disposable implements IExtensionService { } private async _startLocalExtensionHost(extensionHost: ExtensionHostProcessManager, localExtensions: IExtensionDescription[]): Promise { - const enabledExtensions = await this._getRuntimeExtensions(localExtensions); - - this._handleExtensionPoints(enabledExtensions); - extensionHost.start(enabledExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id))); - this._releaseBarrier(); + this._handleExtensionPoints(localExtensions); + extensionHost.start(localExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id))); } private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void { @@ -744,234 +517,19 @@ export class ExtensionService extends Disposable implements IExtensionService { this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', '))); } - let availableExtensions = this._registry.getAllExtensionDescriptions(); - let extensionPoints = ExtensionsRegistry.getExtensionPoints(); - - let messageHandler = (msg: IMessage) => this._handleExtensionPointMessage(msg); - - for (let i = 0, len = extensionPoints.length; i < len; i++) { - ExtensionService._handleExtensionPoint(extensionPoints[i], availableExtensions, messageHandler); - } - } - - private _releaseBarrier(): void { - perf.mark('extensionHostReady'); - this._installedExtensionsReady.open(); - this._onDidRegisterExtensions.fire(undefined); - this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier)); - } - - private isExtensionUnderDevelopment(extension: IExtensionDescription): boolean { - if (this._environmentService.isExtensionDevelopment) { - const extDevLocs = this._environmentService.extensionDevelopmentLocationURI; - if (extDevLocs) { - const extLocation = extension.extensionLocation; - for (let p of extDevLocs) { - if (isEqualOrParent(extLocation, p)) { - return true; - } - } - } - } - return false; - } - - private async _getRuntimeExtensions(allExtensions: IExtensionDescription[]): Promise { - - const runtimeExtensions: IExtensionDescription[] = []; - const extensionsToDisable: IExtensionDescription[] = []; - const userMigratedSystemExtensions: IExtensionIdentifier[] = [{ id: BetterMergeId }]; - - let enableProposedApiFor: string | string[] = this._environmentService.args['enable-proposed-api'] || []; - - const notFound = (id: string) => nls.localize('notFound', "Extension \`{0}\` cannot use PROPOSED API as it cannot be found", id); - - if (enableProposedApiFor.length) { - let allProposed = (enableProposedApiFor instanceof Array ? enableProposedApiFor : [enableProposedApiFor]); - allProposed.forEach(id => { - if (!allExtensions.some(description => ExtensionIdentifier.equals(description.identifier, id))) { - console.error(notFound(id)); - } - }); - // Make enabled proposed API be lowercase for case insensitive comparison - if (Array.isArray(enableProposedApiFor)) { - enableProposedApiFor = enableProposedApiFor.map(id => id.toLowerCase()); - } else { - enableProposedApiFor = enableProposedApiFor.toLowerCase(); - } - } - - const enableProposedApiForAll = !this._environmentService.isBuilt || - (!!this._environmentService.extensionDevelopmentLocationURI && product.nameLong !== 'Visual Studio Code') || - (enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args); - - - for (const extension of allExtensions) { - - // Do not disable extensions under development - if (!this.isExtensionUnderDevelopment(extension)) { - if (!this._extensionEnablementService.isEnabled(toExtension(extension))) { - continue; - } - } - - if (!extension.isBuiltin) { - // Check if the extension is changed to system extension - const userMigratedSystemExtension = userMigratedSystemExtensions.filter(userMigratedSystemExtension => areSameExtensions(userMigratedSystemExtension, { id: extension.identifier.value }))[0]; - if (userMigratedSystemExtension) { - extensionsToDisable.push(extension); - continue; - } - } - runtimeExtensions.push(this._updateEnableProposedApi(extension, enableProposedApiForAll, enableProposedApiFor)); - } - - this._telemetryService.publicLog('extensionsScanned', { - totalCount: runtimeExtensions.length, - disabledCount: allExtensions.length - runtimeExtensions.length - }); - - if (extensionsToDisable.length) { - return this._extensionEnablementService.setEnablement(extensionsToDisable.map(e => toExtension(e)), EnablementState.Disabled) - .then(() => runtimeExtensions); - } else { - return runtimeExtensions; - } - } - - private _updateEnableProposedApi(extension: IExtensionDescription, enableProposedApiForAll: boolean, enableProposedApiFor: string | string[]): IExtensionDescription { - if (allowProposedApiFromProduct(extension.identifier)) { - // fast lane -> proposed api is available to all extensions - // that are listed in product.json-files - extension.enableProposedApi = true; - - } else if (extension.enableProposedApi && !extension.isBuiltin) { - if ( - !enableProposedApiForAll && - enableProposedApiFor.indexOf(extension.identifier.value.toLowerCase()) < 0 - ) { - extension.enableProposedApi = false; - console.error(`Extension '${extension.identifier.value} cannot use PROPOSED API (must started out of dev or enabled via --enable-proposed-api)`); - - } else { - // proposed api is available when developing or when an extension was explicitly - // spelled out via a command line argument - console.warn(`Extension '${extension.identifier.value}' uses PROPOSED API which is subject to change and removal without notice.`); - } - } - return extension; - } - - private _handleExtensionPointMessage(msg: IMessage) { - const extensionKey = ExtensionIdentifier.toKey(msg.extensionId); - - if (!this._extensionsMessages.has(extensionKey)) { - this._extensionsMessages.set(extensionKey, []); - } - this._extensionsMessages.get(extensionKey)!.push(msg); - - const extension = this._registry.getExtensionDescription(msg.extensionId); - const strMsg = `[${msg.extensionId.value}]: ${msg.message}`; - if (extension && extension.isUnderDevelopment) { - // This message is about the extension currently being developed - this._showMessageToUser(msg.type, strMsg); - } else { - this._logMessageInConsole(msg.type, strMsg); - } - - if (!this._isDev && msg.extensionId) { - const { type, extensionId, extensionPointId, message } = msg; - /* __GDPR__ - "extensionsMessage" : { - "type" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "extensionId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "extensionPointId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }, - "message": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } - } - */ - this._telemetryService.publicLog('extensionsMessage', { - type, extensionId: extensionId.value, extensionPointId, message - }); - } + this._doHandleExtensionPoints(this._registry.getAllExtensionDescriptions()); } - private static _handleExtensionPoint(extensionPoint: ExtensionPoint, availableExtensions: IExtensionDescription[], messageHandler: (msg: IMessage) => void): void { - let users: IExtensionPointUser[] = [], usersLen = 0; - for (let i = 0, len = availableExtensions.length; i < len; i++) { - let desc = availableExtensions[i]; - - if (desc.contributes && hasOwnProperty.call(desc.contributes, extensionPoint.name)) { - users[usersLen++] = { - description: desc, - value: desc.contributes[extensionPoint.name], - collector: new ExtensionMessageCollector(messageHandler, desc, extensionPoint.name) - }; - } - } - - extensionPoint.acceptUsers(users); - } - - private _showMessageToUser(severity: Severity, msg: string): void { - if (severity === Severity.Error || severity === Severity.Warning) { - this._notificationService.notify({ severity, message: msg }); - } else { - this._logMessageInConsole(severity, msg); - } - } - - private _logMessageInConsole(severity: Severity, msg: string): void { - if (severity === Severity.Error) { - console.error(msg); - } else if (severity === Severity.Warning) { - console.warn(msg); - } else { - console.log(msg); - } - } - - // -- called by extension host - - public _logOrShowMessage(severity: Severity, msg: string): void { - if (this._isDev) { - this._showMessageToUser(severity, msg); - } else { - this._logMessageInConsole(severity, msg); - } - } - - public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise { - const results = await Promise.all( - this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent)) - ); - const activated = results.some(e => e); - if (!activated) { - throw new Error(`Unknown extension ${extensionId.value}`); - } - } - - public _onWillActivateExtension(extensionId: ExtensionIdentifier): void { - this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId); - } - - public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void { - this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent)); - this._onDidChangeExtensionsStatus.fire([extensionId]); - } - - public _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void { - const extensionKey = ExtensionIdentifier.toKey(extensionId); - if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) { - this._extensionHostExtensionRuntimeErrors.set(extensionKey, []); + public getInspectPort(): number { + if (this._extensionHostProcessManagers.length > 0) { + return this._extensionHostProcessManagers[0].getInspectPort(); } - this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err); - this._onDidChangeExtensionsStatus.fire([extensionId]); + return 0; } public _onExtensionHostExit(code: number): void { // Expected development extension termination: When the extension host goes down we also shutdown the window - const devOpts = parseExtensionDevOptions(this._environmentService); - if (!devOpts.isExtensionDevTestFromCli) { + if (!this._isExtensionDevTestFromCli) { this._windowService.closeWindow(); } diff --git a/src/vs/workbench/services/extensions/node/extensionHostMain.ts b/src/vs/workbench/services/extensions/node/extensionHostMain.ts index 60e47348ba80..d2d1cf46aa1d 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostMain.ts @@ -19,7 +19,6 @@ import { RPCProtocol } from 'vs/workbench/services/extensions/common/rpcProtocol import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; -import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures'; // we don't (yet) throw when extensions parse // uris that have no scheme @@ -53,9 +52,7 @@ export class ExtensionHostMain { hostUtils: IHostUtils, consolePatchFn: IConsolePatchFn, logServiceFn: ILogServiceFn, - uriTransformer: IURITransformer | null, - schemeTransformer: ISchemeTransformer | null, - outputChannelName: string, + uriTransformer: IURITransformer | null ) { this._isTerminating = false; this._hostUtils = hostUtils; @@ -86,8 +83,7 @@ export class ExtensionHostMain { extHostConfiguraiton, initData.environment, this._extHostLogService, - schemeTransformer, - outputChannelName + uriTransformer ); // error forwarding and stack trace scanning diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcess.ts b/src/vs/workbench/services/extensions/node/extensionHostProcess.ts index f8f0ec2bc9f6..b43ea6602c71 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcess.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcess.ts @@ -3,11 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { startExtensionHostProcess } from 'vs/workbench/services/extensions/node/extensionHostProcessSetup'; -startExtensionHostProcess( - _ => null, - _ => null, - _ => nls.localize('extension host Log', "Extension Host") -).catch((err) => console.log(err)); +startExtensionHostProcess().catch((err) => console.log(err)); diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 2e303b8f5fbf..9994b3103f79 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -5,23 +5,33 @@ import * as nativeWatchdog from 'native-watchdog'; import * as net from 'net'; +import * as minimist from 'minimist'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; -import { PersistentProtocol, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net'; -import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; +import { PersistentProtocol, ProtocolConstants, createBufferedEvent } from 'vs/base/parts/ipc/common/ipc.net'; +import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import product from 'vs/platform/product/node/product'; import { IInitData, MainThreadConsoleShape } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { ExtensionHostMain, IExitFn, ILogServiceFn } from 'vs/workbench/services/extensions/node/extensionHostMain'; import { VSBuffer } from 'vs/base/common/buffer'; -import { createBufferSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/common/extensions'; -import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures'; -import { IURITransformer } from 'vs/base/common/uriIpc'; +import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc'; import { exists } from 'vs/base/node/pfs'; import { realpath } from 'vs/base/node/extpath'; import { IHostUtils } from 'vs/workbench/api/node/extHostExtensionService'; +import { SpdLogService } from 'vs/platform/log/node/spdlogService'; + +interface ParsedExtHostArgs { + uriTransformerPath?: string; +} + +const args = minimist(process.argv.slice(2), { + string: [ + 'uriTransformerPath' + ] +}) as ParsedExtHostArgs; // With Electron 2.x and node.js 8.x the "natives" module // can cause a native crash (see https://github.com/nodejs/node/issues/19891 and @@ -72,7 +82,7 @@ function patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void { }; } -const createLogService: ILogServiceFn = initData => createBufferSpdLogService(ExtensionHostLogFileName, initData.logLevel, initData.logsLocation.fsPath); +const createLogService: ILogServiceFn = initData => new SpdLogService(ExtensionHostLogFileName, initData.logsLocation.fsPath, initData.logLevel); interface IRendererConnection { protocol: IMessagePassingProtocol; @@ -101,27 +111,41 @@ function _createExtHostProtocol(): Promise { process.on('message', (msg: IExtHostSocketMessage, handle: net.Socket) => { if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') { const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64')); + let socket: NodeSocket | WebSocketNodeSocket; + if (msg.skipWebSocketFrames) { + socket = new NodeSocket(handle); + } else { + socket = new WebSocketNodeSocket(new NodeSocket(handle)); + } if (protocol) { // reconnection case if (disconnectWaitTimer) { clearTimeout(disconnectWaitTimer); disconnectWaitTimer = null; } - protocol.beginAcceptReconnection(new NodeSocket(handle), initialDataChunk); + protocol.beginAcceptReconnection(socket, initialDataChunk); protocol.endAcceptReconnection(); } else { clearTimeout(timer); - protocol = new PersistentProtocol(new NodeSocket(handle), initialDataChunk); + protocol = new PersistentProtocol(socket, initialDataChunk); protocol.onClose(() => onTerminate()); resolve(protocol); - protocol.onSocketClose(() => { - // The socket has closed, let's give the renderer a certain amount of time to reconnect - disconnectWaitTimer = setTimeout(() => { - disconnectWaitTimer = null; + if (msg.skipWebSocketFrames) { + // Wait for rich client to reconnect + protocol.onSocketClose(() => { + // The socket has closed, let's give the renderer a certain amount of time to reconnect + disconnectWaitTimer = setTimeout(() => { + disconnectWaitTimer = null; + onTerminate(); + }, ProtocolConstants.ReconnectionGraceTime); + }); + } else { + // Do not wait for web companion to reconnect + protocol.onSocketClose(() => { onTerminate(); - }, ProtocolConstants.ReconnectionGraceTime); - }); + }); + } } } }); @@ -155,16 +179,22 @@ async function createExtHostProtocol(): Promise { return new class implements IMessagePassingProtocol { - private _terminating = false; + private readonly _onMessage = new Emitter(); + readonly onMessage: Event = createBufferedEvent(this._onMessage.event); - readonly onMessage: Event = Event.filter(protocol.onMessage, msg => { - if (!isMessageOfType(msg, MessageType.Terminate)) { - return true; - } - this._terminating = true; - onTerminate(); - return false; - }); + private _terminating: boolean; + + constructor() { + this._terminating = false; + protocol.onMessage((msg) => { + if (isMessageOfType(msg, MessageType.Terminate)) { + this._terminating = true; + onTerminate(); + } else { + this._onMessage.fire(msg); + } + }); + } send(msg: any): void { if (!this._terminating) { @@ -272,11 +302,7 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise IURITransformer | null, - schemeTransformerFn: (initData: IInitData) => ISchemeTransformer | null, - outputChannelNameFn: (initData: IInitData) => string, -): Promise { +export async function startExtensionHostProcess(): Promise { const protocol = await createExtHostProtocol(); const renderer = await connectToRenderer(protocol); @@ -291,6 +317,17 @@ export async function startExtensionHostProcess( realpath(path: string) { return realpath(path); } }; + // Attempt to load uri transformer + let uriTransformer: IURITransformer | null = null; + if (initData.remoteAuthority && args.uriTransformerPath) { + try { + const rawURITransformerFactory = require.__$__nodeRequire(args.uriTransformerPath); + const rawURITransformer = rawURITransformerFactory(initData.remoteAuthority); + uriTransformer = new URITransformer(rawURITransformer); + } catch (e) { + console.error(e); + } + } const extensionHostMain = new ExtensionHostMain( renderer.protocol, @@ -298,9 +335,7 @@ export async function startExtensionHostProcess( hostUtils, patchPatchedConsole, createLogService, - uriTransformerFn(initData), - schemeTransformerFn(initData), - outputChannelNameFn(initData) + uriTransformer ); // rewrite onTerminate-function to be a proper shutdown diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 90f69da1828a..169a3370fe86 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -12,40 +12,13 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; -import { getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { getGalleryExtensionId, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator'; -import { ExtensionIdentifier, ExtensionIdentifierWithVersion, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints'; const MANIFEST_FILE = 'package.json'; -export interface Translations { - [id: string]: string; -} - -namespace Translations { - export function equals(a: Translations, b: Translations): boolean { - if (a === b) { - return true; - } - let aKeys = Object.keys(a); - let bKeys: Set = new Set(); - for (let key of Object.keys(b)) { - bKeys.add(key); - } - if (aKeys.length !== bKeys.size) { - return false; - } - - for (let key of aKeys) { - if (a[key] !== b[key]) { - return false; - } - bKeys.delete(key); - } - return bKeys.size === 0; - } -} - export interface NlsConfiguration { readonly devMode: boolean; readonly locale: string | undefined; @@ -53,12 +26,6 @@ export interface NlsConfiguration { readonly translations: Translations; } -export interface ILog { - error(source: string, message: string): void; - warn(source: string, message: string): void; - info(source: string, message: string): void; -} - abstract class ExtensionManifestHandler { protected readonly _ourVersion: string; diff --git a/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts b/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts index 171b524764f2..0fa330490a85 100644 --- a/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts +++ b/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts @@ -16,10 +16,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localize } from 'vs/nls'; -import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/map'; +import { IProductService } from 'vs/platform/product/common/product'; export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService { @@ -35,7 +36,8 @@ export class MultiExtensionManagementService extends Disposable implements IExte constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IProductService private readonly productService: IProductService, ) { super(); this.servers = this.extensionManagementServerService.remoteExtensionManagementServer ? [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer] : [this.extensionManagementServerService.localExtensionManagementServer]; @@ -85,7 +87,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, force?: boolean): Promise { if (server === this.extensionManagementServerService.localExtensionManagementServer) { const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getInstalled(ExtensionType.User); - const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.configurationService) + const dependentNonUIExtensions = installedExtensions.filter(i => !isUIExtension(i.manifest, this.productService, this.configurationService) && i.manifest.extensionDependencies && i.manifest.extensionDependencies.some(id => areSameExtensions({ id }, extension.identifier))); if (dependentNonUIExtensions.length) { return Promise.reject(new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions))); @@ -140,7 +142,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte const [extensionIdentifier] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix))); return extensionIdentifier; } - if (isUIExtension(manifest, this.configurationService)) { + if (isUIExtension(manifest, this.productService, this.configurationService)) { // Install only on local server return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix); } @@ -161,7 +163,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte // Install on both servers return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(() => undefined); } - if (isUIExtension(manifest, this.configurationService)) { + if (isUIExtension(manifest, this.productService, this.configurationService)) { // Install only on local server return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery); } @@ -210,7 +212,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte for (let idx = 0; idx < extensions.length; idx++) { const extension = extensions[idx]; const manifest = manifests[idx]; - if (manifest && isUIExtension(manifest, this.configurationService)) { + if (manifest && isUIExtension(manifest, this.productService, this.configurationService)) { result.set(extension.identifier.id.toLowerCase(), extension); uiExtensionsManifests.push(manifest); } diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 2e3727a96265..c921028712ae 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -457,8 +457,8 @@ async function readCaCertificates() { return undefined; } -function readWindowsCaCertificates() { - const winCA = require.__$__nodeRequire('vscode-windows-ca-certs'); +async function readWindowsCaCertificates() { + const winCA = await import('vscode-windows-ca-certs'); let ders: any[] = []; const store = winCA(); @@ -481,7 +481,14 @@ function readWindowsCaCertificates() { } async function readMacCaCertificates() { - const stdout = (await promisify(cp.execFile)('/usr/bin/security', ['find-certificate', '-a', '-p'], { encoding: 'utf8', maxBuffer: 1024 * 1024 })).stdout; + const stdout = await new Promise((resolve, reject) => { + const child = cp.spawn('/usr/bin/security', ['find-certificate', '-a', '-p']); + const stdout: string[] = []; + child.stdout.setEncoding('utf8'); + child.stdout.on('data', str => stdout.push(str)); + child.on('error', reject); + child.on('exit', code => code ? reject(code) : resolve(stdout.join(''))); + }); const seen = {}; const certs = stdout.split(/(?=-----BEGIN CERTIFICATE-----)/g) .filter(pem => !!pem.length && !seen[pem] && (seen[pem] = true)); diff --git a/src/vs/workbench/services/files/common/fileService.ts b/src/vs/workbench/services/files/common/fileService.ts index 0ee4793ddca5..c7416844d071 100644 --- a/src/vs/workbench/services/files/common/fileService.ts +++ b/src/vs/workbench/services/files/common/fileService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; @@ -55,14 +55,12 @@ export class FileService extends Disposable implements IFileService { providerDisposables.push(provider.onDidErrorOccur(error => this._onError.fire(error))); } - return combinedDisposable([ - toDisposable(() => { - this._onDidChangeFileSystemProviderRegistrations.fire({ added: false, scheme, provider }); - this.provider.delete(scheme); + return toDisposable(() => { + this._onDidChangeFileSystemProviderRegistrations.fire({ added: false, scheme, provider }); + this.provider.delete(scheme); - dispose(providerDisposables); - }) - ]); + dispose(providerDisposables); + }); } async activateProvider(scheme: string): Promise { @@ -80,7 +78,7 @@ export class FileService extends Disposable implements IFileService { }); if (this.provider.has(scheme)) { - return Promise.resolve(); // provider is already here so we can return directly + return; // provider is already here so we can return directly } // If the provider is not yet there, make sure to join on the listeners assuming @@ -237,7 +235,7 @@ export class FileService extends Disposable implements IFileService { return fileStat; } - return Promise.resolve(fileStat); + return fileStat; } async resolveAll(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise; diff --git a/src/vs/workbench/services/files/test/node/diskFileService.test.ts b/src/vs/workbench/services/files/test/node/diskFileService.test.ts index 328718350c68..9c87b789777d 100644 --- a/src/vs/workbench/services/files/test/node/diskFileService.test.ts +++ b/src/vs/workbench/services/files/test/node/diskFileService.test.ts @@ -18,7 +18,7 @@ import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameS import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat } from 'vs/platform/files/common/files'; import { NullLogService } from 'vs/platform/log/common/log'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; @@ -125,21 +125,21 @@ suite('Disk File Service', () => { let testProvider: TestDiskFileSystemProvider; let testDir: string; - let disposables: IDisposable[] = []; + const disposables = new DisposableStore(); setup(async () => { const logService = new NullLogService(); service = new FileService(logService); - disposables.push(service); + disposables.add(service); fileProvider = new TestDiskFileSystemProvider(logService); - disposables.push(service.registerProvider(Schemas.file, fileProvider)); - disposables.push(fileProvider); + disposables.add(service.registerProvider(Schemas.file, fileProvider)); + disposables.add(fileProvider); testProvider = new TestDiskFileSystemProvider(logService); - disposables.push(service.registerProvider(testSchema, testProvider)); - disposables.push(testProvider); + disposables.add(service.registerProvider(testSchema, testProvider)); + disposables.add(testProvider); const id = generateUuid(); testDir = join(parentDir, id); @@ -149,14 +149,14 @@ suite('Disk File Service', () => { }); teardown(async () => { - disposables = dispose(disposables); + disposables.clear(); await rimraf(parentDir, RimRafMode.MOVE); }); test('createFolder', async () => { let event: FileOperationEvent | undefined; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const parent = await service.resolve(URI.file(testDir)); @@ -176,7 +176,7 @@ suite('Disk File Service', () => { test('createFolder: creating multiple folders at once', async function () { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const multiFolderPaths = ['a', 'couple', 'of', 'folders']; const parent = await service.resolve(URI.file(testDir)); @@ -411,7 +411,7 @@ suite('Disk File Service', () => { test('deleteFile', async () => { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const resource = URI.file(join(testDir, 'deep', 'conway.js')); const source = await service.resolve(resource); @@ -426,7 +426,7 @@ suite('Disk File Service', () => { test('deleteFolder (recursive)', async () => { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const resource = URI.file(join(testDir, 'deep')); const source = await service.resolve(resource); @@ -446,15 +446,14 @@ suite('Disk File Service', () => { await service.del(source.resource); return Promise.reject(new Error('Unexpected')); - } - catch (error) { + } catch (error) { return Promise.resolve(true); } }); test('move', async () => { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const source = URI.file(join(testDir, 'index.html')); const sourceContents = readFileSync(source.fsPath); @@ -534,7 +533,7 @@ suite('Disk File Service', () => { async function testMoveAcrossProviders(sourceFile = 'index.html'): Promise { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const source = URI.file(join(testDir, sourceFile)); const sourceContents = readFileSync(source.fsPath); @@ -558,7 +557,7 @@ suite('Disk File Service', () => { test('move - multi folder', async () => { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const multiFolderPaths = ['a', 'couple', 'of', 'folders']; const renameToPath = join(...multiFolderPaths, 'other.html'); @@ -577,7 +576,7 @@ suite('Disk File Service', () => { test('move - directory', async () => { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const source = URI.file(join(testDir, 'deep')); @@ -621,7 +620,7 @@ suite('Disk File Service', () => { async function testMoveFolderAcrossProviders(): Promise { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const source = URI.file(join(testDir, 'deep')); const sourceChildren = readdirSync(source.fsPath); @@ -646,7 +645,7 @@ suite('Disk File Service', () => { test('move - MIX CASE', async () => { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const source = URI.file(join(testDir, 'index.html')); await service.resolve(source); @@ -663,7 +662,7 @@ suite('Disk File Service', () => { test('move - source parent of target', async () => { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); await service.resolve(URI.file(join(testDir, 'index.html'))); try { @@ -676,7 +675,7 @@ suite('Disk File Service', () => { test('move - FILE_MOVE_CONFLICT', async () => { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const source = await service.resolve(URI.file(join(testDir, 'index.html'))); try { @@ -691,7 +690,7 @@ suite('Disk File Service', () => { let createEvent: FileOperationEvent; let moveEvent: FileOperationEvent; let deleteEvent: FileOperationEvent; - disposables.push(service.onAfterOperation(e => { + disposables.add(service.onAfterOperation(e => { if (e.operation === FileOperation.CREATE) { createEvent = e; } else if (e.operation === FileOperation.DELETE) { @@ -755,7 +754,7 @@ suite('Disk File Service', () => { async function doTestCopy(sourceName: string = 'index.html') { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const source = await service.resolve(URI.file(join(testDir, sourceName))); const target = URI.file(join(testDir, 'other.html')); @@ -780,7 +779,7 @@ suite('Disk File Service', () => { let createEvent: FileOperationEvent; let copyEvent: FileOperationEvent; let deleteEvent: FileOperationEvent; - disposables.push(service.onAfterOperation(e => { + disposables.add(service.onAfterOperation(e => { if (e.operation === FileOperation.CREATE) { createEvent = e; } else if (e.operation === FileOperation.DELETE) { @@ -1169,7 +1168,7 @@ suite('Disk File Service', () => { test('createFile', async () => { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const contents = 'Hello World'; const resource = URI.file(join(testDir, 'test.txt')); @@ -1200,7 +1199,7 @@ suite('Disk File Service', () => { test('createFile (allows to overwrite existing)', async () => { let event: FileOperationEvent; - disposables.push(service.onAfterOperation(e => event = e)); + disposables.add(service.onAfterOperation(e => event = e)); const contents = 'Hello World'; const resource = URI.file(join(testDir, 'test.txt')); diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index 74dda793dd92..e49912a7aada 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -28,7 +28,6 @@ import { IKeybindingItem, IKeybindingRule2, KeybindingWeight, KeybindingsRegistr import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { keybindingsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -41,6 +40,7 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { MenuRegistry } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { commandsExtensionPoint } from 'vs/workbench/api/common/menusExtensionPoint'; export class KeyboardMapperFactory { public static readonly INSTANCE = new KeyboardMapperFactory(); @@ -215,7 +215,7 @@ let keybindingType: IJSONSchema = { description: nls.localize('vscode.extension.contributes.keybindings.args', "Arguments to pass to the command to execute.") }, key: { - description: nls.localize('vscode.extension.contributes.keybindings.key', 'Key or key sequence (separate keys with plus-sign and sequences with space, e.g Ctrl+O and Ctrl+L L for a chord).'), + description: nls.localize('vscode.extension.contributes.keybindings.key', 'Key or key sequence (separate keys with plus-sign and sequences with space, e.g. Ctrl+O and Ctrl+L L for a chord).'), type: 'string' }, mac: { @@ -239,6 +239,7 @@ let keybindingType: IJSONSchema = { const keybindingsExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'keybindings', + deps: [commandsExtensionPoint], jsonSchema: { description: nls.localize('vscode.extension.contributes.keybindings', "Contributes keybindings."), oneOf: [ @@ -275,12 +276,11 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { @ITelemetryService telemetryService: ITelemetryService, @INotificationService notificationService: INotificationService, @IEnvironmentService environmentService: IEnvironmentService, - @IStatusbarService statusBarService: IStatusbarService, @IConfigurationService configurationService: IConfigurationService, @IWindowService private readonly windowService: IWindowService, @IExtensionService extensionService: IExtensionService ) { - super(contextKeyService, commandService, telemetryService, notificationService, statusBarService); + super(contextKeyService, commandService, telemetryService, notificationService); updateSchema(); @@ -500,10 +500,21 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { weight = KeybindingWeight.ExternalExtension + idx; } + let commandAction = MenuRegistry.getCommand(command); + let precondition = commandAction && commandAction.precondition; + let fullWhen: ContextKeyExpr | undefined; + if (when && precondition) { + fullWhen = ContextKeyExpr.and(precondition, ContextKeyExpr.deserialize(when)); + } else if (when) { + fullWhen = ContextKeyExpr.deserialize(when); + } else if (precondition) { + fullWhen = precondition; + } + let desc: IKeybindingRule2 = { id: command, args, - when: ContextKeyExpr.deserialize(when), + when: fullWhen, weight: weight, primary: KeybindingParser.parseKeybinding(key, OS), mac: mac ? { primary: KeybindingParser.parseKeybinding(mac, OS) } : null, @@ -712,4 +723,4 @@ const keyboardConfiguration: IConfigurationNode = { configurationRegistry.registerConfiguration(keyboardConfiguration); -registerSingleton(IKeybindingService, WorkbenchKeybindingService); \ No newline at end of file +registerSingleton(IKeybindingService, WorkbenchKeybindingService); diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 7b5051085774..b8fd976c8fed 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -44,6 +44,7 @@ import { TestBackupFileService, TestContextService, TestEditorGroupsService, Tes import { FileService } from 'vs/workbench/services/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; +import { URI } from 'vs/base/common/uri'; interface Modifiers { metaKey?: boolean; @@ -65,7 +66,7 @@ suite('KeybindingsEditing', () => { instantiationService = new TestInstantiationService(); - instantiationService.stub(IEnvironmentService, { appKeybindingsPath: keybindingsFile, appSettingsPath: path.join(testDir, 'settings.json') }); + instantiationService.stub(IEnvironmentService, { appKeybindingsPath: keybindingsFile, settingsResource: URI.file(path.join(testDir, 'settings.json')) }); instantiationService.stub(IConfigurationService, ConfigurationService); instantiationService.stub(IConfigurationService, 'getValue', { 'eol': '\n' }); instantiationService.stub(IConfigurationService, 'onDidUpdateConfiguration', () => { }); diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 57ff08447899..75617e9aa083 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -45,6 +45,21 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ readonly onZenModeChange: Event; + /** + * Emits when fullscreen is enabled or disabled. + */ + readonly onFullscreenChange: Event; + + /** + * Emits when centered layout is enabled or disabled. + */ + readonly onCenteredLayoutChange: Event; + + /** + * Emit when panel position changes. + */ + readonly onPanelPositionChange: Event; + /** * Asks the part service if all parts have been fully restored. For editor part * this means that the contents of editors have loaded. diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 4621348a5008..f477cb728161 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -3,15 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice, IPromptOptions, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; import { INotificationsModel, NotificationsModel, ChoiceAction } from 'vs/workbench/common/notifications'; -import { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IAction } from 'vs/base/common/actions'; export class NotificationService extends Disposable implements INotificationService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private _model: INotificationsModel = this._register(new NotificationsModel()); @@ -26,7 +28,7 @@ export class NotificationService extends Disposable implements INotificationServ return; } - this.model.notify({ severity: Severity.Info, message }); + this.model.addNotification({ severity: Severity.Info, message }); } warn(message: NotificationMessage | NotificationMessage[]): void { @@ -36,7 +38,7 @@ export class NotificationService extends Disposable implements INotificationServ return; } - this.model.notify({ severity: Severity.Warning, message }); + this.model.addNotification({ severity: Severity.Warning, message }); } error(message: NotificationMessage | NotificationMessage[]): void { @@ -46,37 +48,32 @@ export class NotificationService extends Disposable implements INotificationServ return; } - this.model.notify({ severity: Severity.Error, message }); + this.model.addNotification({ severity: Severity.Error, message }); } notify(notification: INotification): INotificationHandle { - return this.model.notify(notification); + return this.model.addNotification(notification); } prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { - const toDispose: IDisposable[] = []; + const toDispose = new DisposableStore(); let choiceClicked = false; let handle: INotificationHandle; // Convert choices into primary/secondary actions - const actions: INotificationActions = { primary: [], secondary: [] }; + const primaryActions: IAction[] = []; + const secondaryActions: IAction[] = []; choices.forEach((choice, index) => { const action = new ChoiceAction(`workbench.dialog.choice.${index}`, choice); if (!choice.isSecondary) { - if (!actions.primary) { - actions.primary = []; - } - actions.primary.push(action); + primaryActions.push(action); } else { - if (!actions.secondary) { - actions.secondary = []; - } - actions.secondary.push(action); + secondaryActions.push(action); } // React to action being clicked - toDispose.push(action.onDidRun(() => { + toDispose.add(action.onDidRun(() => { choiceClicked = true; // Close notification unless we are told to keep open @@ -85,16 +82,17 @@ export class NotificationService extends Disposable implements INotificationServ } })); - toDispose.push(action); + toDispose.add(action); }); // Show notification with actions + const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; handle = this.notify({ severity, message, actions, sticky: options && options.sticky, silent: options && options.silent }); Event.once(handle.onDidClose)(() => { // Cleanup when notification gets disposed - dispose(toDispose); + toDispose.dispose(); // Indicate cancellation to the outside if no action was executed if (options && typeof options.onCancel === 'function' && !choiceClicked) { @@ -104,6 +102,10 @@ export class NotificationService extends Disposable implements INotificationServ return handle; } + + status(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable { + return this.model.showStatusMessage(message, options); + } } registerSingleton(INotificationService, NotificationService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/output/common/outputChannelModel.ts b/src/vs/workbench/services/output/common/outputChannelModel.ts index 73a4db073c8c..49dc1b0f9541 100644 --- a/src/vs/workbench/services/output/common/outputChannelModel.ts +++ b/src/vs/workbench/services/output/common/outputChannelModel.ts @@ -51,10 +51,10 @@ export abstract class AsbtractOutputChannelModelService { export abstract class AbstractFileOutputChannelModel extends Disposable implements IOutputChannelModel { - protected _onDidAppendedContent = new Emitter(); + protected readonly _onDidAppendedContent = this._register(new Emitter()); readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; - protected _onDispose = new Emitter(); + protected readonly _onDispose = this._register(new Emitter()); readonly onDispose: Event = this._onDispose.event; protected modelUpdater: RunOnceScheduler; @@ -96,12 +96,11 @@ export abstract class AbstractFileOutputChannelModel extends Disposable implemen } else { this.model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri); this.onModelCreated(this.model); - const disposables: IDisposable[] = []; - disposables.push(this.model.onWillDispose(() => { + const disposable = this.model.onWillDispose(() => { this.onModelWillDispose(this.model); this.model = null; - dispose(disposables); - })); + dispose(disposable); + }); } return this.model; } @@ -340,11 +339,10 @@ export class BufferredOutputChannel extends Disposable implements IOutputChannel private createModel(content: string): ITextModel { const model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri); - const disposables: IDisposable[] = []; - disposables.push(model.onWillDispose(() => { + const disposable = model.onWillDispose(() => { this.model = null; - dispose(disposables); - })); + dispose(disposable); + }); return model; } diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index ecb2429752d4..1c2075d893a3 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -524,9 +524,9 @@ export class PreferencesService extends Disposable implements IPreferencesServic switch (configurationTarget) { case ConfigurationTarget.USER: case ConfigurationTarget.USER_LOCAL: - return URI.file(this.environmentService.appSettingsPath); + return this.environmentService.settingsResource; case ConfigurationTarget.USER_REMOTE: - return URI.file(this.environmentService.appSettingsPath); + return this.environmentService.settingsResource; case ConfigurationTarget.WORKSPACE: if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { return null; diff --git a/src/vs/workbench/services/progress/browser/localProgressService.ts b/src/vs/workbench/services/progress/browser/localProgressService.ts new file mode 100644 index 000000000000..c1b2c3b06a24 --- /dev/null +++ b/src/vs/workbench/services/progress/browser/localProgressService.ts @@ -0,0 +1,306 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import * as types from 'vs/base/common/types'; +import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { ILocalProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +namespace ProgressState { + + export const enum Type { + None, + Done, + Infinite, + While, + Work + } + + export const None = new class { readonly type = Type.None; }; + export const Done = new class { readonly type = Type.Done; }; + export const Infinite = new class { readonly type = Type.Infinite; }; + + export class While { + readonly type = Type.While; + + constructor( + readonly whilePromise: Promise, + readonly whileStart: number, + readonly whileDelay: number, + ) { } + } + + export class Work { + readonly type = Type.Work; + + constructor( + readonly total: number | undefined, + readonly worked: number | undefined + ) { } + } + + export type State = + typeof None + | typeof Done + | typeof Infinite + | While + | Work; +} + +export abstract class ScopedService extends Disposable { + + constructor( + private viewletService: IViewletService, + private panelService: IPanelService, + private scopeId: string + ) { + super(); + + this.registerListeners(); + } + + registerListeners(): void { + this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId()))); + this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId()))); + + this._register(this.viewletService.onDidViewletClose(viewlet => this.onScopeClosed(viewlet.getId()))); + this._register(this.panelService.onDidPanelClose(panel => this.onScopeClosed(panel.getId()))); + } + + private onScopeClosed(scopeId: string) { + if (scopeId === this.scopeId) { + this.onScopeDeactivated(); + } + } + + private onScopeOpened(scopeId: string) { + if (scopeId === this.scopeId) { + this.onScopeActivated(); + } + } + + abstract onScopeActivated(): void; + + abstract onScopeDeactivated(): void; +} + +export class ScopedProgressService extends ScopedService implements ILocalProgressService { + + _serviceBrand: ServiceIdentifier; + + private isActive: boolean; + private progressbar: ProgressBar; + private progressState: ProgressState.State = ProgressState.None; + + constructor( + progressbar: ProgressBar, + scopeId: string, + isActive: boolean, + @IViewletService viewletService: IViewletService, + @IPanelService panelService: IPanelService + ) { + super(viewletService, panelService, scopeId); + + this.progressbar = progressbar; + this.isActive = isActive || types.isUndefinedOrNull(scopeId); // If service is unscoped, enable by default + } + + onScopeDeactivated(): void { + this.isActive = false; + } + + onScopeActivated(): void { + this.isActive = true; + + // Return early if progress state indicates that progress is done + if (this.progressState.type === ProgressState.Done.type) { + return; + } + + // Replay Infinite Progress from Promise + if (this.progressState.type === ProgressState.Type.While) { + let delay: number | undefined; + if (this.progressState.whileDelay > 0) { + const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart); + if (remainingDelay > 0) { + delay = remainingDelay; + } + } + + this.doShowWhile(delay); + } + + // Replay Infinite Progress + else if (this.progressState.type === ProgressState.Type.Infinite) { + this.progressbar.infinite().show(); + } + + // Replay Finite Progress (Total & Worked) + else if (this.progressState.type === ProgressState.Type.Work) { + if (this.progressState.total) { + this.progressbar.total(this.progressState.total).show(); + } + + if (this.progressState.worked) { + this.progressbar.worked(this.progressState.worked).show(); + } + } + } + + show(infinite: true, delay?: number): IProgressRunner; + show(total: number, delay?: number): IProgressRunner; + show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { + + // Sort out Arguments + if (typeof infiniteOrTotal === 'boolean') { + this.progressState = ProgressState.Infinite; + } else { + this.progressState = new ProgressState.Work(infiniteOrTotal, undefined); + } + + // Active: Show Progress + if (this.isActive) { + + // Infinite: Start Progressbar and Show after Delay + if (this.progressState.type === ProgressState.Type.Infinite) { + this.progressbar.infinite().show(delay); + } + + // Finite: Start Progressbar and Show after Delay + else if (this.progressState.type === ProgressState.Type.Work && typeof this.progressState.total === 'number') { + this.progressbar.total(this.progressState.total).show(delay); + } + } + + return { + total: (total: number) => { + this.progressState = new ProgressState.Work( + total, + this.progressState.type === ProgressState.Type.Work ? this.progressState.worked : undefined); + + if (this.isActive) { + this.progressbar.total(total); + } + }, + + worked: (worked: number) => { + + // Verify first that we are either not active or the progressbar has a total set + if (!this.isActive || this.progressbar.hasTotal()) { + this.progressState = new ProgressState.Work( + this.progressState.type === ProgressState.Type.Work ? this.progressState.total : undefined, + this.progressState.type === ProgressState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked); + + if (this.isActive) { + this.progressbar.worked(worked); + } + } + + // Otherwise the progress bar does not support worked(), we fallback to infinite() progress + else { + this.progressState = ProgressState.Infinite; + this.progressbar.infinite().show(); + } + }, + + done: () => { + this.progressState = ProgressState.Done; + + if (this.isActive) { + this.progressbar.stop().hide(); + } + } + }; + } + + async showWhile(promise: Promise, delay?: number): Promise { + + // Join with existing running promise to ensure progress is accurate + if (this.progressState.type === ProgressState.Type.While) { + promise = Promise.all([promise, this.progressState.whilePromise]); + } + + // Keep Promise in State + this.progressState = new ProgressState.While(promise, delay || 0, Date.now()); + + try { + this.doShowWhile(delay); + + await promise; + } catch (error) { + // ignore + } finally { + + // If this is not the last promise in the list of joined promises, skip this + if (this.progressState.type !== ProgressState.Type.While || this.progressState.whilePromise === promise) { + + // The while promise is either null or equal the promise we last hooked on + this.progressState = ProgressState.None; + + if (this.isActive) { + this.progressbar.stop().hide(); + } + } + } + } + + private doShowWhile(delay?: number): void { + + // Show Progress when active + if (this.isActive) { + this.progressbar.infinite().show(delay); + } + } +} + +export class LocalProgressService implements ILocalProgressService { + + _serviceBrand: ServiceIdentifier; + + constructor(private progressbar: ProgressBar) { } + + show(infinite: true, delay?: number): IProgressRunner; + show(total: number, delay?: number): IProgressRunner; + show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { + if (typeof infiniteOrTotal === 'boolean') { + this.progressbar.infinite().show(delay); + } else { + this.progressbar.total(infiniteOrTotal).show(delay); + } + + return { + total: (total: number) => { + this.progressbar.total(total); + }, + + worked: (worked: number) => { + if (this.progressbar.hasTotal()) { + this.progressbar.worked(worked); + } else { + this.progressbar.infinite().show(); + } + }, + + done: () => { + this.progressbar.stop().hide(); + } + }; + } + + async showWhile(promise: Promise, delay?: number): Promise { + try { + this.progressbar.infinite().show(delay); + + await promise; + } catch (error) { + // ignore + } finally { + this.progressbar.stop().hide(); + } + } +} diff --git a/src/vs/workbench/services/progress/browser/media/progressService2.css b/src/vs/workbench/services/progress/browser/media/progressService.css similarity index 85% rename from src/vs/workbench/services/progress/browser/media/progressService2.css rename to src/vs/workbench/services/progress/browser/media/progressService.css index e4516338b5c1..c7e5960f2b5e 100644 --- a/src/vs/workbench/services/progress/browser/media/progressService2.css +++ b/src/vs/workbench/services/progress/browser/media/progressService.css @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .part.statusbar > .statusbar-item.progress { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.progress { padding-left: 5px; } -.monaco-workbench .part.statusbar > .statusbar-item.progress .spinner-container { +.monaco-workbench .part.statusbar > .items-container > .statusbar-item.progress .spinner-container { padding-right: 5px; } diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index f5f09c13ab9c..80751e844ab2 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -3,294 +3,341 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; -import * as types from 'vs/base/common/types'; -import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; - +import 'vs/css!./media/progressService'; -namespace ProgressState { - export const enum Type { - None, - Done, - Infinite, - While, - Work - } - - export const None = new class { readonly type = Type.None; }; - export const Done = new class { readonly type = Type.Done; }; - export const Infinite = new class { readonly type = Type.Infinite; }; - - export class While { - public readonly type = Type.While; - constructor( - public readonly whilePromise: Promise, - public readonly whileStart: number, - public readonly whileDelay: number, - ) { } - } - - export class Work { - public readonly type = Type.Work; - constructor( - public readonly total: number | undefined, - public readonly worked: number | undefined - ) { } - } - - export type State = - typeof None - | typeof Done - | typeof Infinite - | While - | Work; -} - -export abstract class ScopedService extends Disposable { +import { localize } from 'vs/nls'; +import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; +import { timeout } from 'vs/base/common/async'; +import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; +import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification'; +import { Action } from 'vs/base/common/actions'; +import { Event } from 'vs/base/common/event'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; +import { attachDialogStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { EventHelper } from 'vs/base/browser/dom'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; - constructor(private viewletService: IViewletService, private panelService: IPanelService, private scopeId: string) { - super(); +export class ProgressService implements IProgressService { - this.registerListeners(); - } + _serviceBrand: ServiceIdentifier; - registerListeners(): void { - this._register(this.viewletService.onDidViewletOpen(viewlet => this.onScopeOpened(viewlet.getId()))); - this._register(this.panelService.onDidPanelOpen(({ panel }) => this.onScopeOpened(panel.getId()))); + private readonly _stack: [IProgressOptions, Progress][] = []; + private _globalStatusEntry: IDisposable; - this._register(this.viewletService.onDidViewletClose(viewlet => this.onScopeClosed(viewlet.getId()))); - this._register(this.panelService.onDidPanelClose(panel => this.onScopeClosed(panel.getId()))); - } + constructor( + @IActivityService private readonly _activityBar: IActivityService, + @IViewletService private readonly _viewletService: IViewletService, + @INotificationService private readonly _notificationService: INotificationService, + @IStatusbarService private readonly _statusbarService: IStatusbarService, + @ILayoutService private readonly _layoutService: ILayoutService, + @IThemeService private readonly _themeService: IThemeService, + @IKeybindingService private readonly _keybindingService: IKeybindingService + ) { } + + withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise { + const { location } = options; + if (typeof location === 'string') { + const viewlet = this._viewletService.getViewlet(location); + if (viewlet) { + return this._withViewletProgress(location, task, { ...options, location }); + } - private onScopeClosed(scopeId: string) { - if (scopeId === this.scopeId) { - this.onScopeDeactivated(); + return Promise.reject(new Error(`Bad progress location: ${location}`)); } - } - private onScopeOpened(scopeId: string) { - if (scopeId === this.scopeId) { - this.onScopeActivated(); + switch (location) { + case ProgressLocation.Notification: + return this._withNotificationProgress({ ...options, location }, task, onDidCancel); + case ProgressLocation.Window: + return this._withWindowProgress(options, task); + case ProgressLocation.Explorer: + return this._withViewletProgress('workbench.view.explorer', task, { ...options, location }); + case ProgressLocation.Scm: + return this._withViewletProgress('workbench.view.scm', task, { ...options, location }); + case ProgressLocation.Extensions: + return this._withViewletProgress('workbench.view.extensions', task, { ...options, location }); + case ProgressLocation.Dialog: + return this._withDialogProgress(options, task, onDidCancel); + default: + return Promise.reject(new Error(`Bad progress location: ${location}`)); } } - abstract onScopeActivated(): void; + private _withWindowProgress(options: IProgressOptions, callback: (progress: IProgress<{ message?: string }>) => Promise): Promise { + const task: [IProgressOptions, Progress] = [options, new Progress(() => this._updateWindowProgress())]; + + const promise = callback(task[1]); + + let delayHandle: any = setTimeout(() => { + delayHandle = undefined; + this._stack.unshift(task); + this._updateWindowProgress(); + + // show progress for at least 150ms + Promise.all([ + timeout(150), + promise + ]).finally(() => { + const idx = this._stack.indexOf(task); + this._stack.splice(idx, 1); + this._updateWindowProgress(); + }); + }, 150); + + // cancel delay if promise finishes below 150ms + return promise.finally(() => clearTimeout(delayHandle)); + } - abstract onScopeDeactivated(): void; -} + private _updateWindowProgress(idx: number = 0) { + dispose(this._globalStatusEntry); -export class ScopedProgressService extends ScopedService implements IProgressService { - _serviceBrand: any; - private isActive: boolean; - private progressbar: ProgressBar; - private progressState: ProgressState.State = ProgressState.None; + if (idx < this._stack.length) { + const [options, progress] = this._stack[idx]; - constructor( - progressbar: ProgressBar, - scopeId: string, - isActive: boolean, - @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService - ) { - super(viewletService, panelService, scopeId); - - this.progressbar = progressbar; - this.isActive = isActive || types.isUndefinedOrNull(scopeId); // If service is unscoped, enable by default - } + let progressTitle = options.title; + let progressMessage = progress.value && progress.value.message; + let text: string; + let title: string; - onScopeDeactivated(): void { - this.isActive = false; - } + if (progressTitle && progressMessage) { + // : <message> + text = localize('progress.text2', "{0}: {1}", progressTitle, progressMessage); + title = options.source ? localize('progress.title3', "[{0}] {1}: {2}", options.source, progressTitle, progressMessage) : text; - onScopeActivated(): void { - this.isActive = true; + } else if (progressTitle) { + // <title> + text = progressTitle; + title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressTitle) : text; - // Return early if progress state indicates that progress is done - if (this.progressState.type === ProgressState.Done.type) { - return; - } + } else if (progressMessage) { + // <message> + text = progressMessage; + title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressMessage) : text; - // Replay Infinite Progress from Promise - if (this.progressState.type === ProgressState.Type.While) { - let delay: number | undefined; - if (this.progressState.whileDelay > 0) { - const remainingDelay = this.progressState.whileDelay - (Date.now() - this.progressState.whileStart); - if (remainingDelay > 0) { - delay = remainingDelay; - } + } else { + // no title, no message -> no progress. try with next on stack + this._updateWindowProgress(idx + 1); + return; } - this.doShowWhile(delay); + this._globalStatusEntry = this._statusbarService.addEntry({ + text: `$(sync~spin) ${text}`, + tooltip: title + }, 'status.progress', localize('status.progress', "Progress Message"), StatusbarAlignment.LEFT); } + } - // Replay Infinite Progress - else if (this.progressState.type === ProgressState.Type.Infinite) { - this.progressbar.infinite().show(); - } + private _withNotificationProgress<P extends Promise<R>, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { + const toDispose = new DisposableStore(); - // Replay Finite Progress (Total & Worked) - else if (this.progressState.type === ProgressState.Type.Work) { - if (this.progressState.total) { - this.progressbar.total(this.progressState.total).show(); + const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => { + if (!message) { + return undefined; // we need a message at least } - if (this.progressState.worked) { - this.progressbar.worked(this.progressState.worked).show(); - } - } - } + const primaryActions = options.primaryActions ? Array.from(options.primaryActions) : []; + const secondaryActions = options.secondaryActions ? Array.from(options.secondaryActions) : []; + if (options.cancellable) { + const cancelAction = new class extends Action { + constructor() { + super('progress.cancel', localize('cancel', "Cancel"), undefined, true); + } - show(infinite: true, delay?: number): IProgressRunner; - show(total: number, delay?: number): IProgressRunner; - show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { - // Sort out Arguments - if (typeof infiniteOrTotal === 'boolean') { - this.progressState = ProgressState.Infinite; - } else { - this.progressState = new ProgressState.Work(infiniteOrTotal, undefined); - } + run(): Promise<any> { + if (typeof onDidCancel === 'function') { + onDidCancel(); + } - // Active: Show Progress - if (this.isActive) { + return Promise.resolve(undefined); + } + }; + toDispose.add(cancelAction); - // Infinite: Start Progressbar and Show after Delay - if (this.progressState.type === ProgressState.Type.Infinite) { - this.progressbar.infinite().show(delay); + primaryActions.push(cancelAction); } - // Finite: Start Progressbar and Show after Delay - else if (this.progressState.type === ProgressState.Type.Work && typeof this.progressState.total === 'number') { - this.progressbar.total(this.progressState.total).show(delay); - } - } + const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; + const handle = this._notificationService.notify({ + severity: Severity.Info, + message, + source: options.source, + actions + }); - return { - total: (total: number) => { - this.progressState = new ProgressState.Work( - total, - this.progressState.type === ProgressState.Type.Work ? this.progressState.worked : undefined); + updateProgress(handle, increment); - if (this.isActive) { - this.progressbar.total(total); - } - }, + Event.once(handle.onDidClose)(() => { + toDispose.dispose(); + }); - worked: (worked: number) => { + return handle; + }; - // Verify first that we are either not active or the progressbar has a total set - if (!this.isActive || this.progressbar.hasTotal()) { - this.progressState = new ProgressState.Work( - this.progressState.type === ProgressState.Type.Work ? this.progressState.total : undefined, - this.progressState.type === ProgressState.Type.Work && typeof this.progressState.worked === 'number' ? this.progressState.worked + worked : worked); + const updateProgress = (notification: INotificationHandle, increment?: number): void => { + if (typeof increment === 'number' && increment >= 0) { + notification.progress.total(100); // always percentage based + notification.progress.worked(increment); + } else { + notification.progress.infinite(); + } + }; - if (this.isActive) { - this.progressbar.worked(worked); + let handle: INotificationHandle | undefined; + const updateNotification = (message?: string, increment?: number): void => { + if (!handle) { + handle = createNotification(message, increment); + } else { + if (typeof message === 'string') { + let newMessage: string; + if (typeof options.title === 'string') { + newMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) + } else { + newMessage = message; } - } - // Otherwise the progress bar does not support worked(), we fallback to infinite() progress - else { - this.progressState = ProgressState.Infinite; - this.progressbar.infinite().show(); + handle.updateMessage(newMessage); } - }, - - done: () => { - this.progressState = ProgressState.Done; - if (this.isActive) { - this.progressbar.stop().hide(); + if (typeof increment === 'number') { + updateProgress(handle, increment); } } }; - } - async showWhile(promise: Promise<any>, delay?: number): Promise<void> { + // Show initially + updateNotification(options.title); - // Join with existing running promise to ensure progress is accurate - if (this.progressState.type === ProgressState.Type.While) { - promise = Promise.all([promise, this.progressState.whilePromise]); - } - - // Keep Promise in State - this.progressState = new ProgressState.While(promise, delay || 0, Date.now()); + // Update based on progress + const promise = callback({ + report: progress => { + updateNotification(progress.message, progress.increment); + } + }); - try { - this.doShowWhile(delay); + // Show progress for at least 800ms and then hide once done or canceled + Promise.all([timeout(800), promise]).finally(() => { + if (handle) { + handle.close(); + } + }); - await promise; - } catch (error) { - // ignore - } finally { + return promise; + } - // If this is not the last promise in the list of joined promises, skip this - if (this.progressState.type !== ProgressState.Type.While || this.progressState.whilePromise === promise) { + private _withViewletProgress<P extends Promise<R>, R = unknown>(viewletId: string, task: (progress: IProgress<{ message?: string }>) => P, options: IProgressCompositeOptions): P { + const promise = task(emptyProgress); - // The while promise is either null or equal the promise we last hooked on - this.progressState = ProgressState.None; + // show in viewlet + const viewletProgress = this._viewletService.getProgressIndicator(viewletId); + if (viewletProgress) { + viewletProgress.showWhile(promise, options.delay); + } - if (this.isActive) { - this.progressbar.stop().hide(); + // show activity bar + let activityProgress: IDisposable; + let delayHandle: any = setTimeout(() => { + delayHandle = undefined; + + const handle = this._activityBar.showActivity( + viewletId, + new ProgressBadge(() => ''), + 'progress-badge', + 100 + ); + + const startTimeVisible = Date.now(); + const minTimeVisible = 300; + activityProgress = { + dispose() { + const d = Date.now() - startTimeVisible; + if (d < minTimeVisible) { + // should at least show for Nms + setTimeout(() => handle.dispose(), minTimeVisible - d); + } else { + // shown long enough + handle.dispose(); + } } - } - } - } + }; + }, options.delay || 300); - private doShowWhile(delay?: number): void { + promise.finally(() => { + clearTimeout(delayHandle); + dispose(activityProgress); + }); - // Show Progress when active - if (this.isActive) { - this.progressbar.infinite().show(delay); - } + return promise; } -} - -export class ProgressService implements IProgressService { - _serviceBrand: any; + private _withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { + const disposables = new DisposableStore(); + const allowableCommands = [ + 'workbench.action.quit', + 'workbench.action.reloadWindow' + ]; + + let dialog: Dialog; + + const createDialog = (message: string) => { + dialog = new Dialog( + this._layoutService.container, + message, + [options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")], + { + type: 'pending', + keyEventProcessor: (event: StandardKeyboardEvent) => { + const resolved = this._keybindingService.softDispatch(event, this._layoutService.container); + if (resolved && resolved.commandId) { + if (allowableCommands.indexOf(resolved.commandId) === -1) { + EventHelper.stop(event, true); + } + } + } + } + ); - constructor(private progressbar: ProgressBar) { } + disposables.add(dialog); + disposables.add(attachDialogStyler(dialog, this._themeService)); - show(infinite: true, delay?: number): IProgressRunner; - show(total: number, delay?: number): IProgressRunner; - show(infiniteOrTotal: true | number, delay?: number): IProgressRunner { - if (typeof infiniteOrTotal === 'boolean') { - this.progressbar.infinite().show(delay); - } else { - this.progressbar.total(infiniteOrTotal).show(delay); - } + dialog.show().then(() => { + if (typeof onDidCancel === 'function') { + onDidCancel(); + } - return { - total: (total: number) => { - this.progressbar.total(total); - }, + dispose(dialog); + }); - worked: (worked: number) => { - if (this.progressbar.hasTotal()) { - this.progressbar.worked(worked); - } else { - this.progressbar.infinite().show(); - } - }, + return dialog; + }; - done: () => { - this.progressbar.stop().hide(); + const updateDialog = (message?: string) => { + if (message && !dialog) { + dialog = createDialog(message); + } else if (message) { + dialog.updateMessage(message); } }; - } - async showWhile(promise: Promise<any>, delay?: number): Promise<void> { - try { - this.progressbar.infinite().show(delay); + const promise = task({ + report: progress => { + updateDialog(progress.message); + } + }); - await promise; - } catch (error) { - // ignore - } finally { - this.progressbar.stop().hide(); - } + promise.finally(() => { + dispose(disposables); + }); + + return promise; } } + +registerSingleton(IProgressService, ProgressService, true); diff --git a/src/vs/workbench/services/progress/browser/progressService2.ts b/src/vs/workbench/services/progress/browser/progressService2.ts deleted file mode 100644 index 1e516d069ad7..000000000000 --- a/src/vs/workbench/services/progress/browser/progressService2.ts +++ /dev/null @@ -1,344 +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/progressService2'; - -import { localize } from 'vs/nls'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IProgressService2, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; -import { timeout } from 'vs/base/common/async'; -import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; -import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification'; -import { Action } from 'vs/base/common/actions'; -import { Event } from 'vs/base/common/event'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; -import { attachDialogStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { EventHelper } from 'vs/base/browser/dom'; - -export class ProgressService2 implements IProgressService2 { - - _serviceBrand: any; - - private readonly _stack: [IProgressOptions, Progress<IProgressStep>][] = []; - private _globalStatusEntry: IDisposable; - - constructor( - @IActivityService private readonly _activityBar: IActivityService, - @IViewletService private readonly _viewletService: IViewletService, - @INotificationService private readonly _notificationService: INotificationService, - @IStatusbarService private readonly _statusbarService: IStatusbarService, - @ILayoutService private readonly _layoutService: ILayoutService, - @IThemeService private readonly _themeService: IThemeService, - @IKeybindingService private readonly _keybindingService: IKeybindingService - ) { } - - withProgress<R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: () => void): Promise<R> { - - const { location } = options; - if (typeof location === 'string') { - const viewlet = this._viewletService.getViewlet(location); - if (viewlet) { - return this._withViewletProgress(location, task); - } - return Promise.reject(new Error(`Bad progress location: ${location}`)); - } - - switch (location) { - case ProgressLocation.Notification: - return this._withNotificationProgress({ ...options, location: ProgressLocation.Notification }, task, onDidCancel); - case ProgressLocation.Window: - return this._withWindowProgress(options, task); - case ProgressLocation.Explorer: - return this._withViewletProgress('workbench.view.explorer', task); - case ProgressLocation.Scm: - return this._withViewletProgress('workbench.view.scm', task); - case ProgressLocation.Extensions: - return this._withViewletProgress('workbench.view.extensions', task); - case ProgressLocation.Dialog: - return this._withDialogProgress(options, task, onDidCancel); - default: - return Promise.reject(new Error(`Bad progress location: ${location}`)); - } - } - - private _withWindowProgress<R = unknown>(options: IProgressOptions, callback: (progress: IProgress<{ message?: string }>) => Promise<R>): Promise<R> { - - const task: [IProgressOptions, Progress<IProgressStep>] = [options, new Progress<IProgressStep>(() => this._updateWindowProgress())]; - - const promise = callback(task[1]); - - let delayHandle: any = setTimeout(() => { - delayHandle = undefined; - this._stack.unshift(task); - this._updateWindowProgress(); - - // show progress for at least 150ms - Promise.all([ - timeout(150), - promise - ]).finally(() => { - const idx = this._stack.indexOf(task); - this._stack.splice(idx, 1); - this._updateWindowProgress(); - }); - - }, 150); - - // cancel delay if promise finishes below 150ms - return promise.finally(() => clearTimeout(delayHandle)); - } - - private _updateWindowProgress(idx: number = 0) { - - dispose(this._globalStatusEntry); - - if (idx < this._stack.length) { - - const [options, progress] = this._stack[idx]; - - let progressTitle = options.title; - let progressMessage = progress.value && progress.value.message; - let text: string; - let title: string; - - if (progressTitle && progressMessage) { - // <title>: <message> - text = localize('progress.text2', "{0}: {1}", progressTitle, progressMessage); - title = options.source ? localize('progress.title3', "[{0}] {1}: {2}", options.source, progressTitle, progressMessage) : text; - - } else if (progressTitle) { - // <title> - text = progressTitle; - title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressTitle) : text; - - } else if (progressMessage) { - // <message> - text = progressMessage; - title = options.source ? localize('progress.title2', "[{0}]: {1}", options.source, progressMessage) : text; - - } else { - // no title, no message -> no progress. try with next on stack - this._updateWindowProgress(idx + 1); - return; - } - - this._globalStatusEntry = this._statusbarService.addEntry({ - text: `$(sync~spin) ${text}`, - tooltip: title - }, StatusbarAlignment.LEFT); - } - } - - private _withNotificationProgress<P extends Promise<R>, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { - const toDispose: IDisposable[] = []; - - const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => { - if (!message) { - return undefined; // we need a message at least - } - - const actions: INotificationActions = { primary: options.primaryActions || [], secondary: options.secondaryActions || [] }; - if (options.cancellable) { - const cancelAction = new class extends Action { - constructor() { - super('progress.cancel', localize('cancel', "Cancel"), undefined, true); - } - - run(): Promise<any> { - if (typeof onDidCancel === 'function') { - onDidCancel(); - } - - return Promise.resolve(undefined); - } - }; - toDispose.push(cancelAction); - - actions.primary!.push(cancelAction); - } - - const handle = this._notificationService.notify({ - severity: Severity.Info, - message, - source: options.source, - actions - }); - - updateProgress(handle, increment); - - Event.once(handle.onDidClose)(() => { - dispose(toDispose); - }); - - return handle; - }; - - const updateProgress = (notification: INotificationHandle, increment?: number): void => { - if (typeof increment === 'number' && increment >= 0) { - notification.progress.total(100); // always percentage based - notification.progress.worked(increment); - } else { - notification.progress.infinite(); - } - }; - - let handle: INotificationHandle | undefined; - const updateNotification = (message?: string, increment?: number): void => { - if (!handle) { - handle = createNotification(message, increment); - } else { - if (typeof message === 'string') { - let newMessage: string; - if (typeof options.title === 'string') { - newMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) - } else { - newMessage = message; - } - - handle.updateMessage(newMessage); - } - - if (typeof increment === 'number') { - updateProgress(handle, increment); - } - } - }; - - // Show initially - updateNotification(options.title); - - // Update based on progress - const p = callback({ - report: progress => { - updateNotification(progress.message, progress.increment); - } - }); - - // Show progress for at least 800ms and then hide once done or canceled - Promise.all([timeout(800), p]).finally(() => { - if (handle) { - handle.close(); - } - }); - - return p; - } - - private _withViewletProgress<P extends Promise<R>, R = unknown>(viewletId: string, task: (progress: IProgress<{ message?: string }>) => P): P { - - const promise = task(emptyProgress); - - // show in viewlet - const viewletProgress = this._viewletService.getProgressIndicator(viewletId); - if (viewletProgress) { - viewletProgress.showWhile(promise); - } - - // show activity bar - let activityProgress: IDisposable; - let delayHandle: any = setTimeout(() => { - delayHandle = undefined; - const handle = this._activityBar.showActivity( - viewletId, - new ProgressBadge(() => ''), - 'progress-badge', - 100 - ); - const startTimeVisible = Date.now(); - const minTimeVisible = 300; - activityProgress = { - dispose() { - const d = Date.now() - startTimeVisible; - if (d < minTimeVisible) { - // should at least show for Nms - setTimeout(() => handle.dispose(), minTimeVisible - d); - } else { - // shown long enough - handle.dispose(); - } - } - }; - }, 300); - - const onDone = () => { - clearTimeout(delayHandle); - dispose(activityProgress); - }; - - promise.then(onDone, onDone); - return promise; - } - - private _withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { - const disposables: IDisposable[] = []; - const allowableCommands = [ - 'workbench.action.quit', - 'workbench.action.reloadWindow' - ]; - - let dialog: Dialog; - - const createDialog = (message: string) => { - dialog = new Dialog( - this._layoutService.container, - message, - [options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")], - { - type: 'pending', - keyEventProcessor: (event: StandardKeyboardEvent) => { - const resolved = this._keybindingService.softDispatch(event, this._layoutService.container); - if (resolved && resolved.commandId) { - if (allowableCommands.indexOf(resolved.commandId) === -1) { - EventHelper.stop(event, true); - } - } - } - } - ); - - disposables.push(dialog); - disposables.push(attachDialogStyler(dialog, this._themeService)); - - dialog.show().then(() => { - if (typeof onDidCancel === 'function') { - onDidCancel(); - } - - dispose(dialog); - }); - - return dialog; - }; - - const updateDialog = (message?: string) => { - if (message && !dialog) { - dialog = createDialog(message); - } else if (message) { - dialog.updateMessage(message); - } - }; - - const p = task({ - report: progress => { - updateDialog(progress.message); - } - }); - - p.finally(() => { - dispose(disposables); - }); - - return p; - } -} - -registerSingleton(IProgressService2, ProgressService2, true); diff --git a/src/vs/workbench/services/progress/test/progressService.test.ts b/src/vs/workbench/services/progress/test/localProgressService.test.ts similarity index 99% rename from src/vs/workbench/services/progress/test/progressService.test.ts rename to src/vs/workbench/services/progress/test/localProgressService.test.ts index f0dc396eae52..367d6c92ac98 100644 --- a/src/vs/workbench/services/progress/test/progressService.test.ts +++ b/src/vs/workbench/services/progress/test/localProgressService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { IAction, IActionViewItem } from 'vs/base/common/actions'; import { IEditorControl } from 'vs/workbench/common/editor'; -import { ScopedProgressService, ScopedService } from 'vs/workbench/services/progress/browser/progressService'; +import { ScopedProgressService, ScopedService } from 'vs/workbench/services/progress/browser/localProgressService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewlet } from 'vs/workbench/common/viewlet'; diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index e39184d32496..994c32f1e809 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -13,7 +13,6 @@ import { keys, ResourceMap, values } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI as uri } from 'vs/base/common/uri'; -import * as pfs from 'vs/base/node/pfs'; import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -425,6 +424,7 @@ export class DiskSearch implements ISearchResultProvider { searchDebug: IDebugParams | undefined, @ILogService private readonly logService: ILogService, @IConfigurationService private readonly configService: IConfigurationService, + @IFileService private readonly fileService: IFileService ) { const timeout = this.configService.getValue<ISearchConfiguration>().search.maintainFileSearchCache ? Number.MAX_VALUE : @@ -465,7 +465,7 @@ export class DiskSearch implements ISearchResultProvider { textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): Promise<ISearchComplete> { const folderQueries = query.folderQueries || []; - return Promise.all(folderQueries.map(q => q.folder.scheme === Schemas.file && pfs.exists(q.folder.fsPath))) + return Promise.all(folderQueries.map(q => this.fileService.exists(q.folder))) .then(exists => { if (token && token.isCancellationRequested) { throw canceled(); @@ -480,7 +480,7 @@ export class DiskSearch implements ISearchResultProvider { fileSearch(query: IFileQuery, token?: CancellationToken): Promise<ISearchComplete> { const folderQueries = query.folderQueries || []; - return Promise.all(folderQueries.map(q => q.folder.scheme === Schemas.file && pfs.exists(q.folder.fsPath))) + return Promise.all(folderQueries.map(q => this.fileService.exists(q.folder))) .then(exists => { if (token && token.isCancellationRequested) { throw canceled(); diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 6e25f6ffd3de..aa47e093f124 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -37,7 +37,7 @@ export class TelemetryService extends Disposable implements ITelemetryService { const channel = sharedProcessService.getChannel('telemetryAppender'); const config: ITelemetryServiceConfig = { appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.installSourcePath), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), piiPaths: [environmentService.appRoot, environmentService.extensionsPath] }; diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts new file mode 100644 index 000000000000..6f535e52e175 --- /dev/null +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -0,0 +1,519 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { Color } from 'vs/base/common/color'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; +import * as resources from 'vs/base/common/resources'; +import * as types from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/token'; +import { IState, ITokenizationSupport, LanguageId, TokenMetadata, TokenizationRegistry } from 'vs/editor/common/modes'; +import { nullTokenize2 } from 'vs/editor/common/modes/nullMode'; +import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint, TokenTypesContribution, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; +import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; +import { ITokenColorizationRule, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2, IGrammar, ITokenTypeMap, Registry, StackElement, StandardTokenType, RegistryOptions, IRawGrammar } from 'vscode-textmate'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +export class TMScopeRegistry extends Disposable { + + private _scopeNameToLanguageRegistration: { [scopeName: string]: TMLanguageRegistration; }; + private _encounteredLanguages: boolean[]; + + private readonly _onDidEncounterLanguage = this._register(new Emitter<LanguageId>()); + public readonly onDidEncounterLanguage: Event<LanguageId> = this._onDidEncounterLanguage.event; + + constructor() { + super(); + this.reset(); + } + + public reset(): void { + this._scopeNameToLanguageRegistration = Object.create(null); + this._encounteredLanguages = []; + } + + public register(scopeName: string, grammarLocation: URI, embeddedLanguages?: IEmbeddedLanguagesMap, tokenTypes?: TokenTypesContribution): void { + if (this._scopeNameToLanguageRegistration[scopeName]) { + const existingRegistration = this._scopeNameToLanguageRegistration[scopeName]; + if (!resources.isEqual(existingRegistration.grammarLocation, grammarLocation)) { + console.warn( + `Overwriting grammar scope name to file mapping for scope ${scopeName}.\n` + + `Old grammar file: ${existingRegistration.grammarLocation.toString()}.\n` + + `New grammar file: ${grammarLocation.toString()}` + ); + } + } + this._scopeNameToLanguageRegistration[scopeName] = new TMLanguageRegistration(scopeName, grammarLocation, embeddedLanguages, tokenTypes); + } + + public getLanguageRegistration(scopeName: string): TMLanguageRegistration { + return this._scopeNameToLanguageRegistration[scopeName] || null; + } + + public getGrammarLocation(scopeName: string): URI | null { + let data = this.getLanguageRegistration(scopeName); + return data ? data.grammarLocation : null; + } + + /** + * To be called when tokenization found/hit an embedded language. + */ + public onEncounteredLanguage(languageId: LanguageId): void { + if (!this._encounteredLanguages[languageId]) { + this._encounteredLanguages[languageId] = true; + this._onDidEncounterLanguage.fire(languageId); + } + } +} + +export class TMLanguageRegistration { + _topLevelScopeNameDataBrand: void; + + readonly scopeName: string; + readonly grammarLocation: URI; + readonly embeddedLanguages: IEmbeddedLanguagesMap; + readonly tokenTypes: ITokenTypeMap; + + constructor(scopeName: string, grammarLocation: URI, embeddedLanguages: IEmbeddedLanguagesMap | undefined, tokenTypes: TokenTypesContribution | undefined) { + this.scopeName = scopeName; + this.grammarLocation = grammarLocation; + + // embeddedLanguages handling + this.embeddedLanguages = Object.create(null); + + if (embeddedLanguages) { + // If embeddedLanguages are configured, fill in `this._embeddedLanguages` + let scopes = Object.keys(embeddedLanguages); + for (let i = 0, len = scopes.length; i < len; i++) { + let scope = scopes[i]; + let language = embeddedLanguages[scope]; + if (typeof language !== 'string') { + // never hurts to be too careful + continue; + } + this.embeddedLanguages[scope] = language; + } + } + + this.tokenTypes = Object.create(null); + if (tokenTypes) { + // If tokenTypes is configured, fill in `this._tokenTypes` + const scopes = Object.keys(tokenTypes); + for (const scope of scopes) { + const tokenType = tokenTypes[scope]; + switch (tokenType) { + case 'string': + this.tokenTypes[scope] = StandardTokenType.String; + break; + case 'other': + this.tokenTypes[scope] = StandardTokenType.Other; + break; + case 'comment': + this.tokenTypes[scope] = StandardTokenType.Comment; + break; + } + } + } + } +} + +interface ICreateGrammarResult { + languageId: LanguageId; + grammar: IGrammar; + initialState: StackElement; + containsEmbeddedLanguages: boolean; +} + +export abstract class AbstractTextMateService extends Disposable implements ITextMateService { + public _serviceBrand: any; + + private readonly _onDidEncounterLanguage: Emitter<LanguageId> = this._register(new Emitter<LanguageId>()); + public readonly onDidEncounterLanguage: Event<LanguageId> = this._onDidEncounterLanguage.event; + + private readonly _styleElement: HTMLStyleElement; + private readonly _createdModes: string[]; + + protected _scopeRegistry: TMScopeRegistry; + private _injections: { [scopeName: string]: string[]; }; + private _injectedEmbeddedLanguages: { [scopeName: string]: IEmbeddedLanguagesMap[]; }; + protected _languageToScope: Map<string, string>; + private _grammarRegistry: Promise<[Registry, StackElement]> | null; + private _tokenizersRegistrations: IDisposable[]; + private _currentTokenColors: ITokenColorizationRule[] | null; + private _themeListener: IDisposable | null; + + constructor( + @IModeService private readonly _modeService: IModeService, + @IWorkbenchThemeService private readonly _themeService: IWorkbenchThemeService, + @IFileService private readonly _fileService: IFileService, + @INotificationService private readonly _notificationService: INotificationService, + @ILogService private readonly _logService: ILogService, + @IConfigurationService private readonly _configurationService: IConfigurationService + ) { + super(); + this._styleElement = dom.createStyleSheet(); + this._styleElement.className = 'vscode-tokens-styles'; + this._createdModes = []; + this._scopeRegistry = new TMScopeRegistry(); + this._scopeRegistry.onDidEncounterLanguage((language) => this._onDidEncounterLanguage.fire(language)); + this._injections = {}; + this._injectedEmbeddedLanguages = {}; + this._languageToScope = new Map<string, string>(); + this._grammarRegistry = null; + this._tokenizersRegistrations = []; + this._currentTokenColors = null; + this._themeListener = null; + + grammarsExtPoint.setHandler((extensions) => { + this._scopeRegistry.reset(); + this._injections = {}; + this._injectedEmbeddedLanguages = {}; + this._languageToScope = new Map<string, string>(); + this._grammarRegistry = null; + this._tokenizersRegistrations = dispose(this._tokenizersRegistrations); + this._currentTokenColors = null; + if (this._themeListener) { + this._themeListener.dispose(); + this._themeListener = null; + } + + for (const extension of extensions) { + let grammars = extension.value; + for (const grammar of grammars) { + this._handleGrammarExtensionPointUser(extension.description.extensionLocation, grammar, extension.collector); + } + } + + for (const createMode of this._createdModes) { + this._registerDefinitionIfAvailable(createMode); + } + }); + + // Generate some color map until the grammar registry is loaded + let colorTheme = this._themeService.getColorTheme(); + let defaultForeground: Color = Color.transparent; + let defaultBackground: Color = Color.transparent; + for (let i = 0, len = colorTheme.tokenColors.length; i < len; i++) { + let rule = colorTheme.tokenColors[i]; + if (!rule.scope && rule.settings) { + if (rule.settings.foreground) { + defaultForeground = Color.fromHex(rule.settings.foreground); + } + if (rule.settings.background) { + defaultBackground = Color.fromHex(rule.settings.background); + } + } + } + TokenizationRegistry.setColorMap([null!, defaultForeground, defaultBackground]); + + this._modeService.onDidCreateMode((mode) => { + let modeId = mode.getId(); + this._createdModes.push(modeId); + this._registerDefinitionIfAvailable(modeId); + }); + } + + private _registerDefinitionIfAvailable(modeId: string): void { + if (this._languageToScope.has(modeId)) { + const promise = this._createGrammar(modeId).then((r) => { + return new TMTokenization(this._scopeRegistry, r.languageId, r.grammar, r.initialState, r.containsEmbeddedLanguages, this._notificationService, this._configurationService); + }, e => { + onUnexpectedError(e); + return null; + }); + this._tokenizersRegistrations.push(TokenizationRegistry.registerPromise(modeId, promise)); + } + } + + protected _getRegistryOptions(parseRawGrammar: (content: string, filePath: string) => IRawGrammar): RegistryOptions { + return { + loadGrammar: async (scopeName: string) => { + const location = this._scopeRegistry.getGrammarLocation(scopeName); + if (!location) { + this._logService.trace(`No grammar found for scope ${scopeName}`); + return null; + } + try { + const content = await this._fileService.readFile(location); + return parseRawGrammar(content.value.toString(), location.path); + } catch (e) { + this._logService.error(`Unable to load and parse grammar for scope ${scopeName} from ${location}`, e); + return null; + } + }, + getInjections: (scopeName: string) => { + const scopeParts = scopeName.split('.'); + let injections: string[] = []; + for (let i = 1; i <= scopeParts.length; i++) { + const subScopeName = scopeParts.slice(0, i).join('.'); + injections = [...injections, ...(this._injections[subScopeName] || [])]; + } + return injections; + } + }; + } + + private async _createGrammarRegistry(): Promise<[Registry, StackElement]> { + const { Registry, INITIAL, parseRawGrammar } = await this._loadVSCodeTextmate(); + const grammarRegistry = new Registry(this._getRegistryOptions(parseRawGrammar)); + this._updateTheme(grammarRegistry); + this._themeListener = this._themeService.onDidColorThemeChange((e) => this._updateTheme(grammarRegistry)); + return <[Registry, StackElement]>[grammarRegistry, INITIAL]; + } + + private _getOrCreateGrammarRegistry(): Promise<[Registry, StackElement]> { + if (!this._grammarRegistry) { + this._grammarRegistry = this._createGrammarRegistry(); + } + return this._grammarRegistry; + } + + private static _toColorMap(colorMap: string[]): Color[] { + let result: Color[] = [null!]; + for (let i = 1, len = colorMap.length; i < len; i++) { + result[i] = Color.fromHex(colorMap[i]); + } + return result; + } + + private _updateTheme(grammarRegistry: Registry): void { + let colorTheme = this._themeService.getColorTheme(); + if (!this.compareTokenRules(colorTheme.tokenColors)) { + return; + } + grammarRegistry.setTheme({ name: colorTheme.label, settings: colorTheme.tokenColors }); + let colorMap = AbstractTextMateService._toColorMap(grammarRegistry.getColorMap()); + let cssRules = generateTokensCSSForColorMap(colorMap); + this._styleElement.innerHTML = cssRules; + TokenizationRegistry.setColorMap(colorMap); + } + + private compareTokenRules(newRules: ITokenColorizationRule[]): boolean { + let currRules = this._currentTokenColors; + this._currentTokenColors = newRules; + if (!newRules || !currRules || newRules.length !== currRules.length) { + return true; + } + for (let i = newRules.length - 1; i >= 0; i--) { + let r1 = newRules[i]; + let r2 = currRules[i]; + if (r1.scope !== r2.scope) { + return true; + } + let s1 = r1.settings; + let s2 = r2.settings; + if (s1 && s2) { + if (s1.fontStyle !== s2.fontStyle || s1.foreground !== s2.foreground || s1.background !== s2.background) { + return true; + } + } else if (!s1 || !s2) { + return true; + } + } + return false; + } + + private _handleGrammarExtensionPointUser(extensionLocation: URI, syntax: ITMSyntaxExtensionPoint, collector: ExtensionMessageCollector): void { + if (syntax.language && ((typeof syntax.language !== 'string') || !this._modeService.isRegisteredMode(syntax.language))) { + collector.error(nls.localize('invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", grammarsExtPoint.name, String(syntax.language))); + return; + } + if (!syntax.scopeName || (typeof syntax.scopeName !== 'string')) { + collector.error(nls.localize('invalid.scopeName', "Expected string in `contributes.{0}.scopeName`. Provided value: {1}", grammarsExtPoint.name, String(syntax.scopeName))); + return; + } + if (!syntax.path || (typeof syntax.path !== 'string')) { + collector.error(nls.localize('invalid.path.0', "Expected string in `contributes.{0}.path`. Provided value: {1}", grammarsExtPoint.name, String(syntax.path))); + return; + } + if (syntax.injectTo && (!Array.isArray(syntax.injectTo) || syntax.injectTo.some(scope => typeof scope !== 'string'))) { + collector.error(nls.localize('invalid.injectTo', "Invalid value in `contributes.{0}.injectTo`. Must be an array of language scope names. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.injectTo))); + return; + } + if (syntax.embeddedLanguages && !types.isObject(syntax.embeddedLanguages)) { + collector.error(nls.localize('invalid.embeddedLanguages', "Invalid value in `contributes.{0}.embeddedLanguages`. Must be an object map from scope name to language. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.embeddedLanguages))); + return; + } + + if (syntax.tokenTypes && !types.isObject(syntax.tokenTypes)) { + collector.error(nls.localize('invalid.tokenTypes', "Invalid value in `contributes.{0}.tokenTypes`. Must be an object map from scope name to token type. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.tokenTypes))); + return; + } + + const grammarLocation = resources.joinPath(extensionLocation, syntax.path); + if (!resources.isEqualOrParent(grammarLocation, extensionLocation)) { + collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", grammarsExtPoint.name, grammarLocation.path, extensionLocation.path)); + } + + this._scopeRegistry.register(syntax.scopeName, grammarLocation, syntax.embeddedLanguages, syntax.tokenTypes); + + if (syntax.injectTo) { + for (let injectScope of syntax.injectTo) { + let injections = this._injections[injectScope]; + if (!injections) { + this._injections[injectScope] = injections = []; + } + injections.push(syntax.scopeName); + } + + if (syntax.embeddedLanguages) { + for (let injectScope of syntax.injectTo) { + let injectedEmbeddedLanguages = this._injectedEmbeddedLanguages[injectScope]; + if (!injectedEmbeddedLanguages) { + this._injectedEmbeddedLanguages[injectScope] = injectedEmbeddedLanguages = []; + } + injectedEmbeddedLanguages.push(syntax.embeddedLanguages); + } + } + } + + let modeId = syntax.language; + if (modeId) { + this._languageToScope.set(modeId, syntax.scopeName); + } + } + + private _resolveEmbeddedLanguages(embeddedLanguages: IEmbeddedLanguagesMap): IEmbeddedLanguagesMap2 { + let scopes = Object.keys(embeddedLanguages); + let result: IEmbeddedLanguagesMap2 = Object.create(null); + for (let i = 0, len = scopes.length; i < len; i++) { + let scope = scopes[i]; + let language = embeddedLanguages[scope]; + let languageIdentifier = this._modeService.getLanguageIdentifier(language); + if (languageIdentifier) { + result[scope] = languageIdentifier.id; + } + } + return result; + } + + public async createGrammar(modeId: string): Promise<IGrammar> { + const { grammar } = await this._createGrammar(modeId); + return grammar; + } + + private async _createGrammar(modeId: string): Promise<ICreateGrammarResult> { + const scopeName = this._languageToScope.get(modeId); + if (typeof scopeName !== 'string') { + // No TM grammar defined + return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language."))); + } + const languageRegistration = this._scopeRegistry.getLanguageRegistration(scopeName); + if (!languageRegistration) { + // No TM grammar defined + return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language."))); + } + let embeddedLanguages = this._resolveEmbeddedLanguages(languageRegistration.embeddedLanguages); + let rawInjectedEmbeddedLanguages = this._injectedEmbeddedLanguages[scopeName]; + if (rawInjectedEmbeddedLanguages) { + let injectedEmbeddedLanguages: IEmbeddedLanguagesMap2[] = rawInjectedEmbeddedLanguages.map(this._resolveEmbeddedLanguages.bind(this)); + for (const injected of injectedEmbeddedLanguages) { + for (const scope of Object.keys(injected)) { + embeddedLanguages[scope] = injected[scope]; + } + } + } + + let languageId = this._modeService.getLanguageIdentifier(modeId)!.id; + let containsEmbeddedLanguages = (Object.keys(embeddedLanguages).length > 0); + + const [grammarRegistry, initialState] = await this._getOrCreateGrammarRegistry(); + const grammar = await grammarRegistry.loadGrammarWithConfiguration(scopeName, languageId, { embeddedLanguages, tokenTypes: languageRegistration.tokenTypes }); + return { + languageId: languageId, + grammar: grammar, + initialState: initialState, + containsEmbeddedLanguages: containsEmbeddedLanguages + }; + } + + protected abstract _loadVSCodeTextmate(): Promise<typeof import('vscode-textmate')>; +} + +class TMTokenization implements ITokenizationSupport { + + private readonly _scopeRegistry: TMScopeRegistry; + private readonly _languageId: LanguageId; + private readonly _grammar: IGrammar; + private readonly _containsEmbeddedLanguages: boolean; + private readonly _seenLanguages: boolean[]; + private readonly _initialState: StackElement; + private _maxTokenizationLineLength: number; + private _tokenizationWarningAlreadyShown: boolean; + + constructor(scopeRegistry: TMScopeRegistry, languageId: LanguageId, grammar: IGrammar, initialState: StackElement, containsEmbeddedLanguages: boolean, @INotificationService private readonly notificationService: INotificationService, @IConfigurationService readonly configurationService: IConfigurationService) { + this._scopeRegistry = scopeRegistry; + this._languageId = languageId; + this._grammar = grammar; + this._initialState = initialState; + this._containsEmbeddedLanguages = containsEmbeddedLanguages; + this._seenLanguages = []; + this._maxTokenizationLineLength = configurationService.getValue<number>('editor.maxTokenizationLineLength'); + } + + public getInitialState(): IState { + return this._initialState; + } + + public tokenize(line: string, state: IState, offsetDelta: number): TokenizationResult { + throw new Error('Not supported!'); + } + + public tokenize2(line: string, state: StackElement, offsetDelta: number): TokenizationResult2 { + if (offsetDelta !== 0) { + throw new Error('Unexpected: offsetDelta should be 0.'); + } + + // Do not attempt to tokenize if a line is too long + if (line.length >= this._maxTokenizationLineLength) { + if (!this._tokenizationWarningAlreadyShown) { + this._tokenizationWarningAlreadyShown = true; + this.notificationService.warn(nls.localize('too many characters', "Tokenization is skipped for long lines for performance reasons. The length of a long line can be configured via `editor.maxTokenizationLineLength`.")); + } + console.log(`Line (${line.substr(0, 15)}...): longer than ${this._maxTokenizationLineLength} characters, tokenization skipped.`); + return nullTokenize2(this._languageId, line, state, offsetDelta); + } + + let textMateResult = this._grammar.tokenizeLine2(line, state); + + if (this._containsEmbeddedLanguages) { + let seenLanguages = this._seenLanguages; + let tokens = textMateResult.tokens; + + // Must check if any of the embedded languages was hit + for (let i = 0, len = (tokens.length >>> 1); i < len; i++) { + let metadata = tokens[(i << 1) + 1]; + let languageId = TokenMetadata.getLanguageId(metadata); + + if (!seenLanguages[languageId]) { + seenLanguages[languageId] = true; + this._scopeRegistry.onEncounteredLanguage(languageId); + } + } + } + + let endState: StackElement; + // try to save an object if possible + if (state.equals(textMateResult.ruleStack)) { + endState = state; + } else { + endState = textMateResult.ruleStack; + + } + + return new TokenizationResult2(textMateResult.tokens, endState); + } +} diff --git a/src/vs/workbench/services/textMate/browser/textMateService.ts b/src/vs/workbench/services/textMate/browser/textMateService.ts new file mode 100644 index 000000000000..b6bc58c24a7c --- /dev/null +++ b/src/vs/workbench/services/textMate/browser/textMateService.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService'; +import * as vscodeTextmate from 'vscode-textmate'; +import * as onigasm from 'onigasm-umd'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +export class TextMateService extends AbstractTextMateService { + + constructor( + @IModeService modeService: IModeService, + @IWorkbenchThemeService themeService: IWorkbenchThemeService, + @IFileService fileService: IFileService, + @INotificationService notificationService: INotificationService, + @ILogService logService: ILogService, + @IConfigurationService configurationService: IConfigurationService + ) { + super(modeService, themeService, fileService, notificationService, logService, configurationService); + } + + protected _loadVSCodeTextmate(): Promise<typeof import('vscode-textmate')> { + return import('vscode-textmate'); + } + + protected _getRegistryOptions(parseRawGrammar: (content: string, filePath: string) => vscodeTextmate.IRawGrammar): vscodeTextmate.RegistryOptions { + const result = super._getRegistryOptions(parseRawGrammar); + result.getOnigLib = () => loadOnigasm(); + return result; + } +} + +let onigasmPromise: Promise<vscodeTextmate.IOnigLib> | null = null; +async function loadOnigasm(): Promise<vscodeTextmate.IOnigLib> { + if (!onigasmPromise) { + onigasmPromise = doLoadOnigasm(); + } + return onigasmPromise; +} + +async function doLoadOnigasm(): Promise<vscodeTextmate.IOnigLib> { + const wasmBytes = await loadOnigasmWASM(); + await onigasm.loadWASM(wasmBytes); + return { + createOnigScanner(patterns: string[]) { return new onigasm.OnigScanner(patterns); }, + createOnigString(s: string) { return new onigasm.OnigString(s); } + }; +} + +async function loadOnigasmWASM(): Promise<ArrayBuffer> { + const wasmPath = require.toUrl('onigasm-umd/../onigasm.wasm'); + const response = await fetch(wasmPath); + const bytes = await response.arrayBuffer(); + return bytes; +} + +registerSingleton(ITextMateService, TextMateService); diff --git a/src/vs/workbench/services/textMate/electron-browser/cgmanifest.json b/src/vs/workbench/services/textMate/common/cgmanifest.json similarity index 100% rename from src/vs/workbench/services/textMate/electron-browser/cgmanifest.json rename to src/vs/workbench/services/textMate/common/cgmanifest.json diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts index 0d8a3b776963..5ac94859a879 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts @@ -3,512 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import * as dom from 'vs/base/browser/dom'; -import { Color } from 'vs/base/common/color'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { Emitter, Event } from 'vs/base/common/event'; -import * as resources from 'vs/base/common/resources'; -import * as types from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; -import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/token'; -import { IState, ITokenizationSupport, LanguageId, TokenMetadata, TokenizationRegistry } from 'vs/editor/common/modes'; -import { nullTokenize2 } from 'vs/editor/common/modes/nullMode'; -import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint, TokenTypesContribution, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; -import { ITokenColorizationRule, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2, IGrammar, ITokenTypeMap, Registry, StackElement, StandardTokenType } from 'vscode-textmate'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService'; -export class TMScopeRegistry { +export class TextMateService extends AbstractTextMateService { - private _scopeNameToLanguageRegistration: { [scopeName: string]: TMLanguageRegistration; }; - private _encounteredLanguages: boolean[]; - - private readonly _onDidEncounterLanguage = new Emitter<LanguageId>(); - public readonly onDidEncounterLanguage: Event<LanguageId> = this._onDidEncounterLanguage.event; - - constructor() { - this.reset(); - } - - public reset(): void { - this._scopeNameToLanguageRegistration = Object.create(null); - this._encounteredLanguages = []; - } - - public register(scopeName: string, grammarLocation: URI, embeddedLanguages?: IEmbeddedLanguagesMap, tokenTypes?: TokenTypesContribution): void { - if (this._scopeNameToLanguageRegistration[scopeName]) { - const existingRegistration = this._scopeNameToLanguageRegistration[scopeName]; - if (!resources.isEqual(existingRegistration.grammarLocation, grammarLocation)) { - console.warn( - `Overwriting grammar scope name to file mapping for scope ${scopeName}.\n` + - `Old grammar file: ${existingRegistration.grammarLocation.toString()}.\n` + - `New grammar file: ${grammarLocation.toString()}` - ); - } - } - this._scopeNameToLanguageRegistration[scopeName] = new TMLanguageRegistration(scopeName, grammarLocation, embeddedLanguages, tokenTypes); - } - - public getLanguageRegistration(scopeName: string): TMLanguageRegistration { - return this._scopeNameToLanguageRegistration[scopeName] || null; - } - - public getGrammarLocation(scopeName: string): URI | null { - let data = this.getLanguageRegistration(scopeName); - return data ? data.grammarLocation : null; - } - - /** - * To be called when tokenization found/hit an embedded language. - */ - public onEncounteredLanguage(languageId: LanguageId): void { - if (!this._encounteredLanguages[languageId]) { - this._encounteredLanguages[languageId] = true; - this._onDidEncounterLanguage.fire(languageId); - } - } -} - -export class TMLanguageRegistration { - _topLevelScopeNameDataBrand: void; - - readonly scopeName: string; - readonly grammarLocation: URI; - readonly embeddedLanguages: IEmbeddedLanguagesMap; - readonly tokenTypes: ITokenTypeMap; - - constructor(scopeName: string, grammarLocation: URI, embeddedLanguages: IEmbeddedLanguagesMap | undefined, tokenTypes: TokenTypesContribution | undefined) { - this.scopeName = scopeName; - this.grammarLocation = grammarLocation; - - // embeddedLanguages handling - this.embeddedLanguages = Object.create(null); - - if (embeddedLanguages) { - // If embeddedLanguages are configured, fill in `this._embeddedLanguages` - let scopes = Object.keys(embeddedLanguages); - for (let i = 0, len = scopes.length; i < len; i++) { - let scope = scopes[i]; - let language = embeddedLanguages[scope]; - if (typeof language !== 'string') { - // never hurts to be too careful - continue; - } - this.embeddedLanguages[scope] = language; - } - } - - this.tokenTypes = Object.create(null); - if (tokenTypes) { - // If tokenTypes is configured, fill in `this._tokenTypes` - const scopes = Object.keys(tokenTypes); - for (const scope of scopes) { - const tokenType = tokenTypes[scope]; - switch (tokenType) { - case 'string': - this.tokenTypes[scope] = StandardTokenType.String; - break; - case 'other': - this.tokenTypes[scope] = StandardTokenType.Other; - break; - case 'comment': - this.tokenTypes[scope] = StandardTokenType.Comment; - break; - } - } - } - } -} - -interface ICreateGrammarResult { - languageId: LanguageId; - grammar: IGrammar; - initialState: StackElement; - containsEmbeddedLanguages: boolean; -} - -export class TextMateService extends Disposable implements ITextMateService { - public _serviceBrand: any; - - private readonly _onDidEncounterLanguage: Emitter<LanguageId> = this._register(new Emitter<LanguageId>()); - public readonly onDidEncounterLanguage: Event<LanguageId> = this._onDidEncounterLanguage.event; - - private readonly _styleElement: HTMLStyleElement; - private readonly _createdModes: string[]; - - private _scopeRegistry: TMScopeRegistry; - private _injections: { [scopeName: string]: string[]; }; - private _injectedEmbeddedLanguages: { [scopeName: string]: IEmbeddedLanguagesMap[]; }; - private _languageToScope: Map<string, string>; - private _grammarRegistry: Promise<[Registry, StackElement]> | null; - private _tokenizersRegistrations: IDisposable[]; - private _currentTokenColors: ITokenColorizationRule[] | null; - private _themeListener: IDisposable | null; - - constructor( - @IModeService private readonly _modeService: IModeService, - @IWorkbenchThemeService private readonly _themeService: IWorkbenchThemeService, - @IFileService private readonly _fileService: IFileService, - @INotificationService private readonly _notificationService: INotificationService, - @ILogService private readonly _logService: ILogService, - @IConfigurationService private readonly _configurationService: IConfigurationService - ) { - super(); - this._styleElement = dom.createStyleSheet(); - this._styleElement.className = 'vscode-tokens-styles'; - this._createdModes = []; - this._scopeRegistry = new TMScopeRegistry(); - this._scopeRegistry.onDidEncounterLanguage((language) => this._onDidEncounterLanguage.fire(language)); - this._injections = {}; - this._injectedEmbeddedLanguages = {}; - this._languageToScope = new Map<string, string>(); - this._grammarRegistry = null; - this._tokenizersRegistrations = []; - this._currentTokenColors = null; - this._themeListener = null; - - grammarsExtPoint.setHandler((extensions) => { - this._scopeRegistry.reset(); - this._injections = {}; - this._injectedEmbeddedLanguages = {}; - this._languageToScope = new Map<string, string>(); - this._grammarRegistry = null; - this._tokenizersRegistrations = dispose(this._tokenizersRegistrations); - this._currentTokenColors = null; - if (this._themeListener) { - this._themeListener.dispose(); - this._themeListener = null; - } - - for (const extension of extensions) { - let grammars = extension.value; - for (const grammar of grammars) { - this._handleGrammarExtensionPointUser(extension.description.extensionLocation, grammar, extension.collector); - } - } - - for (const createMode of this._createdModes) { - this._registerDefinitionIfAvailable(createMode); - } - }); - - // Generate some color map until the grammar registry is loaded - let colorTheme = this._themeService.getColorTheme(); - let defaultForeground: Color = Color.transparent; - let defaultBackground: Color = Color.transparent; - for (let i = 0, len = colorTheme.tokenColors.length; i < len; i++) { - let rule = colorTheme.tokenColors[i]; - if (!rule.scope && rule.settings) { - if (rule.settings.foreground) { - defaultForeground = Color.fromHex(rule.settings.foreground); - } - if (rule.settings.background) { - defaultBackground = Color.fromHex(rule.settings.background); - } - } - } - TokenizationRegistry.setColorMap([null!, defaultForeground, defaultBackground]); - - this._modeService.onDidCreateMode((mode) => { - let modeId = mode.getId(); - this._createdModes.push(modeId); - this._registerDefinitionIfAvailable(modeId); - }); - } - - private _registerDefinitionIfAvailable(modeId: string): void { - if (this._languageToScope.has(modeId)) { - const promise = this._createGrammar(modeId).then((r) => { - return new TMTokenization(this._scopeRegistry, r.languageId, r.grammar, r.initialState, r.containsEmbeddedLanguages, this._notificationService, this._configurationService); - }, e => { - onUnexpectedError(e); - return null; - }); - this._tokenizersRegistrations.push(TokenizationRegistry.registerPromise(modeId, promise)); - } - } - - private async _createGrammarRegistry(): Promise<[Registry, StackElement]> { - const { Registry, INITIAL, parseRawGrammar } = await import('vscode-textmate'); - const grammarRegistry = new Registry({ - loadGrammar: async (scopeName: string) => { - const location = this._scopeRegistry.getGrammarLocation(scopeName); - if (!location) { - this._logService.trace(`No grammar found for scope ${scopeName}`); - return null; - } - try { - const content = await this._fileService.readFile(location); - return parseRawGrammar(content.value.toString(), location.path); - } catch (e) { - this._logService.error(`Unable to load and parse grammar for scope ${scopeName} from ${location}`, e); - return null; - } - }, - getInjections: (scopeName: string) => { - const scopeParts = scopeName.split('.'); - let injections: string[] = []; - for (let i = 1; i <= scopeParts.length; i++) { - const subScopeName = scopeParts.slice(0, i).join('.'); - injections = [...injections, ...(this._injections[subScopeName] || [])]; - } - return injections; - } - }); - this._updateTheme(grammarRegistry); - this._themeListener = this._themeService.onDidColorThemeChange((e) => this._updateTheme(grammarRegistry)); - return <[Registry, StackElement]>[grammarRegistry, INITIAL]; - } - - private _getOrCreateGrammarRegistry(): Promise<[Registry, StackElement]> { - if (!this._grammarRegistry) { - this._grammarRegistry = this._createGrammarRegistry(); - } - return this._grammarRegistry; - } - - private static _toColorMap(colorMap: string[]): Color[] { - let result: Color[] = [null!]; - for (let i = 1, len = colorMap.length; i < len; i++) { - result[i] = Color.fromHex(colorMap[i]); - } - return result; - } - - private _updateTheme(grammarRegistry: Registry): void { - let colorTheme = this._themeService.getColorTheme(); - if (!this.compareTokenRules(colorTheme.tokenColors)) { - return; - } - grammarRegistry.setTheme({ name: colorTheme.label, settings: colorTheme.tokenColors }); - let colorMap = TextMateService._toColorMap(grammarRegistry.getColorMap()); - let cssRules = generateTokensCSSForColorMap(colorMap); - this._styleElement.innerHTML = cssRules; - TokenizationRegistry.setColorMap(colorMap); - } - - private compareTokenRules(newRules: ITokenColorizationRule[]): boolean { - let currRules = this._currentTokenColors; - this._currentTokenColors = newRules; - if (!newRules || !currRules || newRules.length !== currRules.length) { - return true; - } - for (let i = newRules.length - 1; i >= 0; i--) { - let r1 = newRules[i]; - let r2 = currRules[i]; - if (r1.scope !== r2.scope) { - return true; - } - let s1 = r1.settings; - let s2 = r2.settings; - if (s1 && s2) { - if (s1.fontStyle !== s2.fontStyle || s1.foreground !== s2.foreground || s1.background !== s2.background) { - return true; - } - } else if (!s1 || !s2) { - return true; - } - } - return false; - } - - private _handleGrammarExtensionPointUser(extensionLocation: URI, syntax: ITMSyntaxExtensionPoint, collector: ExtensionMessageCollector): void { - if (syntax.language && ((typeof syntax.language !== 'string') || !this._modeService.isRegisteredMode(syntax.language))) { - collector.error(nls.localize('invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", grammarsExtPoint.name, String(syntax.language))); - return; - } - if (!syntax.scopeName || (typeof syntax.scopeName !== 'string')) { - collector.error(nls.localize('invalid.scopeName', "Expected string in `contributes.{0}.scopeName`. Provided value: {1}", grammarsExtPoint.name, String(syntax.scopeName))); - return; - } - if (!syntax.path || (typeof syntax.path !== 'string')) { - collector.error(nls.localize('invalid.path.0', "Expected string in `contributes.{0}.path`. Provided value: {1}", grammarsExtPoint.name, String(syntax.path))); - return; - } - if (syntax.injectTo && (!Array.isArray(syntax.injectTo) || syntax.injectTo.some(scope => typeof scope !== 'string'))) { - collector.error(nls.localize('invalid.injectTo', "Invalid value in `contributes.{0}.injectTo`. Must be an array of language scope names. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.injectTo))); - return; - } - if (syntax.embeddedLanguages && !types.isObject(syntax.embeddedLanguages)) { - collector.error(nls.localize('invalid.embeddedLanguages', "Invalid value in `contributes.{0}.embeddedLanguages`. Must be an object map from scope name to language. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.embeddedLanguages))); - return; - } - - if (syntax.tokenTypes && !types.isObject(syntax.tokenTypes)) { - collector.error(nls.localize('invalid.tokenTypes', "Invalid value in `contributes.{0}.tokenTypes`. Must be an object map from scope name to token type. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.tokenTypes))); - return; - } - - const grammarLocation = resources.joinPath(extensionLocation, syntax.path); - if (!resources.isEqualOrParent(grammarLocation, extensionLocation)) { - collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", grammarsExtPoint.name, grammarLocation.path, extensionLocation.path)); - } - - this._scopeRegistry.register(syntax.scopeName, grammarLocation, syntax.embeddedLanguages, syntax.tokenTypes); - - if (syntax.injectTo) { - for (let injectScope of syntax.injectTo) { - let injections = this._injections[injectScope]; - if (!injections) { - this._injections[injectScope] = injections = []; - } - injections.push(syntax.scopeName); - } - - if (syntax.embeddedLanguages) { - for (let injectScope of syntax.injectTo) { - let injectedEmbeddedLanguages = this._injectedEmbeddedLanguages[injectScope]; - if (!injectedEmbeddedLanguages) { - this._injectedEmbeddedLanguages[injectScope] = injectedEmbeddedLanguages = []; - } - injectedEmbeddedLanguages.push(syntax.embeddedLanguages); - } - } - } - - let modeId = syntax.language; - if (modeId) { - this._languageToScope.set(modeId, syntax.scopeName); - } - } - - private _resolveEmbeddedLanguages(embeddedLanguages: IEmbeddedLanguagesMap): IEmbeddedLanguagesMap2 { - let scopes = Object.keys(embeddedLanguages); - let result: IEmbeddedLanguagesMap2 = Object.create(null); - for (let i = 0, len = scopes.length; i < len; i++) { - let scope = scopes[i]; - let language = embeddedLanguages[scope]; - let languageIdentifier = this._modeService.getLanguageIdentifier(language); - if (languageIdentifier) { - result[scope] = languageIdentifier.id; - } - } - return result; - } - - public async createGrammar(modeId: string): Promise<IGrammar> { - const { grammar } = await this._createGrammar(modeId); - return grammar; - } - - private async _createGrammar(modeId: string): Promise<ICreateGrammarResult> { - const scopeName = this._languageToScope.get(modeId); - if (typeof scopeName !== 'string') { - // No TM grammar defined - return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language."))); - } - const languageRegistration = this._scopeRegistry.getLanguageRegistration(scopeName); - if (!languageRegistration) { - // No TM grammar defined - return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language."))); - } - let embeddedLanguages = this._resolveEmbeddedLanguages(languageRegistration.embeddedLanguages); - let rawInjectedEmbeddedLanguages = this._injectedEmbeddedLanguages[scopeName]; - if (rawInjectedEmbeddedLanguages) { - let injectedEmbeddedLanguages: IEmbeddedLanguagesMap2[] = rawInjectedEmbeddedLanguages.map(this._resolveEmbeddedLanguages.bind(this)); - for (const injected of injectedEmbeddedLanguages) { - for (const scope of Object.keys(injected)) { - embeddedLanguages[scope] = injected[scope]; - } - } - } - - let languageId = this._modeService.getLanguageIdentifier(modeId)!.id; - let containsEmbeddedLanguages = (Object.keys(embeddedLanguages).length > 0); - - const [grammarRegistry, initialState] = await this._getOrCreateGrammarRegistry(); - const grammar = await grammarRegistry.loadGrammarWithConfiguration(scopeName, languageId, { embeddedLanguages, tokenTypes: languageRegistration.tokenTypes }); - return { - languageId: languageId, - grammar: grammar, - initialState: initialState, - containsEmbeddedLanguages: containsEmbeddedLanguages - }; - } -} - -class TMTokenization implements ITokenizationSupport { - - private readonly _scopeRegistry: TMScopeRegistry; - private readonly _languageId: LanguageId; - private readonly _grammar: IGrammar; - private readonly _containsEmbeddedLanguages: boolean; - private readonly _seenLanguages: boolean[]; - private readonly _initialState: StackElement; - private _maxTokenizationLineLength: number; - private _tokenizationWarningAlreadyShown: boolean; - - constructor(scopeRegistry: TMScopeRegistry, languageId: LanguageId, grammar: IGrammar, initialState: StackElement, containsEmbeddedLanguages: boolean, @INotificationService private readonly notificationService: INotificationService, @IConfigurationService readonly configurationService: IConfigurationService) { - this._scopeRegistry = scopeRegistry; - this._languageId = languageId; - this._grammar = grammar; - this._initialState = initialState; - this._containsEmbeddedLanguages = containsEmbeddedLanguages; - this._seenLanguages = []; - this._maxTokenizationLineLength = configurationService.getValue<number>('editor.maxTokenizationLineLength'); - } - - public getInitialState(): IState { - return this._initialState; - } - - public tokenize(line: string, state: IState, offsetDelta: number): TokenizationResult { - throw new Error('Not supported!'); - } - - public tokenize2(line: string, state: StackElement, offsetDelta: number): TokenizationResult2 { - if (offsetDelta !== 0) { - throw new Error('Unexpected: offsetDelta should be 0.'); - } - - // Do not attempt to tokenize if a line is too long - if (line.length >= this._maxTokenizationLineLength) { - if (!this._tokenizationWarningAlreadyShown) { - this._tokenizationWarningAlreadyShown = true; - this.notificationService.warn(nls.localize('too many characters', "Tokenization is skipped for long lines for performance reasons. The length of a long line can be configured via `editor.maxTokenizationLineLength`.")); - } - console.log(`Line (${line.substr(0, 15)}...): longer than ${this._maxTokenizationLineLength} characters, tokenization skipped.`); - return nullTokenize2(this._languageId, line, state, offsetDelta); - } - - let textMateResult = this._grammar.tokenizeLine2(line, state); - - if (this._containsEmbeddedLanguages) { - let seenLanguages = this._seenLanguages; - let tokens = textMateResult.tokens; - - // Must check if any of the embedded languages was hit - for (let i = 0, len = (tokens.length >>> 1); i < len; i++) { - let metadata = tokens[(i << 1) + 1]; - let languageId = TokenMetadata.getLanguageId(metadata); - - if (!seenLanguages[languageId]) { - seenLanguages[languageId] = true; - this._scopeRegistry.onEncounteredLanguage(languageId); - } - } - } - - let endState: StackElement; - // try to save an object if possible - if (state.equals(textMateResult.ruleStack)) { - endState = state; - } else { - endState = textMateResult.ruleStack; - - } - - return new TokenizationResult2(textMateResult.tokens, endState); + protected _loadVSCodeTextmate(): Promise<typeof import('vscode-textmate')> { + return import('vscode-textmate'); } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 8a07bf72701e..09d81533fa47 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -144,7 +144,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private onFileChanges(e: FileChangesEvent): void { + private async onFileChanges(e: FileChangesEvent): Promise<void> { let fileEventImpactsModel = false; let newInOrphanModeGuess: boolean | undefined; @@ -167,28 +167,25 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } if (fileEventImpactsModel && this.inOrphanMode !== newInOrphanModeGuess) { - let checkOrphanedPromise: Promise<boolean>; + let newInOrphanModeValidated: boolean = false; if (newInOrphanModeGuess) { // We have received reports of users seeing delete events even though the file still // exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665). // Since we do not want to mark the model as orphaned, we have to check if the // file is really gone and not just a faulty file event. - checkOrphanedPromise = timeout(100).then(() => { - if (this.disposed) { - return true; - } + await timeout(100); - return this.fileService.exists(this.resource).then(exists => !exists); - }); - } else { - checkOrphanedPromise = Promise.resolve(false); + if (this.disposed) { + newInOrphanModeValidated = true; + } else { + const exists = await this.fileService.exists(this.resource); + newInOrphanModeValidated = !exists; + } } - checkOrphanedPromise.then(newInOrphanModeValidated => { - if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) { - this.setOrphaned(newInOrphanModeValidated); - } - }); + if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) { + this.setOrphaned(newInOrphanModeValidated); + } } } @@ -239,13 +236,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.backupFileService.backupResource<IBackupMetaData>(target, this.createSnapshot(), this.versionId, meta); } - - return Promise.resolve(); } async revert(soft?: boolean): Promise<void> { if (!this.isResolved()) { - return Promise.resolve(undefined); + return; } // Cancel any running auto-save @@ -254,25 +249,21 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Unset flags const undo = this.setDirty(false); - let loadPromise: Promise<unknown>; - if (soft) { - loadPromise = Promise.resolve(); - } else { - loadPromise = this.load({ forceReadFromDisk: true }); - } - - try { - await loadPromise; - - // Emit file change event - this._onDidStateChange.fire(StateChange.REVERTED); - } catch (error) { + // Force read from disk unless reverting soft + if (!soft) { + try { + await this.load({ forceReadFromDisk: true }); + } catch (error) { - // Set flags back to previous values, we are still dirty if revert failed - undo(); + // Set flags back to previous values, we are still dirty if revert failed + undo(); - return Promise.reject(error); + throw error; + } } + + // Emit file change event + this._onDidStateChange.fire(StateChange.REVERTED); } async load(options?: ILoadOptions): Promise<ITextFileEditorModel> { @@ -600,7 +591,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil save(options: ISaveOptions = Object.create(null)): Promise<void> { if (!this.isResolved()) { - return Promise.resolve(undefined); + return Promise.resolve(); } this.logService.trace('save() - enter', this.resource); @@ -626,7 +617,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (this.saveSequentializer.hasPendingSave(versionId)) { this.logService.trace(`doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource); - return this.saveSequentializer.pendingSave || Promise.resolve(undefined); + return this.saveSequentializer.pendingSave || Promise.resolve(); } // Return early if not dirty (unless forced) or version changed meanwhile @@ -639,7 +630,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if ((!options.force && !this.dirty) || versionId !== this.versionId) { this.logService.trace(`doSave(${versionId}) - exit - because not dirty and/or versionId is different (this.isDirty: ${this.dirty}, this.versionId: ${this.versionId})`, this.resource); - return Promise.resolve(undefined); + return Promise.resolve(); } // Return if currently saving by storing this save request as the next save that should happen. @@ -792,7 +783,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Check for global settings file - if (isEqual(this.resource, URI.file(this.environmentService.appSettingsPath), !isLinux)) { + if (isEqual(this.resource, this.environmentService.settingsResource, !isLinux)) { return 'global-settings'; } diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index f36e3b0221ce..f26eadc667f1 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -443,7 +443,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return this.fileService.del(resource, options); } - async move(source: URI, target: URI, overwrite?: boolean): Promise<void> { + async move(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata> { const waitForPromises: Promise<unknown>[] = []; // Event @@ -498,10 +498,12 @@ export abstract class TextFileService extends Disposable implements ITextFileSer // Rename to target try { - await this.fileService.move(source, target, overwrite); + const stat = await this.fileService.move(source, target, overwrite); // Load models that were dirty before await Promise.all(dirtyTargetModelUris.map(dirtyTargetModel => this.models.loadOrCreate(dirtyTargetModel))); + + return stat; } catch (error) { // In case of an error, discard any dirty target backups that were made diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 8c46a858ac32..c3027baedfb5 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -125,7 +125,7 @@ export interface ITextFileService extends IDisposable { /** * Move a file. If the file is dirty, its contents will be preserved and restored. */ - move(source: URI, target: URI, overwrite?: boolean): Promise<void>; + move(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata>; /** * Brings up the confirm dialog to either save, don't save or cancel. diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index 8c5fc900d993..a9372a7605a3 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -17,7 +17,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { Schemas } from 'vs/base/common/network'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { rimraf, RimRafMode, copy, readFile, exists } from 'vs/base/node/pfs'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { FileService } from 'vs/workbench/services/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; @@ -76,7 +76,7 @@ suite('Files - TextFileService i/o', () => { const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'textfileservice'); let accessor: ServiceAccessor; - let disposables: IDisposable[] = []; + const disposables = new DisposableStore(); let service: ITextFileService; let testDir: string; @@ -88,8 +88,8 @@ suite('Files - TextFileService i/o', () => { const fileService = new FileService(logService); const fileProvider = new DiskFileSystemProvider(logService); - disposables.push(fileService.registerProvider(Schemas.file, fileProvider)); - disposables.push(fileProvider); + disposables.add(fileService.registerProvider(Schemas.file, fileProvider)); + disposables.add(fileProvider); const collection = new ServiceCollection(); collection.set(IFileService, fileService); @@ -108,7 +108,7 @@ suite('Files - TextFileService i/o', () => { (<TextFileEditorModelManager>accessor.textFileService.models).dispose(); accessor.untitledEditorService.revertAll(); - disposables = dispose(disposables); + disposables.clear(); await rimraf(parentDir, RimRafMode.MOVE); }); diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 340bb88cf98a..987f06054409 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -151,10 +151,10 @@ export class TextModelResolverService implements ITextModelService { const cachedModel = this.modelService.getModel(resource); if (!cachedModel) { - return Promise.reject(new Error('Cant resolve inmemory resource')); + throw new Error('Cant resolve inmemory resource'); } - return Promise.resolve(new ImmortalReference(this.instantiationService.createInstance(ResourceEditorModel, resource) as IResolvedTextEditorModel)); + return new ImmortalReference(this.instantiationService.createInstance(ResourceEditorModel, resource) as IResolvedTextEditorModel); } const ref = this.resourceModelCollection.acquire(resource.toString()); diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index efda47b66aab..ef50604c8daf 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -45,6 +45,7 @@ const defaultThemeExtensionId = 'sql-theme-carbon'; const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults'; const DEFAULT_ICON_THEME_SETTING_VALUE = 'vs-seti'; +const DEFAULT_ICON_THEME_ID = 'vscode.vscode-theme-seti-vs-seti'; const fileIconsEnabledClass = 'file-icons-enabled'; const colorThemeRulesClassName = 'contributedColorTheme'; @@ -192,10 +193,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (!theme) { // current theme is no longer available prevFileIconId = this.currentIconTheme.id; - this.setFileIconTheme(DEFAULT_ICON_THEME_SETTING_VALUE, 'auto'); + this.setFileIconTheme(DEFAULT_ICON_THEME_ID, 'auto'); } else { // restore color - if (this.currentIconTheme.id === DEFAULT_ICON_THEME_SETTING_VALUE && !types.isUndefined(prevFileIconId) && await this.iconThemeStore.findThemeData(prevFileIconId)) { + if (this.currentIconTheme.id === DEFAULT_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.iconThemeStore.findThemeData(prevFileIconId)) { this.setFileIconTheme(prevFileIconId, 'auto'); prevFileIconId = undefined; } @@ -271,7 +272,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (devThemes.length) { return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } else { - return this.setFileIconTheme(theme && theme.id || DEFAULT_ICON_THEME_SETTING_VALUE, undefined); + return this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined); } }); }), @@ -294,7 +295,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { let iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING); if (iconThemeSetting !== this.currentIconTheme.settingsId) { this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => { - this.setFileIconTheme(theme && theme.id || DEFAULT_ICON_THEME_SETTING_VALUE, undefined); + this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined); }); } } @@ -481,7 +482,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.doSetFileIconTheme(newIconTheme); // remember theme data for a quick restore - if (newIconTheme.isLoaded && newIconTheme.location && !getRemoteAuthority(newIconTheme.location)) { + if (newIconTheme.isLoaded && (!newIconTheme.location || !getRemoteAuthority(newIconTheme.location))) { this.storageService.store(PERSISTED_ICON_THEME_STORAGE_KEY, newIconTheme.toStorageData(), StorageScope.GLOBAL); } diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index ad6b3a828af9..a6f3188bf029 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -251,7 +251,7 @@ export class ColorThemeData implements IColorTheme { break; case 'themeTokenColors': case 'id': case 'label': case 'settingsId': case 'extensionData': case 'watch': - theme[key] = data[key]; + (theme as any)[key] = data[key]; break; } } diff --git a/src/vs/workbench/services/themes/common/fileIconThemeData.ts b/src/vs/workbench/services/themes/common/fileIconThemeData.ts index 6472b581a70b..d4a691c8c310 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeData.ts @@ -118,7 +118,7 @@ export class FileIconThemeData implements IFileIconTheme { case 'hidesExplorerArrows': case 'hasFolderIcons': case 'watch': - theme[key] = data[key]; + (theme as any)[key] = data[key]; break; case 'location': theme.location = URI.revive(data.location); diff --git a/src/vs/workbench/services/themes/common/fileIconThemeStore.ts b/src/vs/workbench/services/themes/common/fileIconThemeStore.ts index 0816834bbc23..800f017ec8a8 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeStore.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeStore.ts @@ -13,6 +13,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { Event, Emitter } from 'vs/base/common/event'; import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData'; import { URI } from 'vs/base/common/uri'; +import { Disposable } from 'vs/base/common/lifecycle'; const iconThemeExtPoint = ExtensionsRegistry.registerExtensionPoint<IThemeExtensionPoint[]>({ extensionPoint: 'iconThemes', @@ -46,16 +47,16 @@ export interface FileIconThemeChangeEvent { added: FileIconThemeData[]; } -export class FileIconThemeStore { +export class FileIconThemeStore extends Disposable { private knownIconThemes: FileIconThemeData[]; - private readonly onDidChangeEmitter: Emitter<FileIconThemeChangeEvent>; - public get onDidChange(): Event<FileIconThemeChangeEvent> { return this.onDidChangeEmitter.event; } + private readonly onDidChangeEmitter = this._register(new Emitter<FileIconThemeChangeEvent>()); + readonly onDidChange: Event<FileIconThemeChangeEvent> = this.onDidChangeEmitter.event; constructor(@IExtensionService private readonly extensionService: IExtensionService) { + super(); this.knownIconThemes = []; - this.onDidChangeEmitter = new Emitter<FileIconThemeChangeEvent>(); this.initialize(); } @@ -167,5 +168,4 @@ export class FileIconThemeStore { return this.knownIconThemes; }); } - } diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index fd947df735b4..1f2dbf365dc8 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -12,7 +12,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IUpdateService } from 'vs/platform/update/common/update'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -23,14 +22,12 @@ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessi /* __GDPR__FRAGMENT__ "IMemoryInfo" : { "workingSetSize" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "peakWorkingSetSize": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "privateBytes": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "sharedBytes": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true } } */ export interface IMemoryInfo { readonly workingSetSize: number; - readonly peakWorkingSetSize: number; readonly privateBytes: number; readonly sharedBytes: number; } @@ -211,7 +208,7 @@ export interface IStartupMetrics { readonly ellapsedWorkspaceServiceInit: number; /** - * The time it took to load the main-bundle of the workbench, e.g `workbench.main.js`. + * The time it took to load the main-bundle of the workbench, e.g. `workbench.main.js`. * * * Happens in the renderer-process * * Measured with the `willLoadWorkbenchMain` and `didLoadWorkbenchMain` performance marks. @@ -355,7 +352,13 @@ class TimerService implements ITimerService { release = os.release(); arch = os.arch(); loadavg = os.loadavg(); - meminfo = process.getProcessMemoryInfo(); + + const processMemoryInfo = await process.getProcessMemoryInfo(); + meminfo = { + workingSetSize: processMemoryInfo.residentSet, + privateBytes: processMemoryInfo.private, + sharedBytes: processMemoryInfo.shared + }; isVMLikelyhood = Math.round((virtualMachineHint.value() * 100)); @@ -428,16 +431,19 @@ export function didUseCachedData(): boolean { if (!Boolean((<any>global).require.getConfig().nodeCachedData)) { return false; } - // whenever cached data is produced or rejected a onNodeCachedData-callback is invoked. That callback - // stores data in the `MonacoEnvironment.onNodeCachedData` global. See: - // https://github.com/Microsoft/vscode/blob/efe424dfe76a492eab032343e2fa4cfe639939f0/src/vs/workbench/electron-browser/bootstrap/index.js#L299 - if (isNonEmptyArray(MonacoEnvironment.onNodeCachedData)) { - return false; + // There are loader events that signal if cached data was missing, rejected, + // or used. The former two mean no cached data. + let cachedDataFound = 0; + for (const event of require.getStats()) { + switch (event.type) { + case LoaderEventType.CachedDataRejected: + return false; + case LoaderEventType.CachedDataFound: + cachedDataFound += 1; + break; + } } - return true; + return cachedDataFound > 0; } -declare type OnNodeCachedDataArgs = [{ errorCode: string, path: string, detail?: string }, { path: string, length: number }]; -declare const MonacoEnvironment: { onNodeCachedData: OnNodeCachedDataArgs[] }; - //#endregion diff --git a/src/vs/workbench/services/viewlet/browser/viewlet.ts b/src/vs/workbench/services/viewlet/browser/viewlet.ts index 39624a7d04b2..a82651c76894 100644 --- a/src/vs/workbench/services/viewlet/browser/viewlet.ts +++ b/src/vs/workbench/services/viewlet/browser/viewlet.ts @@ -7,7 +7,7 @@ import { IViewlet } from 'vs/workbench/common/viewlet'; import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ILocalProgressService } from 'vs/platform/progress/common/progress'; export const IViewletService = createDecorator<IViewletService>('viewletService'); @@ -47,7 +47,7 @@ export interface IViewletService { /** * Returns the progress indicator for the side bar. */ - getProgressIndicator(id: string): IProgressService | null; + getProgressIndicator(id: string): ILocalProgressService | null; /** * Hide the active viewlet. diff --git a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts index 5d1f94806ce8..09347c163cd0 100644 --- a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts @@ -67,74 +67,81 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } - private saveUntitedBeforeShutdown(reason: ShutdownReason): Promise<boolean> | undefined { + private async saveUntitedBeforeShutdown(reason: ShutdownReason): Promise<boolean> { if (reason !== ShutdownReason.LOAD && reason !== ShutdownReason.CLOSE) { - return undefined; // only interested when window is closing or loading + return false; // only interested when window is closing or loading } const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); if (!workspaceIdentifier || !isEqualOrParent(workspaceIdentifier.configPath, this.environmentService.untitledWorkspacesHome)) { - return undefined; // only care about untitled workspaces to ask for saving + return false; // only care about untitled workspaces to ask for saving } - return this.windowsService.getWindowCount().then(windowCount => { - if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { - return false; // Windows/Linux: quits when last window is closed, so do not ask then - } - enum ConfirmResult { - SAVE, - DONT_SAVE, - CANCEL - } + const windowCount = await this.windowsService.getWindowCount(); - const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE }; - const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE }; - const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; - - const buttons: { label: string; result: ConfirmResult; }[] = []; - if (isWindows) { - buttons.push(save, dontSave, cancel); - } else if (isLinux) { - buttons.push(dontSave, cancel, save); - } else { - buttons.push(save, cancel, dontSave); - } + if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { + return false; // Windows/Linux: quits when last window is closed, so do not ask then + } + + enum ConfirmResult { + SAVE, + DONT_SAVE, + CANCEL + } + + const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE }; + const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE }; + const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; + + const buttons: { label: string; result: ConfirmResult; }[] = []; + if (isWindows) { + buttons.push(save, dontSave, cancel); + } else if (isLinux) { + buttons.push(dontSave, cancel, save); + } else { + buttons.push(save, cancel, dontSave); + } + + const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"); + const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."); + const cancelId = buttons.indexOf(cancel); + + const res = await this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId }); + + switch (buttons[res].result) { - const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"); - const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."); - const cancelId = buttons.indexOf(cancel); - - return this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId }).then(res => { - switch (buttons[res].result) { - - // Cancel: veto unload - case ConfirmResult.CANCEL: - return true; - - // Don't Save: delete workspace - case ConfirmResult.DONT_SAVE: - this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); - return false; - - // Save: save workspace, but do not veto unload - case ConfirmResult.SAVE: { - return this.pickNewWorkspacePath().then(newWorkspacePath => { - if (newWorkspacePath) { - return this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath).then(_ => { - return this.workspaceService.getWorkspaceIdentifier(newWorkspacePath).then(newWorkspaceIdentifier => { - const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); - this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); - this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); - return false; - }); - }, () => false); - } - return true; // keep veto if no target was provided - }); - } + // Cancel: veto unload + case ConfirmResult.CANCEL: + return true; + + // Don't Save: delete workspace + case ConfirmResult.DONT_SAVE: + this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + return false; + + // Save: save workspace, but do not veto unload if path provided + case ConfirmResult.SAVE: { + const newWorkspacePath = await this.pickNewWorkspacePath(); + if (!newWorkspacePath) { + return true; // keep veto if no target was provided } - }); - }); + + try { + await this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath); + + const newWorkspaceIdentifier = await this.workspaceService.getWorkspaceIdentifier(newWorkspacePath); + + const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); + this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); + + this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + } catch (error) { + // ignore + } + + return false; + } + } } pickNewWorkspacePath(): Promise<URI | undefined> { @@ -218,7 +225,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { newWorkspaceFolders = distinct(newWorkspaceFolders, folder => getComparisonKey(folder.uri)); if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) { - return Promise.resolve(); // return if the operation is a no-op for the current state + return; // return if the operation is a no-op for the current state } return this.createAndEnterWorkspace(newWorkspaceFolders); @@ -267,7 +274,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise<void> { if (path && !await this.isValidTargetWorkspacePath(path)) { - return Promise.reject(null); + return; } const remoteAuthority = this.environmentService.configuration.remoteAuthority; const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority); @@ -281,11 +288,11 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { async saveAndEnterWorkspace(path: URI): Promise<void> { if (!await this.isValidTargetWorkspacePath(path)) { - return Promise.reject(null); + return; } const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); if (!workspaceIdentifier) { - return Promise.reject(null); + return; } await this.saveWorkspaceAs(workspaceIdentifier, path); @@ -318,7 +325,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // Return early if target is same as source if (isEqual(configPathURI, targetConfigPathURI)) { - return Promise.resolve(null); + return; } // Read the contents of the workspace file, update it to new location and save it. @@ -327,18 +334,17 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true }); } - private handleWorkspaceConfigurationEditingError(error: JSONEditingError): Promise<void> { + private handleWorkspaceConfigurationEditingError(error: JSONEditingError): void { switch (error.code) { case JSONEditingErrorCode.ERROR_INVALID_FILE: this.onInvalidWorkspaceConfigurationFileError(); - return Promise.resolve(); + break; case JSONEditingErrorCode.ERROR_FILE_DIRTY: this.onWorkspaceConfigurationFileDirtyError(); - return Promise.resolve(); + break; + default: + this.notificationService.error(error.message); } - this.notificationService.error(error.message); - - return Promise.resolve(); } private onInvalidWorkspaceConfigurationFileError(): void { @@ -362,7 +368,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { async enterWorkspace(path: URI): Promise<void> { if (!!this.environmentService.extensionTestsLocationURI) { - return Promise.reject(new Error('Entering a new workspace is not possible in tests.')); + throw new Error('Entering a new workspace is not possible in tests.'); } const workspace = await this.workspaceService.getWorkspaceIdentifier(path); diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index 95f31fe0b043..01b138d94e3b 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { NotificationsModel, NotificationViewItem, INotificationChangeEvent, NotificationChangeType, NotificationViewItemLabelKind } from 'vs/workbench/common/notifications'; +import { NotificationsModel, NotificationViewItem, INotificationChangeEvent, NotificationChangeType, NotificationViewItemLabelKind, IStatusMessageChangeEvent, StatusMessageChangeType } from 'vs/workbench/common/notifications'; import { Action } from 'vs/base/common/actions'; import { INotification, Severity } from 'vs/platform/notification/common/notification'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; @@ -132,9 +132,14 @@ suite('Notifications', () => { test('Model', () => { const model = new NotificationsModel(); - let lastEvent!: INotificationChangeEvent; + let lastNotificationEvent!: INotificationChangeEvent; model.onDidNotificationChange(e => { - lastEvent = e; + lastNotificationEvent = e; + }); + + let lastStatusMessageEvent!: IStatusMessageChangeEvent; + model.onDidStatusMessageChange(e => { + lastStatusMessageEvent = e; }); let item1: INotification = { severity: Severity.Error, message: 'Error Message', actions: { primary: [new Action('id', 'label')] } }; @@ -142,23 +147,23 @@ suite('Notifications', () => { let item2Duplicate: INotification = { severity: Severity.Warning, message: 'Warning Message', source: 'Some Source' }; let item3: INotification = { severity: Severity.Info, message: 'Info Message' }; - let item1Handle = model.notify(item1); - assert.equal(lastEvent.item.severity, item1.severity); - assert.equal(lastEvent.item.message.value, item1.message); - assert.equal(lastEvent.index, 0); - assert.equal(lastEvent.kind, NotificationChangeType.ADD); + let item1Handle = model.addNotification(item1); + assert.equal(lastNotificationEvent.item.severity, item1.severity); + assert.equal(lastNotificationEvent.item.message.value, item1.message); + assert.equal(lastNotificationEvent.index, 0); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD); - let item2Handle = model.notify(item2); - assert.equal(lastEvent.item.severity, item2.severity); - assert.equal(lastEvent.item.message.value, item2.message); - assert.equal(lastEvent.index, 0); - assert.equal(lastEvent.kind, NotificationChangeType.ADD); + let item2Handle = model.addNotification(item2); + assert.equal(lastNotificationEvent.item.severity, item2.severity); + assert.equal(lastNotificationEvent.item.message.value, item2.message); + assert.equal(lastNotificationEvent.index, 0); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD); - model.notify(item3); - assert.equal(lastEvent.item.severity, item3.severity); - assert.equal(lastEvent.item.message.value, item3.message); - assert.equal(lastEvent.index, 0); - assert.equal(lastEvent.kind, NotificationChangeType.ADD); + model.addNotification(item3); + assert.equal(lastNotificationEvent.item.severity, item3.severity); + assert.equal(lastNotificationEvent.item.message.value, item3.message); + assert.equal(lastNotificationEvent.index, 0); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD); assert.equal(model.notifications.length, 3); @@ -170,29 +175,48 @@ suite('Notifications', () => { item1Handle.close(); assert.equal(called, 1); assert.equal(model.notifications.length, 2); - assert.equal(lastEvent.item.severity, item1.severity); - assert.equal(lastEvent.item.message.value, item1.message); - assert.equal(lastEvent.index, 2); - assert.equal(lastEvent.kind, NotificationChangeType.REMOVE); + assert.equal(lastNotificationEvent.item.severity, item1.severity); + assert.equal(lastNotificationEvent.item.message.value, item1.message); + assert.equal(lastNotificationEvent.index, 2); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.REMOVE); - model.notify(item2Duplicate); + model.addNotification(item2Duplicate); assert.equal(model.notifications.length, 2); - assert.equal(lastEvent.item.severity, item2Duplicate.severity); - assert.equal(lastEvent.item.message.value, item2Duplicate.message); - assert.equal(lastEvent.index, 0); - assert.equal(lastEvent.kind, NotificationChangeType.ADD); + assert.equal(lastNotificationEvent.item.severity, item2Duplicate.severity); + assert.equal(lastNotificationEvent.item.message.value, item2Duplicate.message); + assert.equal(lastNotificationEvent.index, 0); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.ADD); item2Handle.close(); assert.equal(model.notifications.length, 1); - assert.equal(lastEvent.item.severity, item2Duplicate.severity); - assert.equal(lastEvent.item.message.value, item2Duplicate.message); - assert.equal(lastEvent.index, 0); - assert.equal(lastEvent.kind, NotificationChangeType.REMOVE); + assert.equal(lastNotificationEvent.item.severity, item2Duplicate.severity); + assert.equal(lastNotificationEvent.item.message.value, item2Duplicate.message); + assert.equal(lastNotificationEvent.index, 0); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.REMOVE); model.notifications[0].expand(); - assert.equal(lastEvent.item.severity, item3.severity); - assert.equal(lastEvent.item.message.value, item3.message); - assert.equal(lastEvent.index, 0); - assert.equal(lastEvent.kind, NotificationChangeType.CHANGE); + assert.equal(lastNotificationEvent.item.severity, item3.severity); + assert.equal(lastNotificationEvent.item.message.value, item3.message); + assert.equal(lastNotificationEvent.index, 0); + assert.equal(lastNotificationEvent.kind, NotificationChangeType.CHANGE); + + const disposable = model.showStatusMessage('Hello World'); + assert.equal(model.statusMessage!.message, 'Hello World'); + assert.equal(lastStatusMessageEvent.item.message, model.statusMessage!.message); + assert.equal(lastStatusMessageEvent.kind, StatusMessageChangeType.ADD); + disposable.dispose(); + assert.ok(!model.statusMessage); + assert.equal(lastStatusMessageEvent.kind, StatusMessageChangeType.REMOVE); + + let disposable2 = model.showStatusMessage('Hello World 2'); + const disposable3 = model.showStatusMessage('Hello World 3'); + + assert.equal(model.statusMessage!.message, 'Hello World 3'); + + disposable2.dispose(); + assert.equal(model.statusMessage!.message, 'Hello World 3'); + + disposable3.dispose(); + assert.ok(!model.statusMessage); }); }); \ No newline at end of file diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 86d1abc0b102..605792b2e3a0 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -194,7 +194,7 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); const value = await getCodeLensData(model, CancellationToken.None); - assert.equal(value.length, 1); + assert.equal(value.lenses.length, 1); }); test('CodeLens, do not resolve a resolved lens', async () => { @@ -212,8 +212,8 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); const value = await getCodeLensData(model, CancellationToken.None); - assert.equal(value.length, 1); - const data = value[0]; + assert.equal(value.lenses.length, 1); + const [data] = value.lenses; const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); assert.equal(symbol!.command!.id, 'id'); assert.equal(symbol!.command!.title, 'Title'); @@ -229,8 +229,8 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); const value = await getCodeLensData(model, CancellationToken.None); - assert.equal(value.length, 1); - let data = value[0]; + assert.equal(value.lenses.length, 1); + let [data] = value.lenses; const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); assert.equal(symbol!.command!.id, 'missing'); assert.equal(symbol!.command!.title, '!!MISSING: command!!'); @@ -1041,7 +1041,9 @@ suite('ExtHostLanguageFeatures', function () { disposables.push(extHost.registerDocumentLinkProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentLinkProvider { provideDocumentLinks() { - return [new types.DocumentLink(new types.Range(0, 0, 1, 1), URI.parse('foo:bar#3'))]; + const link = new types.DocumentLink(new types.Range(0, 0, 1, 1), URI.parse('foo:bar#3')); + link.tooltip = 'tooltip'; + return [link]; } })); @@ -1051,6 +1053,7 @@ suite('ExtHostLanguageFeatures', function () { let [first] = links; assert.equal(first.url, 'foo:bar#3'); assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 2, endColumn: 2 }); + assert.equal(first.tooltip, 'tooltip'); }); test('Links, evil provider', async () => { diff --git a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts index 102eede46651..75e0ec6fdc4f 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts @@ -6,9 +6,11 @@ import * as assert from 'assert'; import { MainThreadMessageService } from 'vs/workbench/api/browser/mainThreadMessageService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification'; +import { INotificationService, INotification, NoOpNotification, INotificationHandle, Severity, IPromptChoice, IPromptOptions, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; const emptyDialogService = new class implements IDialogService { _serviceBrand: 'dialogService'; @@ -30,7 +32,7 @@ const emptyCommandService: ICommandService = { }; const emptyNotificationService = new class implements INotificationService { - _serviceBrand: 'notificiationService'; + _serviceBrand: ServiceIdentifier<INotificationService>; notify(...args: any[]): never { throw new Error('not implemented'); } @@ -46,11 +48,13 @@ const emptyNotificationService = new class implements INotificationService { prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { throw new Error('not implemented'); } + status(message: string | Error, options?: IStatusMessageOptions): IDisposable { + return Disposable.None; + } }; class EmptyNotificationService implements INotificationService { - - _serviceBrand: any; + _serviceBrand: ServiceIdentifier<INotificationService>; constructor(private withNotify: (notification: INotification) => void) { } @@ -72,6 +76,9 @@ class EmptyNotificationService implements INotificationService { prompt(severity: Severity, message: string, choices: IPromptChoice[], options?: IPromptOptions): INotificationHandle { throw new Error('not implemented'); } + status(message: string, options?: IStatusMessageOptions): IDisposable { + return Disposable.None; + } } suite('ExtHostMessageService', function () { diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index a1638d7cd3e0..b8647a6fc0fb 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { mapArrayOrNot } from 'vs/base/common/arrays'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isPromiseCanceledError } from 'vs/base/common/errors'; -import { dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; @@ -21,7 +21,7 @@ import * as vscode from 'vscode'; let rpcProtocol: TestRPCProtocol; let extHostSearch: ExtHostSearch; -let disposables: vscode.Disposable[] = []; +const disposables = new DisposableStore(); let mockMainThreadSearch: MockMainThreadSearch; class MockMainThreadSearch implements MainThreadSearchShape { @@ -63,12 +63,12 @@ export function extensionResultIsMatch(data: vscode.TextSearchResult): data is v suite('ExtHostSearch', () => { async function registerTestTextSearchProvider(provider: vscode.TextSearchProvider, scheme = 'file'): Promise<void> { - disposables.push(extHostSearch.registerTextSearchProvider(scheme, provider)); + disposables.add(extHostSearch.registerTextSearchProvider(scheme, provider)); await rpcProtocol.sync(); } async function registerTestFileSearchProvider(provider: vscode.FileSearchProvider, scheme = 'file'): Promise<void> { - disposables.push(extHostSearch.registerFileSearchProvider(scheme, provider)); + disposables.add(extHostSearch.registerFileSearchProvider(scheme, provider)); await rpcProtocol.sync(); } @@ -139,7 +139,7 @@ suite('ExtHostSearch', () => { }); teardown(() => { - dispose(disposables); + disposables.clear(); return rpcProtocol.sync(); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts index 20e7cb621213..06f8131bb8d9 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTypes.test.ts @@ -31,6 +31,7 @@ suite('ExtHostTypes', function () { scheme: 'file', path: '/path/test.file', fsPath: '/path/test.file'.replace(/\//g, isWindows ? '\\' : '/'), + _sep: isWindows ? 1 : undefined, }); assert.ok(uri.toString()); @@ -39,6 +40,7 @@ suite('ExtHostTypes', function () { scheme: 'file', path: '/path/test.file', fsPath: '/path/test.file'.replace(/\//g, isWindows ? '\\' : '/'), + _sep: isWindows ? 1 : undefined, external: 'file:///path/test.file' }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts index 46474373c10e..2dae8ec3e913 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { MainThreadWebviews } from 'vs/workbench/api/electron-browser/mainThreadWebview'; +import { MainThreadWebviews } from 'vs/workbench/api/browser/mainThreadWebview'; import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import * as vscode from 'vscode'; diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts index c9640a742306..532131bc725a 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -62,7 +62,7 @@ suite('MainThreadEditors', () => { } move(source: URI, target: URI) { movedResources.set(source, target); - return Promise.resolve(undefined); + return Promise.resolve(Object.create(null)); } models = <any>{ onModelSaved: Event.None, diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 325ad75f16a0..b823d8b63240 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -456,6 +456,9 @@ export class TestLayoutService implements IWorkbenchLayoutService { container: HTMLElement = window.document.body; onZenModeChange: Event<boolean> = Event.None; + onCenteredLayoutChange: Event<boolean> = Event.None; + onFullscreenChange: Event<boolean> = Event.None; + onPanelPositionChange: Event<string> = Event.None; onLayout = Event.None; private _onTitleBarVisibilityChange = new Emitter<void>(); diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 3851f1e7fb0d..418079dcb31b 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -7,7 +7,8 @@ import 'vs/editor/editor.all'; -import 'vs/workbench/api/electron-browser/extensionHost.contribution'; +import 'vs/workbench/api/browser/extensionHost.contribution'; +import 'sql/workbench/api/electron-browser/extensionHost.contribution'; // {{SQL CARBON EDIT}} @anthonydresser add our extension contributions import 'vs/workbench/electron-browser/main.contribution'; import 'vs/workbench/browser/workbench.contribution'; @@ -102,9 +103,8 @@ import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService import 'vs/workbench/services/extensions/common/inactiveExtensionUrlHandler'; import 'vs/workbench/services/decorations/browser/decorationsService'; import 'vs/workbench/services/search/node/searchService'; -import 'vs/workbench/services/progress/browser/progressService2'; +import 'vs/workbench/services/progress/browser/progressService'; import 'vs/workbench/services/editor/browser/codeEditorService'; -import 'vs/workbench/services/broadcast/electron-browser/broadcastService'; import 'vs/workbench/services/extensions/electron-browser/extensionHostDebugService'; import 'vs/workbench/services/preferences/browser/preferencesService'; import 'vs/workbench/services/output/node/outputChannelModelService'; @@ -163,6 +163,7 @@ registerSingleton(IMenubarService, MenubarService); registerSingleton(IURLService, RelayURLService); registerSingleton(ITunnelService, TunnelService, true); registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); +registerSingleton(ICredentialsService, KeytarCredentialsService, true); //#endregion @@ -179,7 +180,7 @@ import { IAngularEventingService } from 'sql/platform/angularEventing/common/ang import { AngularEventingService } from 'sql/platform/angularEventing/node/angularEventingService'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { CapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesServiceImpl'; -import { ICredentialsService, CredentialsService } from 'sql/platform/credentials/common/credentialsService'; +import { ICredentialsService as sqlICredentialsService, CredentialsService } from 'sql/platform/credentials/common/credentialsService'; import { ISerializationService, SerializationService } from 'sql/platform/serialization/common/serializationService'; import { IMetadataService, MetadataService } from 'sql/platform/metadata/common/metadataService'; import { IObjectExplorerService, ObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; @@ -243,7 +244,7 @@ registerSingleton(ICapabilitiesService, CapabilitiesService); registerSingleton(IErrorMessageService, ErrorMessageService); registerSingleton(IConnectionDialogService, ConnectionDialogService); registerSingleton(IServerGroupController, ServerGroupController); -registerSingleton(ICredentialsService, CredentialsService); +registerSingleton(sqlICredentialsService, CredentialsService); registerSingleton(IResourceProviderService, ResourceProviderService); registerSingleton(IAccountManagementService, AccountManagementService); registerSingleton(IConnectionManagementService, ConnectionManagementService as any); @@ -298,8 +299,11 @@ import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; // Preferences -import 'vs/workbench/contrib/preferences/electron-browser/preferences.contribution'; +import 'vs/workbench/contrib/preferences/browser/preferences.contribution'; import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; +import { IPreferencesSearchService } from 'vs/workbench/contrib/preferences/common/preferences'; +import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/electron-browser/preferencesSearch'; +registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); // Logs import 'vs/workbench/contrib/logs/common/logs.contribution'; @@ -433,11 +437,10 @@ import 'vs/workbench/contrib/outline/browser/outline.contribution'; // Experiments import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution'; -// Code Insets -import 'vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution'; - // Issues import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; +import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; // {{SQL CARBON EDIT}} // SQL @@ -450,7 +453,6 @@ import 'sql/workbench/parts/dataExplorer/browser/dataExplorerExtensionPoint'; import 'sql/workbench/parts/dataExplorer/electron-browser/nodeActions.contribution'; import 'sql/platform/telemetry/telemetry.contribution'; -import 'sql/workbench/api/node/sqlExtHost.contribution'; import 'sql/workbench/parts/connection/browser/connection.contribution'; import 'sql/workbench/parts/query/browser/query.contribution'; import 'sql/workbench/parts/query/common/resultsGridContribution'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 0e4a8d5f5bc3..77625531113a 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -7,7 +7,7 @@ import 'vs/editor/editor.all'; -// import 'vs/workbench/api/electron-browser/extensionHost.contribution'; +import 'vs/workbench/api/browser/extensionHost.contribution'; // import 'vs/workbench/electron-browser/main.contribution'; import 'vs/workbench/browser/workbench.contribution'; @@ -90,7 +90,6 @@ import { ContextViewService } from 'vs/platform/contextview/browser/contextViewS // import { IURLService } from 'vs/platform/url/common/url'; // import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; import { IHeapService, NullHeapService } from 'vs/workbench/services/heap/common/heap'; -import { IBroadcastService, NullBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast'; import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; @@ -101,14 +100,13 @@ import 'vs/platform/dialogs/browser/dialogService'; import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; // import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/keybinding/common/keybindingEditing'; -// import 'vs/workbench/services/textMate/electron-browser/textMateService'; +import 'vs/workbench/services/textMate/browser/textMateService'; // import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; // import 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; import 'vs/workbench/services/decorations/browser/decorationsService'; // import 'vs/workbench/services/search/node/searchService'; -import 'vs/workbench/services/progress/browser/progressService2'; +import 'vs/workbench/services/progress/browser/progressService'; import 'vs/workbench/services/editor/browser/codeEditorService'; -// import 'vs/workbench/services/broadcast/electron-browser/broadcastService'; import 'vs/workbench/services/preferences/browser/preferencesService'; import 'vs/workbench/services/output/common/outputChannelModelService'; import 'vs/workbench/services/configuration/common/jsonEditingService'; @@ -128,7 +126,7 @@ import 'vs/workbench/services/mode/common/workbenchModeService'; import 'vs/workbench/services/commands/common/commandService'; import 'vs/workbench/services/themes/browser/workbenchThemeService'; // import 'vs/workbench/services/extensionManagement/node/extensionEnablementService'; -// import 'vs/workbench/services/extensions/electron-browser/extensionService'; +import 'vs/workbench/services/extensions/browser/extensionService'; // import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; // import 'vs/workbench/services/extensionManagement/node/multiExtensionManagement'; import 'vs/workbench/services/label/common/labelService'; @@ -167,7 +165,6 @@ registerSingleton(IContextViewService, ContextViewService, true); // registerSingleton(IMenubarService, MenubarService); // registerSingleton(IURLService, RelayURLService); registerSingleton(IHeapService, NullHeapService); -registerSingleton(IBroadcastService, NullBroadcastService); registerSingleton(IContextMenuService, ContextMenuService); registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); @@ -198,8 +195,11 @@ import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; // import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; // Preferences -// import 'vs/workbench/contrib/preferences/electron-browser/preferences.contribution'; +import 'vs/workbench/contrib/preferences/browser/preferences.contribution'; import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; +import { IPreferencesSearchService } from 'vs/workbench/contrib/preferences/common/preferences'; +import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/browser/preferencesSearch'; +registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); // Logs import 'vs/workbench/contrib/logs/common/logs.contribution'; @@ -249,6 +249,8 @@ import 'vs/workbench/contrib/url/common/url.contribution'; // Webview // import 'vs/workbench/contrib/webview/electron-browser/webview.contribution'; +registerSingleton(IWebviewService, NullWebviewService, true); +registerSingleton(IWebviewEditorService, WebviewEditorService, true); // Extensions Management // import 'vs/workbench/contrib/extensions/electron-browser/extensions.contribution'; @@ -322,13 +324,13 @@ import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; // Outline import 'vs/workbench/contrib/outline/browser/outline.contribution'; +import { IWebviewService } from 'vs/workbench/contrib/webview/common/webview'; +import { NullWebviewService } from 'vs/workbench/contrib/webview/browser/webviewService'; +import { IWebviewEditorService, WebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; // Experiments // import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution'; -// Code Insets -// import 'vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution'; - // Issues // import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; diff --git a/test/smoke/README.md b/test/smoke/README.md index 3eed271d355b..b5be966e5184 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -15,11 +15,14 @@ yarn smoketest # Build yarn smoketest --build PATH_TO_NEW_BUILD_PARENT_FOLDER --stable-build PATH_TO_LAST_STABLE_BUILD_PARENT_FOLDER + +# Remote +yarn smoketest --build PATH_TO_NEW_BUILD_PARENT_FOLDER --remote ``` ### Run for a release -You must always run the smoketest version which matches the release you are testing. So, if you want to run the smoketest for a release build (eg `release/1.22`), you need that version of the smoke tests too: +You must always run the smoketest version which matches the release you are testing. So, if you want to run the smoketest for a release build (e.g. `release/1.22`), you need that version of the smoke tests too: ```bash git checkout release/1.22 diff --git a/test/smoke/package.json b/test/smoke/package.json index 00b04f8453df..e19de8f9de2b 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -17,12 +17,12 @@ "@types/mkdirp": "0.5.1", "@types/mocha": "2.2.41", "@types/ncp": "2.0.1", - "@types/node": "8.0.33", + "@types/node": "^10.14.8", "@types/rimraf": "2.0.2", "@types/webdriverio": "4.6.1", "concurrently": "^3.5.1", "cpx": "^1.5.0", - "electron": "3.1.8", + "electron": "4.2.3", "htmlparser2": "^3.9.2", "mkdirp": "^0.5.1", "mocha": "^5.2.0", diff --git a/test/smoke/src/application.ts b/test/smoke/src/application.ts index 7c60cabdfb36..f2f346ebefab 100644 --- a/test/smoke/src/application.ts +++ b/test/smoke/src/application.ts @@ -47,6 +47,10 @@ export class Application { return this.options.logger; } + get remote(): boolean { + return !!this.options.remote; + } + private _workspacePathOrFolder: string; get workspacePathOrFolder(): string { return this._workspacePathOrFolder; @@ -142,8 +146,12 @@ export class Application { await this.code.waitForWindowIds(ids => ids.length > 0); await this.code.waitForElement('.monaco-workbench'); + if (this.remote) { + await this.code.waitForElement('.monaco-workbench .statusbar-item.statusbar-entry a[title="Editing on TestResolver"]'); + } + // wait a bit, since focus might be stolen off widgets - // as soon as they open (eg quick open) + // as soon as they open (e.g. quick open) await new Promise(c => setTimeout(c, 1000)); } } diff --git a/test/smoke/src/areas/editor/peek.ts b/test/smoke/src/areas/editor/peek.ts index baa00aff0fdc..1cb127456e5d 100644 --- a/test/smoke/src/areas/editor/peek.ts +++ b/test/smoke/src/areas/editor/peek.ts @@ -10,7 +10,7 @@ export class References { private static readonly REFERENCES_WIDGET = '.monaco-editor .zone-widget .zone-widget-container.peekview-widget.reference-zone-widget.results-loaded'; private static readonly REFERENCES_TITLE_FILE_NAME = `${References.REFERENCES_WIDGET} .head .peekview-title .filename`; private static readonly REFERENCES_TITLE_COUNT = `${References.REFERENCES_WIDGET} .head .peekview-title .meta`; - private static readonly REFERENCES = `${References.REFERENCES_WIDGET} .body .ref-tree.inline .monaco-list-row .reference`; + private static readonly REFERENCES = `${References.REFERENCES_WIDGET} .body .ref-tree.inline .monaco-list-row .highlight`; constructor(private code: Code) { } diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index 5f1a5ac69454..d664cd8108c9 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -20,6 +20,10 @@ export function setup() { await app.workbench.extensions.installExtension('michelkaporin.vscode-smoketest-check', 'vscode-smoketest-check'); await app.workbench.extensions.waitForExtensionsViewlet(); + + if (app.remote) { + await app.reload(); + } await app.workbench.quickopen.runCommand('Smoke Test Check'); await app.workbench.statusbar.waitForStatusbarText('smoke test', 'VS Code Smoke Test Check'); }); diff --git a/test/smoke/src/areas/multiroot/multiroot.test.ts b/test/smoke/src/areas/multiroot/multiroot.test.ts index 996792effa18..ca98e172aaca 100644 --- a/test/smoke/src/areas/multiroot/multiroot.test.ts +++ b/test/smoke/src/areas/multiroot/multiroot.test.ts @@ -47,7 +47,8 @@ export function setup() { const app = this.app as Application; await app.workbench.quickopen.openQuickOpen('*.*'); - await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 6); + // TODO roblourens: Go to files finds welcome page: issue 74875 + await app.workbench.quickopen.waitForQuickOpenElements(names => names.length === 6 || names.length === 7); await app.workbench.quickopen.closeQuickOpen(); }); diff --git a/test/smoke/src/areas/statusbar/statusbar.ts b/test/smoke/src/areas/statusbar/statusbar.ts index b3aecf0c4b62..b6919965ab67 100644 --- a/test/smoke/src/areas/statusbar/statusbar.ts +++ b/test/smoke/src/areas/statusbar/statusbar.ts @@ -48,7 +48,7 @@ export class StatusBar { case StatusBarElement.SYNC_STATUS: return `${this.mainSelector} ${this.leftSelector} .octicon.octicon-sync`; case StatusBarElement.PROBLEMS_STATUS: - return `${this.mainSelector} ${this.leftSelector} .task-statusbar-item[title="Problems"]`; + return `${this.mainSelector} ${this.leftSelector} .octicon.octicon-error`; case StatusBarElement.SELECTION_STATUS: return `${this.mainSelector} ${this.rightSelector} .editor-status-selection`; case StatusBarElement.INDENTATION_STATUS: diff --git a/test/smoke/src/vscode/code.ts b/test/smoke/src/vscode/code.ts index f6398446d17f..ac38b41e9824 100644 --- a/test/smoke/src/vscode/code.ts +++ b/test/smoke/src/vscode/code.ts @@ -7,6 +7,7 @@ import * as path from 'path'; import * as cp from 'child_process'; import * as os from 'os'; import * as fs from 'fs'; +import * as mkdirp from 'mkdirp'; import { tmpName } from 'tmp'; import { IDriver, connect as connectDriver, IDisposable, IElement, Thenable } from './driver'; import { Logger } from '../logger'; @@ -123,6 +124,8 @@ export async function spawn(options: SpawnOptions): Promise<Code> { '--driver', handle ]; + const env = process.env; + if (options.remote) { // Replace workspace path with URI args.shift(); @@ -139,6 +142,9 @@ export async function spawn(options: SpawnOptions): Promise<Code> { } } args.push('--enable-proposed-api=vscode.vscode-test-resolver'); + const remoteDataDir = `${options.userDataDir}-server`; + mkdirp.sync(remoteDataDir); + env['TESTRESOLVER_DATA_FOLDER'] = remoteDataDir; } if (!codePath) { @@ -157,7 +163,7 @@ export async function spawn(options: SpawnOptions): Promise<Code> { args.push(...options.extraArgs); } - const spawnOptions: cp.SpawnOptions = {}; + const spawnOptions: cp.SpawnOptions = { env }; const child = cp.spawn(electronPath, args, spawnOptions); diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 5e1fd36aa4f7..cdcb6f7dadaa 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -44,15 +44,15 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== -"@types/node@8.0.33": - version "8.0.33" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.33.tgz#1126e94374014e54478092830704f6ea89df04cd" - integrity sha512-vmCdO8Bm1ExT+FWfC9sd9r4jwqM7o97gGy2WBshkkXbf/2nLAJQUrZfIhw27yVOtLUev6kSZc4cav/46KbDd8A== +"@types/node@^10.12.18": + version "10.12.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" + integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ== -"@types/node@^8.0.24": - version "8.10.23" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.23.tgz#e5ccfdafff42af5397c29669b6d7d65f7d629a00" - integrity sha512-aEp5ZTLr4mYhR9S85cJ+sEYkcsgFY10N1Si5m49iTAVzanZXOwp/pgw6ibFLKXxpflqm71aSWZCRtnTXXO56gA== +"@types/node@^10.14.8": + version "10.14.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" + integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== "@types/rimraf@2.0.2": version "2.0.2" @@ -596,12 +596,12 @@ electron-download@^4.1.0: semver "^5.4.1" sumchecker "^2.0.2" -electron@3.1.8: - version "3.1.8" - resolved "https://registry.yarnpkg.com/electron/-/electron-3.1.8.tgz#01b0b147dfcca47967ff07dbf72bf5e96125a2ac" - integrity sha512-1MiFoMzxGaR0wDfwFE5Ydnuk6ry/4lKgF0c+NFyEItxM/WyEHNZPNjJAeKJ+M/0sevmZ+6W4syNZnQL5M3GgsQ== +electron@4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/electron/-/electron-4.2.3.tgz#5d45da9dd5ae97269dbee2623840da808c72d29d" + integrity sha512-nx+jHxj2eNhaYHXFGdzr7zgSphpVHEU9WAu6qqEUsQ936X3c6bQ5Bdg08KbHZj+cyRRQ06JMu6/ILh5pWrDZaA== dependencies: - "@types/node" "^8.0.24" + "@types/node" "^10.12.18" electron-download "^4.1.0" extract-zip "^1.0.3" diff --git a/yarn.lock b/yarn.lock index 53c2a681f79f..704d8d89107d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -990,11 +990,18 @@ binaryextensions@~1.0.0: resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-1.0.1.tgz#1e637488b35b58bda5f4774bf96a5212a8c90755" integrity sha1-HmN0iLNbWL2l9HdL+WpSEqjJB1U= -bindings@^1.2.1, bindings@^1.3.0: +bindings@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" integrity sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw== +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + bl@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" @@ -1547,14 +1554,6 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -clean-css@3.4.6: - version "3.4.6" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.6.tgz#fcb4f17057ddb7f8721616f70b07b294d95ffc45" - integrity sha1-/LTxcFfdt/hyFhb3CweylNlf/EU= - dependencies: - commander "2.8.x" - source-map "0.4.x" - cli-cursor@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" @@ -1777,13 +1776,6 @@ commander@2.3.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" integrity sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM= -commander@2.8.x: - version "2.8.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" - integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ= - dependencies: - graceful-readlink ">= 1.0.0" - commander@^2.12.1, commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -3245,6 +3237,13 @@ fd-slicer@~1.0.1: dependencies: pend "~1.2.0" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + figures@^1.3.5: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -3268,6 +3267,11 @@ file-entry-cache@^2.0.0: flat-cache "^1.2.1" object-assign "^4.0.1" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + filename-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775" @@ -3866,11 +3870,6 @@ graceful-fs@4.1.11, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= - growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -6063,7 +6062,7 @@ mute-stream@0.0.7, mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@2.8.0, nan@^2.8.0: +nan@2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" integrity sha1-7XFfP+neArV6XmJS2QqWZ14fCFo= @@ -6078,7 +6077,7 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== -nan@^2.13.2: +nan@^2.13.2, nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -6218,10 +6217,10 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" -node-pty@0.9.0-beta9: - version "0.9.0-beta9" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0-beta9.tgz#75cffcf4026f543475c115f017ca7fe66cf6e7fe" - integrity sha512-h6e8jUikGSZwqt1JHmzT5Zi0fdUCultX/BWrS35suTaZNJm/YSJA2QDG9HTVoSA6dhRvtFoaGiBtgbX9uZKe6w== +node-pty@0.9.0-beta17: + version "0.9.0-beta17" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0-beta17.tgz#9b490df86a8124dea595e9fbedeaaf4b2eedbbcb" + integrity sha512-E94XwIs3JxLKAboquHY9Kytbbj/T/tJtRpQoAUdfPE7UXRta/NV+xdmRNhZkeU9jCji+plm656GbYFievgNPkQ== dependencies: nan "^2.13.2" @@ -6496,6 +6495,11 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" +onigasm-umd@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257" + integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw== + oniguruma@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/oniguruma/-/oniguruma-7.0.2.tgz#a5c922cf7066da1dbcc60f6385a90437a83f8d0b" @@ -8437,18 +8441,18 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@0.4.x, source-map@^0.4.4: +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" integrity sha1-66T12pwNyZneaAMti092FzZSA2s= dependencies: amdefine ">=0.0.4" -source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -8466,14 +8470,14 @@ sparkles@^1.0.0: resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" integrity sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM= -spdlog@0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.8.1.tgz#dfb3f3422ab3efe32be79e4769b95440ed72699f" - integrity sha512-W0s8IOXpn86md+8PJ4mJeB/22thykzH5YaNc3Rgnql4x4/zFIhvNiEx6/a1arnqvmJF0HtRO0Ehlswg0WcwTLQ== +spdlog@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.9.0.tgz#c85dd9d0b9cd385f6f3f5b92dc9d2e1691092b5c" + integrity sha512-AeLWPCYjGi4w5DfpXFKb9pCdgMe4gFBMroGfgwXiNfzwmcNYGoFQkIuD1MChZBR1Iwrx0nGhsTSHFslt/qfTAQ== dependencies: - bindings "^1.3.0" + bindings "^1.5.0" mkdirp "^0.5.1" - nan "^2.8.0" + nan "^2.14.0" spdx-correct@~1.0.0: version "1.0.2" @@ -9249,10 +9253,10 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@3.4.5: - version "3.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" - integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== +typescript@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" + integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== typescript@^2.6.2: version "2.6.2" @@ -9739,10 +9743,10 @@ vscode-chokidar@1.6.5: optionalDependencies: vscode-fsevents "0.3.10" -vscode-debugprotocol@1.34.0: - version "1.34.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.34.0.tgz#aef63274166ccbc6d1d68e68c7d7f6d013802f08" - integrity sha512-tcMThtgk9TUtE8zzAIwPvHZfgnEYnVa7cI3YaQk/o54Q9cme+TLd/ao60a6ycj5rCrI/B5r/mAfeK5EKSItm7g== +vscode-debugprotocol@1.35.0: + version "1.35.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.35.0.tgz#565140cd42945e30c6c85cafb38c631457d4a46c" + integrity sha512-+OMm11R1bGYbpIJ5eQIkwoDGFF4GvBz3Ztl6/VM+/RNNb2Gjk2c0Ku+oMmfhlTmTlPCpgHBsH4JqVCbUYhu5bA== vscode-fsevents@0.3.10: version "0.3.10" @@ -9769,10 +9773,10 @@ vscode-nls-dev@3.2.5: xml2js "^0.4.19" yargs "^10.1.1" -vscode-nsfw@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-1.1.1.tgz#7c3febe153677c5850b197a0b64a197cd11e95c7" - integrity sha512-Wg3vzN1U3T6P1uE13LdVVRIhdy7XWnWkwmAXhkLsIkH2QY0E/pvNDRLrwAMMW6GC1Fvvbxm3hzdIrCmr7Hq3FA== +vscode-nsfw@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vscode-nsfw/-/vscode-nsfw-1.1.2.tgz#9cb9073b5854386801afe41f7152f721b4ea9e80" + integrity sha512-J0So+JNK/5kQboTO1hKNk4ie/wwUegrJilYSY5sVxU9JJlo3aQdP0zi2NtU8CEK3kkN6qRp0MbXCzbT0LKGorg== dependencies: fs-extra "^7.0.0" lodash.isinteger "^4.0.4" @@ -9801,10 +9805,10 @@ vscode-sqlite3@4.0.7: dependencies: nan "~2.10.0" -vscode-textmate@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.0.1.tgz#6c36f28e9059ce12bc34907f7a33ea43166b26a8" - integrity sha512-gHTXTj04TUgbjB8y7pkVwxOiuCuD6aU5gnFzIByQuqdgFpe/bJaaEIS4geGjbjWbd1XJh6zG1EthLfpNaXEqUw== +vscode-textmate@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.1.1.tgz#857e836fbc13a376ec624242437e1747d79610a9" + integrity sha512-xBjq9LH6fMhWDhIVkbKlB1JeCu6lT3FI/QKN24Xi4RKPBUm16IhHTqs6Q6SUGewkNsFZGkb1tJdZsuMnlmVpgw== dependencies: oniguruma "^7.0.0" @@ -9822,11 +9826,6 @@ vscode-windows-registry@1.0.1: dependencies: nan "^2.12.1" -vscode-xterm@3.14.0-beta3: - version "3.14.0-beta3" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.14.0-beta3.tgz#e2624e231f7b940edd0675eb569a10b3af75e29d" - integrity sha512-80Bbq6R4q0xABJl7COwE1DSNoJp3H2LyfKDHSbh1PwUY+6wofpW8/V9xSYELizeg3lVAd7mcEKW+rG+sY1hpqA== - vso-node-api@6.1.2-preview: version "6.1.2-preview" resolved "https://registry.yarnpkg.com/vso-node-api/-/vso-node-api-6.1.2-preview.tgz#aab3546df2451ecd894e071bb99b5df19c5fa78f" @@ -10096,6 +10095,21 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" +xterm-addon-search@0.1.0-beta5: + version "0.1.0-beta5" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.1.0-beta5.tgz#a9ad03d8fc02f8dfaab57809d446d4100b194fda" + integrity sha512-+50eYWs6D77kDHOFxaVss6r4lRskvkrmEXiz5x5aAgO07YIhOfQ3S0RFnQPch2kQ1mS66hsRnnUf0NGNEIZ1+A== + +xterm-addon-web-links@0.1.0-beta9: + version "0.1.0-beta9" + resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.1.0-beta9.tgz#8d30e41f54887ba668974d736488518322bea2c5" + integrity sha512-2UbhFqYMNGY2Eg6jrn+j2jpK2gGRqluiF5I++lSDYbKrxtfj7+WJZYhBzb0EeE7gbzDfcGZqsoueM/q+fAWB8Q== + +xterm@3.15.0-beta23: + version "3.15.0-beta23" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.15.0-beta23.tgz#a10b47bf3ebf30df253f7a7dac60c97f74e171f0" + integrity sha512-bMesTUsJP5M2Jhxe6u2FXuzQ3+R997eN8E6ike+0rbijnGLy2Sz+kZbNZQlBjCYO3jEEscRvaBsfiDcNQQVefA== + y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" @@ -10209,7 +10223,7 @@ yauzl@2.4.1: dependencies: fd-slicer "~1.0.1" -yauzl@^2.2.1, yauzl@^2.3.1, yauzl@^2.9.1: +yauzl@^2.2.1, yauzl@^2.3.1: version "2.9.1" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f" integrity sha1-qBmB6nCleUYTOIPwKcWCGok1mn8= @@ -10217,6 +10231,14 @@ yauzl@^2.2.1, yauzl@^2.3.1, yauzl@^2.9.1: buffer-crc32 "~0.2.3" fd-slicer "~1.0.1" +yauzl@^2.9.2: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yazl@^2.2.1, yazl@^2.2.2, yazl@^2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.4.3.tgz#ec26e5cc87d5601b9df8432dbdd3cd2e5173a071"